/**
  * Save a return entity into the database.
  *
  * @todo   Update to handle multiple return items.
  * @todo   Break this up into either events or smaller classes handling
  *         each separate responsibility.
  *
  * @param  Entity\OrderReturn $return
  * @return Entity\OrderReturn
  */
 public function create(Entity\OrderReturn $return)
 {
     $isStandalone = !($return->item->order and $return->item->order->id);
     $this->_validate($return);
     $this->_orderCreate->setTransaction($this->_trans);
     $this->_noteCreate->setTransaction($this->_trans);
     $this->_paymentCreate->setTransaction($this->_trans);
     $this->_refundCreate->setTransaction($this->_trans);
     $this->_stockManager->setTransaction($this->_trans);
     $this->_orderItemEdit->setTransaction($this->_trans);
     $this->_orderRefundCreate->setTransaction($this->_trans);
     $this->_orderPaymentCreate->setTransaction($this->_trans);
     $this->_stockManager->setAutomated(true);
     $this->_stockManager->createWithRawNote(true);
     // Get the return item status
     $statusCode = $return->item->status ? $return->item->status->code : Statuses::AWAITING_RETURN;
     // Set create authorship data if not already set
     if (!$return->authorship->createdAt()) {
         $return->authorship->create(new DateTimeImmutable(), $this->_currentUser->id);
     }
     // Create the return
     $this->_trans->run("\n\t\t\tINSERT INTO\n\t\t\t\t`return`\n\t\t\tSET\n\t\t\t\tcreated_at   = :createdAt?i,\n\t\t\t\tcreated_by   = :createdBy?i,\n\t\t\t\tcompleted_at = :completedAt?i,\n\t\t\t\tcompleted_by = :completedBy?i,\n\t\t\t\ttype         = :type?s,\n\t\t\t\tcurrency_id  = :currencyID?s\n\t\t", ['createdAt' => $return->authorship->createdAt(), 'createdBy' => $return->authorship->createdBy(), 'completedAt' => $statusCode == Statuses::RETURN_COMPLETED ? $return->authorship->createdAt() : null, 'completedBy' => $statusCode == Statuses::RETURN_COMPLETED ? $return->authorship->createdBy() : null, 'type' => $return->type, 'currencyID' => $return->currencyID]);
     $this->_trans->setIDVariable(self::MYSQL_ID_VAR);
     $return->id = '@' . self::MYSQL_ID_VAR;
     // Get the order for the return for quick reference
     $order = $return->item->order;
     // If this is a standalone exchange, create an order for entities to be
     // attached to and representing the original sale.
     if ($isStandalone && $return->item->exchangeItem) {
         $order = clone $this->_newOrder;
     }
     if ($order && !$order->currencyID) {
         $order->currencyID = $return->currencyID;
     }
     if ($order && !$order->type) {
         $order->type = 'standalone-return';
     }
     // Create the related note if there is one
     if ($order && $return->item->note) {
         $return->item->note->order = $order;
         $order->notes->append($return->item->note);
         if (!$isStandalone) {
             $this->_noteCreate->create($return->item->note);
         }
     }
     // Create the related payments if there are any
     if ($return->payments) {
         foreach ($return->payments as $payment) {
             // Set the currency id to match the return if null
             if (!$payment->currencyID) {
                 $payment->currencyID = $return->currencyID;
             }
             $this->_trans->run("\n\t\t\t\t\tINSERT INTO\n\t\t\t\t\t\t`return_payment`\n\t\t\t\t\tSET\n\t\t\t\t\t\treturn_id  = :returnID?i,\n\t\t\t\t\t\tpayment_id = :paymentID?i\n\t\t\t\t", ['returnID' => $return->id, 'paymentID' => $payment->id]);
             $this->_paymentCreate->create($payment);
             if ($order) {
                 $orderPayment = new OrderPayment($payment);
                 $orderPayment->order = $order;
                 $order->payments->append($orderPayment);
                 if (!$isStandalone) {
                     $this->_orderPaymentCreate->create($orderPayment);
                 }
             }
         }
     }
     // Create the related refunds if there are any
     if ($return->refunds) {
         foreach ($return->refunds as $refund) {
             // Set the currency id to match the return if null
             if (!$refund->currencyID) {
                 $refund->currencyID = $return->currencyID;
             }
             $this->_refundCreate->create($refund);
             $this->_trans->run("\n\t\t\t\t\tINSERT INTO\n\t\t\t\t\t\t`return_refund`\n\t\t\t\t\tSET\n\t\t\t\t\t\treturn_id = :returnID?i,\n\t\t\t\t\t\trefund_id = :refundID?i\n\t\t\t\t", ['returnID' => $return->id, 'refundID' => $refund->id]);
             if ($order) {
                 $orderRefund = new OrderRefund($refund);
                 $orderRefund->order = $order;
                 $order->refunds->append($orderRefund);
                 if (!$isStandalone) {
                     $this->_orderRefundCreate->create($orderRefund);
                 }
             }
         }
     }
     // Create the related exchange item, set the status and move it's stock
     if ($return->item->exchangeItem) {
         if (!$return->item->exchangeItem->status) {
             $return->item->exchangeItem->status = clone $this->_orderItemStatusCollection->get(OrderItemStatuses::HOLD);
         }
         if (!$return->item->exchangeItem->stockLocation) {
             $return->item->exchangeItem->stockLocation = $this->_stockLocations->getRoleLocation(StockLocations::SELL_ROLE);
         }
         $return->item->exchangeItem->order = $order;
         $order->items->append($return->item->exchangeItem);
         if (!$isStandalone) {
             $return->item->exchangeItem = $this->_orderItemCreate->create($return->item->exchangeItem);
         }
     }
     // If there is a related order item update its status
     if ($return->item->orderItem) {
         $this->_orderItemEdit->updateStatus($return->item->orderItem, $statusCode);
     }
     // If this is a standalone return, create the new order
     if ($isStandalone && $order) {
         $this->_orderCreate->create($order);
     }
     // Get the values for the return item
     $returnItemValues = array_merge((array) $return->item, ['returnID' => $return->id, 'orderID' => !$isStandalone && $order ? $order->id : null, 'orderItemID' => $return->item->orderItem ? $return->item->orderItem->id : null, 'exchangeItemID' => $return->item->exchangeItem ? $return->item->exchangeItem->id : null, 'noteID' => $return->item->note ? $return->item->note->id : null, 'createdAt' => $return->authorship->createdAt(), 'createdBy' => $return->authorship->createdBy(), 'completedAt' => $statusCode == Statuses::RETURN_COMPLETED ? $return->authorship->createdAt() : null, 'completedBy' => $statusCode == Statuses::RETURN_COMPLETED ? $return->authorship->createdBy() : null, 'statusCode' => $statusCode, 'reason' => $return->item->reason->code, 'returnedStockLocation' => $return->item->returnedStockLocation ? $return->item->returnedStockLocation->name : null, 'returnedStock' => $return->item->returnedStock]);
     // Create the return item
     $itemResult = $this->_trans->run("\n\t\t\tINSERT INTO\n\t\t\t\t`return_item`\n\t\t\tSET\n\t\t\t\treturn_id               = :returnID?i,\n\t\t\t\torder_id                = :orderID?in,\n\t\t\t\titem_id                 = :orderItemID?in,\n\t\t\t\texchange_item_id        = :exchangeItemID?in,\n\t\t\t\tnote_id                 = :noteID?in,\n\t\t\t\tcreated_at              = :createdAt?i,\n\t\t\t\tcreated_by              = :createdBy?i,\n\t\t\t\tcompleted_at            = :completedAt?in,\n\t\t\t\tcompleted_by            = :completedBy?in,\n\t\t\t\tstatus_code             = :statusCode?i,\n\t\t\t\treason                  = :reason?s,\n\t\t\t\taccepted                = :accepted?bn,\n\t\t\t\tbalance                 = :balance?fn,\n\t\t\t\tcalculated_balance      = :calculatedBalance?fn,\n\t\t\t\tremaining_balance       = :remainingBalance?fn,\n\t\t\t\treturned_value          = :returnedValue?f,\n\t\t\t\treturned_stock_location = :returnedStockLocation?s,\n\t\t\t\treturned_stock          = :returnedStock?b,\n\t\t\t\tlist_price              = :listPrice?f,\n\t\t\t\tactual_price            = :actualPrice?f,\n\t\t\t\tnet                     = :net?f,\n\t\t\t\tdiscount                = :discount?f,\n\t\t\t\ttax                     = :tax?f,\n\t\t\t\tgross                   = :gross?f,\n\t\t\t\trrp                     = :rrp?f,\n\t\t\t\ttax_rate                = :taxRate?f,\n\t\t\t\tproduct_tax_rate        = :productTaxRate?f,\n\t\t\t\ttax_strategy            = :taxStrategy?s,\n\t\t\t\tproduct_id              = :productID?i,\n\t\t\t\tproduct_name            = :productName?s,\n\t\t\t\tunit_id                 = :unitID?i,\n\t\t\t\tunit_revision           = :unitRevision?s,\n\t\t\t\tsku                     = :sku?s,\n\t\t\t\tbarcode                 = :barcode?s,\n\t\t\t\toptions                 = :options?s,\n\t\t\t\tbrand                   = :brand?s,\n\t\t\t\tweight_grams            = :weight?i\n\t\t", $returnItemValues);
     // Insert item tax rates
     $tokens = [];
     $inserts = [];
     $this->_trans->setIDVariable(self::MYSQL_ITEM_ID_VAR);
     $idToken = '@' . self::MYSQL_ITEM_ID_VAR;
     foreach ($return->item->taxes as $type => $rate) {
         $tokens[] = '(?i, ?s, ?f, ?f)';
         $inserts[] = $idToken;
         $inserts[] = $type;
         $inserts[] = $rate;
         $inserts[] = $return->item->net * $rate / 100;
     }
     if ($inserts) {
         $this->_trans->run("INSERT INTO \n\t\t\t\t\t`return_item_tax` (`return_item_id`, `tax_type`, `tax_rate`, `tax_amount`) \n\t\t\t\tVALUES " . implode(',', $tokens) . ";", $inserts);
     }
     // set stock manager's properties, because we can't change them anymore
     // once an adjustment was added...
     if (true === $return->item->accepted) {
         if ($return->item->order) {
             $this->_trans->run("SET @STOCK_NOTE = CONCAT('Order #', CONCAT(:orderID?i, CONCAT(', Return #', :returnID?i)));", ['orderID' => $return->item->order->id, 'returnID' => $return->id]);
         } else {
             $this->_trans->run("SET @STOCK_NOTE = CONCAT('Standalone Return #', ?i);", $return->id);
         }
         $this->_stockManager->setReason($this->_stockMovementReasons->get(ReturnStockMovementReasons::RETURNED));
     }
     if (!$isStandalone && $return->item->exchangeItem) {
         $this->_trans->run("SET @STOCK_NOTE = CONCAT('Order #', CONCAT(:orderID?i, CONCAT(', Return #', CONCAT(:returnID?i, ', Exchange Item requested'))));", ['orderID' => $return->item->order->id, 'returnID' => $return->id]);
         $this->_stockManager->setReason($this->_stockMovementReasons->get(ReturnStockMovementReasons::EXCHANGE_ITEM));
     }
     $this->_stockManager->setNote('@STOCK_NOTE');
     // if the return is already accepted, immediatly move returned item back
     // to stock
     if (true === $return->item->accepted) {
         $this->_stockManager->increment($return->item->unit, $return->item->returnedStockLocation);
     }
     // Adjust the stock if this is an exchange and the newly created order
     // doesn't take care of this
     if (!$isStandalone && $return->item->exchangeItem) {
         $unit = $return->item->exchangeItem->getUnit();
         // Decrement from sell stock
         $this->_stockManager->decrement($unit, $return->item->exchangeItem->stockLocation);
         if (null === $return->item->accepted) {
             // Increment in hold stock
             $this->_stockManager->increment($unit, $this->_stockLocations->getRoleLocation(StockLocations::HOLD_ROLE));
         }
     }
     // Fire the created event
     $event = new Event($return);
     $event->setTransaction($this->_trans);
     $return = $this->_eventDispatcher->dispatch(Events::CREATE_END, $event)->getReturn();
     // Commit all the changes
     if (!$this->_transOverridden) {
         $this->_trans->commit();
         $return->id = $this->_trans->getIDVariable(self::MYSQL_ID_VAR);
         $this->_eventDispatcher->dispatch(Events::CREATE_COMPLETE, $event);
     }
     return $return;
 }