classes/XLite/Model/Order.php line 81

Open in your IDE?
  1. <?php
  2. /**
  3.  * Copyright (c) 2011-present Qualiteam software Ltd. All rights reserved.
  4.  * See https://www.x-cart.com/license-agreement.html for license details.
  5.  */
  6. namespace XLite\Model;
  7. use ApiPlatform\Core\Annotation as ApiPlatform;
  8. use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
  9. use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
  10. use Doctrine\Common\Collections\ArrayCollection;
  11. use Doctrine\DBAL\LockMode;
  12. use Doctrine\ORM\EntityManager;
  13. use Doctrine\ORM\Mapping as ORM;
  14. use XCart\Domain\GmvTrackerDomain;
  15. use XCart\Framework\ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\IntegerDateFilter;
  16. use XLite\API\Endpoint\Order\DTO\OrderOutput as Output;
  17. use XLite\Core\Database;
  18. use XLite\Model\Order\Surcharge;
  19. use XLite\Model\Payment\Transaction;
  20. /**
  21.  * Class represents an order
  22.  *
  23.  * @ORM\Entity
  24.  * @ORM\Table  (name="orders",
  25.  *      indexes={
  26.  *          @ORM\Index (name="date", columns={"date"}),
  27.  *          @ORM\Index (name="total", columns={"total"}),
  28.  *          @ORM\Index (name="subtotal", columns={"subtotal"}),
  29.  *          @ORM\Index (name="tracking", columns={"tracking"}),
  30.  *          @ORM\Index (name="payment_status", columns={"payment_status_id"}),
  31.  *          @ORM\Index (name="shipping_status", columns={"shipping_status_id"}),
  32.  *          @ORM\Index (name="shipping_id", columns={"shipping_id"}),
  33.  *          @ORM\Index (name="lastRenewDate", columns={"lastRenewDate"}),
  34.  *          @ORM\Index (name="orderNumber", columns={"orderNumber"}),
  35.  *          @ORM\Index (name="is_order", columns={"is_order"}),
  36.  *          @ORM\Index (name="xcPendingExport", columns={"xcPendingExport"})
  37.  *      }
  38.  * )
  39.  *
  40.  * @ ClearDiscriminatorCondition
  41.  * @ORM\InheritanceType       ("SINGLE_TABLE")
  42.  * @ORM\DiscriminatorColumn   (name="is_order", type="integer", length=1)
  43.  * @ORM\DiscriminatorMap      ({1 = "XLite\Model\Order", 0 = "XLite\Model\Cart"})
  44.  * @ApiPlatform\ApiResource(
  45.  *     output=Output::class,
  46.  *     itemOperations={
  47.  *          "get"={
  48.  *              "method"="GET",
  49.  *              "path"="/orders/{id}.{_format}",
  50.  *              "identifiers"={"id"},
  51.  *              "requirements"={"id"="\d+"},
  52.  *              "openapi_context"={
  53.  *                  "summary"="Retrieve an order by order id",
  54.  *                  "parameters"={
  55.  *                      {"name"="id", "in"="path", "required"=true, "schema"={"type"="integer"}}
  56.  *                  }
  57.  *             },
  58.  *          },
  59.  *          "get_by_number"={
  60.  *              "method"="GET",
  61.  *              "path"="/orders/by_number/{orderNumber}.{_format}",
  62.  *              "requirements"={"orderNumber"="\w+"},
  63.  *              "identifiers"={"orderNumber"},
  64.  *              "openapi_context"={
  65.  *                  "summary"="Retrieve an order by order number",
  66.  *                  "parameters"={
  67.  *                      {"name"="orderNumber", "in"="path", "required"=true, "schema"={"type"="string"}}
  68.  *                  }
  69.  *             },
  70.  *          }
  71.  *     },
  72.  *     collectionOperations={
  73.  *          "get"={
  74.  *              "method"="GET",
  75.  *              "path"="/orders.{_format}",
  76.  *              "identifiers"={"id"}
  77.  *          }
  78.  *     }
  79.  * )
  80.  * @ApiPlatform\ApiFilter(IntegerDateFilter::class, properties={"date", "lastRenewDate"})
  81.  * @ApiPlatform\ApiFilter(OrderFilter::class, properties={"date","lastRenewDate"}, arguments={"orderParameterName"="order"})
  82.  * @ApiPlatform\ApiFilter(SearchFilter::class, properties={"paymentStatus.code"="exact", "shippingStatus.code"="exact", "profile.login"="partial"})
  83.  */
  84. class Order extends \XLite\Model\Base\SurchargeOwner
  85. {
  86.     use \XLite\Core\Cache\ExecuteCachedTrait;
  87.     /**
  88.      * Order total that is financially declared as zero (null)
  89.      */
  90.     public const ORDER_ZERO 0.00001;
  91.     /**
  92.      * Add item error codes
  93.      */
  94.     public const NOT_VALID_ERROR 'notValid';
  95.     public const DEFAULT_SALES_CHANNEL 'Online store';
  96.     /**
  97.      * Order unique id
  98.      *
  99.      * @var mixed
  100.      *
  101.      * @ORM\Id
  102.      * @ORM\GeneratedValue (strategy="AUTO")
  103.      * @ORM\Column         (type="integer")
  104.      */
  105.     protected $order_id;
  106.     /**
  107.      * Order unique id
  108.      *
  109.      * @var string|null
  110.      *
  111.      * @ORM\Column (type="string", length=36, nullable=true, options={"fixed": true})
  112.      */
  113.     protected ?string $public_id null;
  114.     /**
  115.      * Order profile
  116.      *
  117.      * @var \XLite\Model\Profile
  118.      *
  119.      * @ORM\OneToOne   (targetEntity="XLite\Model\Profile", cascade={"merge","detach","persist"})
  120.      * @ORM\JoinColumn (name="profile_id", referencedColumnName="profile_id", onDelete="CASCADE")
  121.      */
  122.     protected $profile;
  123.     /**
  124.      * Original profile
  125.      *
  126.      * @var \XLite\Model\Profile
  127.      *
  128.      * @ORM\ManyToOne  (targetEntity="XLite\Model\Profile", cascade={"merge","detach","persist"})
  129.      * @ORM\JoinColumn (name="orig_profile_id", referencedColumnName="profile_id", onDelete="SET NULL")
  130.      */
  131.     protected $orig_profile;
  132.     /**
  133.      * Shipping method unique id
  134.      *
  135.      * @var integer
  136.      *
  137.      * @ORM\Column (type="integer")
  138.      */
  139.     protected $shipping_id 0;
  140.     /**
  141.      * Shipping method name
  142.      *
  143.      * @var string
  144.      *
  145.      * @ORM\Column (type="string", nullable=true)
  146.      */
  147.     protected $shipping_method_name '';
  148.     /**
  149.      * Payment method name
  150.      *
  151.      * @var string
  152.      *
  153.      * @ORM\Column (type="string", nullable=true)
  154.      */
  155.     protected $payment_method_name '';
  156.     /**
  157.      * @var string
  158.      *
  159.      * @ORM\Column (type="string", nullable=true)
  160.      */
  161.     protected $salesChannel self::DEFAULT_SALES_CHANNEL;
  162.     /**
  163.      * Shipping tracking code
  164.      *
  165.      * @var string
  166.      *
  167.      * @ORM\Column (type="string", length=32)
  168.      */
  169.     protected $tracking '';
  170.     /**
  171.      * Order creation timestamp
  172.      *
  173.      * @var integer
  174.      *
  175.      * @ORM\Column (type="integer")
  176.      */
  177.     protected $date;
  178.     /**
  179.      * Last order renew date
  180.      *
  181.      * @var integer
  182.      *
  183.      * @ORM\Column (type="integer")
  184.      */
  185.     protected $lastRenewDate 0;
  186.     /**
  187.      * Payment status
  188.      *
  189.      * @var \XLite\Model\Order\Status\Payment
  190.      *
  191.      * @ORM\ManyToOne  (targetEntity="XLite\Model\Order\Status\Payment")
  192.      * @ORM\JoinColumn (name="payment_status_id", referencedColumnName="id", onDelete="SET NULL")
  193.      */
  194.     protected $paymentStatus;
  195.     /**
  196.      * Shipping status
  197.      *
  198.      * @var \XLite\Model\Order\Status\Shipping
  199.      *
  200.      * @ORM\ManyToOne  (targetEntity="XLite\Model\Order\Status\Shipping")
  201.      * @ORM\JoinColumn (name="shipping_status_id", referencedColumnName="id", onDelete="SET NULL")
  202.      */
  203.     protected $shippingStatus;
  204.     /**
  205.      * Customer notes
  206.      *
  207.      * @var string
  208.      *
  209.      * @ORM\Column (type="text")
  210.      */
  211.     protected $notes '';
  212.     /**
  213.      * Admin notes
  214.      *
  215.      * @var string
  216.      *
  217.      * @ORM\Column (type="text")
  218.      */
  219.     protected $adminNotes '';
  220.     /**
  221.      * Order details
  222.      *
  223.      * @var \Doctrine\Common\Collections\Collection
  224.      *
  225.      * @ORM\OneToMany (targetEntity="XLite\Model\OrderDetail", mappedBy="order", cascade={"all"})
  226.      * @ORM\OrderBy   ({"name" = "ASC"})
  227.      */
  228.     protected $details;
  229.     /**
  230.      * Order tracking numbers
  231.      *
  232.      * @var \Doctrine\Common\Collections\Collection
  233.      *
  234.      * @ORM\OneToMany (targetEntity="XLite\Model\OrderTrackingNumber", mappedBy="order", cascade={"all"})
  235.      */
  236.     protected $trackingNumbers;
  237.     /**
  238.      * Order events queue
  239.      *
  240.      * @var \Doctrine\Common\Collections\Collection
  241.      *
  242.      * @ORM\OneToMany (targetEntity="XLite\Model\OrderHistoryEvents", mappedBy="order", cascade={"all"})
  243.      */
  244.     protected $events;
  245.     /**
  246.      * Order items
  247.      *
  248.      * @var \Doctrine\Common\Collections\Collection|\XLite\Model\OrderItem[]
  249.      *
  250.      * @ORM\OneToMany (targetEntity="XLite\Model\OrderItem", mappedBy="order", cascade={"all"})
  251.      */
  252.     protected $items;
  253.     /**
  254.      * Order surcharges
  255.      *
  256.      * @var \Doctrine\Common\Collections\Collection
  257.      *
  258.      * @ORM\OneToMany (targetEntity="XLite\Model\Order\Surcharge", mappedBy="owner", cascade={"all"})
  259.      * @ORM\OrderBy   ({"weight" = "ASC", "id" = "ASC"})
  260.      */
  261.     protected $surcharges;
  262.     /**
  263.      * Payment transactions
  264.      *
  265.      * @var \Doctrine\Common\Collections\Collection
  266.      *
  267.      * @ORM\OneToMany (targetEntity="XLite\Model\Payment\Transaction", mappedBy="order", cascade={"all"})
  268.      */
  269.     protected $payment_transactions;
  270.     /**
  271.      * Currency
  272.      *
  273.      * @var \XLite\Model\Currency
  274.      *
  275.      * @ORM\ManyToOne  (targetEntity="XLite\Model\Currency", inversedBy="orders", cascade={"merge","detach"})
  276.      * @ORM\JoinColumn (name="currency_id", referencedColumnName="currency_id", onDelete="CASCADE")
  277.      */
  278.     protected $currency;
  279.     /**
  280.      * Unique order identificator (it is working for orders only, not for cart entities)
  281.      *
  282.      * @var integer
  283.      *
  284.      * @ORM\Column (type="string", length=255, nullable=true)
  285.      */
  286.     protected $orderNumber;
  287.     /**
  288.      * Recent flag: true if order's statuses were not changed manually by an administrator, otherwise - false
  289.      *
  290.      * @var boolean
  291.      *
  292.      * @ORM\Column (type="boolean")
  293.      */
  294.     protected $recent true;
  295.     /**
  296.      * @var string
  297.      *
  298.      * @ORM\Column (type="string", length=255, nullable=true)
  299.      */
  300.     protected $stockStatus '';
  301.     /**
  302.      * 'Add item' error code
  303.      *
  304.      * @var string
  305.      */
  306.     protected $addItemError;
  307.     /**
  308.      * Order previous payment status
  309.      *
  310.      * @var \XLite\Model\Order\Status\Payment
  311.      */
  312.     protected $oldPaymentStatus;
  313.     /**
  314.      * Flag: true - order is prepared for removing
  315.      *
  316.      * @var boolean
  317.      */
  318.     protected $isRemoving false;
  319.     /**
  320.      * Order previous shipping status
  321.      *
  322.      * @var \XLite\Model\Order\Status\Shipping
  323.      */
  324.     protected $oldShippingStatus;
  325.     /**
  326.      * Modifiers (cache)
  327.      *
  328.      * @var \XLite\DataSet\Collection\OrderModifier
  329.      */
  330.     protected $modifiers;
  331.     /**
  332.      * Shipping carrier object cache.
  333.      * Use $this->getShippingProcessor() method to retrieve.
  334.      *
  335.      * @var \XLite\Model\Shipping\Processor\AProcessor
  336.      */
  337.     protected $shippingProcessor;
  338.     /**
  339.      * Check if notification sent by any status handler to avoid extra 'changed' notification
  340.      *
  341.      * @var boolean
  342.      */
  343.     protected $isNotificationSent false;
  344.     /**
  345.      * Flag: Ignore sending notifications to a customer if true
  346.      *
  347.      * @var boolean
  348.      */
  349.     protected $ignoreCustomerNotifications false;
  350.     /**
  351.      * Flag: is email notifications are allowed for the order
  352.      *
  353.      * @var boolean
  354.      */
  355.     protected $isNotificationsAllowedFlag true;
  356.     /**
  357.      * Check status is set or not
  358.      *
  359.      * @var boolean
  360.      */
  361.     protected $statusIsSet false;
  362.     /**
  363.      * Payment transaction sums
  364.      *
  365.      * @var array
  366.      */
  367.     protected $paymentTransactionSums;
  368.     /**
  369.      * Source address
  370.      *
  371.      * @var \XLite\Model\Address
  372.      */
  373.     protected $sourceAddress;
  374.     /**
  375.      * Flag to exporting entities
  376.      *
  377.      * @var boolean
  378.      *
  379.      * @ORM\Column (type="boolean")
  380.      */
  381.     protected $xcPendingExport false;
  382.     /**
  383.      * Profiles
  384.      *
  385.      * @var ArrayCollection
  386.      *
  387.      * @ORM\ManyToMany (targetEntity="XLite\Model\Order")
  388.      * @ORM\JoinTable  (
  389.      *      name="order_backorder_competitors",
  390.      *      joinColumns={@ORM\JoinColumn(name="id", referencedColumnName="order_id", onDelete="CASCADE")},
  391.      *      inverseJoinColumns={@ORM\JoinColumn(name="competitor_id", referencedColumnName="order_id", onDelete="CASCADE")}
  392.      * )
  393.      */
  394.     protected $backorderCompetitors;
  395.     protected $backorderProductAmounts = [];
  396.     /**
  397.      * If entity temporary
  398.      *
  399.      * @var bool
  400.      */
  401.     protected $temporary false;
  402.     /**
  403.      * @return boolean
  404.      */
  405.     public function isTemporary()
  406.     {
  407.         return $this->temporary;
  408.     }
  409.     /**
  410.      * @param boolean $temporary
  411.      */
  412.     public function setTemporary($temporary)
  413.     {
  414.         $this->temporary $temporary;
  415.     }
  416.     /**
  417.      * Flag if cart become order in current runtime
  418.      *
  419.      * @var bool
  420.      */
  421.     protected $justClosed false;
  422.     /**
  423.      * @return bool
  424.      */
  425.     public function isJustClosed()
  426.     {
  427.         return $this->justClosed;
  428.     }
  429.     /**
  430.      * @param bool $justClosed
  431.      */
  432.     protected function setJustClosed($justClosed)
  433.     {
  434.         $this->justClosed $justClosed;
  435.     }
  436.     /**
  437.      * Add item to order
  438.      *
  439.      * @param \XLite\Model\OrderItem $newItem Item to add
  440.      *
  441.      * @return boolean
  442.      */
  443.     public function addItem(\XLite\Model\OrderItem $newItem)
  444.     {
  445.         $result false;
  446.         if ($newItem->isValid() && $newItem->isConfigured()) {
  447.             $this->addItemError null;
  448.             $newItem->setOrder($this);
  449.             $item $this->getItemByItem($newItem);
  450.             $warningText '';
  451.             if ($item) {
  452.                 $warningText $item->getAmountWarning(
  453.                     $item->getAmount() + $newItem->getAmount()
  454.                 );
  455.                 $item->setAmount($item->getAmount() + $newItem->getAmount());
  456.                 $item->refreshUpdateDate();
  457.             } else {
  458.                 $newItem->refreshUpdateDate();
  459.                 $this->addItems($newItem);
  460.             }
  461.             $result $warningText === '';
  462.             if ($warningText !== '') {
  463.                 \XLite\Core\TopMessage::addWarning($warningText);
  464.             }
  465.         } else {
  466.             $this->addItemError = static::NOT_VALID_ERROR;
  467.         }
  468.         return $result;
  469.     }
  470.     /**
  471.      * Remove item from order
  472.      *
  473.      * @param \XLite\Model\OrderItem $item
  474.      */
  475.     public function removeItem(\XLite\Model\OrderItem $item)
  476.     {
  477.         if ($item->getAmount() > 1) {
  478.             $item->setAmount($item->getAmount() - 1);
  479.         } else {
  480.             $this->getItems()->removeElement($item);
  481.             \XLite\Core\Database::getEM()->remove($item);
  482.         }
  483.     }
  484.     /**
  485.      * Get 'Add item' error code
  486.      *
  487.      * @return string|void
  488.      */
  489.     public function getAddItemError()
  490.     {
  491.         return $this->addItemError;
  492.     }
  493.     /**
  494.      * Get item from order by another item
  495.      *
  496.      * @param \XLite\Model\OrderItem $item Another item
  497.      *
  498.      * @return \XLite\Model\OrderItem|void
  499.      */
  500.     public function getItemByItem(\XLite\Model\OrderItem $item)
  501.     {
  502.         return $this->getItemByItemKey($item->getKey());
  503.     }
  504.     /**
  505.      * Get item from order by item key
  506.      *
  507.      * @param string $key Item key
  508.      *
  509.      * @return \XLite\Model\OrderItem|void
  510.      */
  511.     public function getItemByItemKey($key)
  512.     {
  513.         $items $this->getItems();
  514.         return \Includes\Utils\ArrayManager::findValue(
  515.             $items,
  516.             [$this'checkItemKeyEqual'],
  517.             $key
  518.         );
  519.     }
  520.     /**
  521.      * Get item from order by item  id
  522.      *
  523.      * @param integer $itemId Item id
  524.      *
  525.      * @return \XLite\Model\OrderItem|void
  526.      */
  527.     public function getItemByItemId($itemId)
  528.     {
  529.         $items $this->getItems();
  530.         return \Includes\Utils\ArrayManager::findValue(
  531.             $items,
  532.             [$this'checkItemIdEqual'],
  533.             $itemId
  534.         );
  535.     }
  536.     /**
  537.      * Find items by product ID
  538.      *
  539.      * @param integer $productId Product ID to use
  540.      *
  541.      * @return array
  542.      */
  543.     public function getItemsByProductId($productId)
  544.     {
  545.         $items $this->getItems();
  546.         return \Includes\Utils\ArrayManager::filter(
  547.             $items,
  548.             [$this'isItemProductIdEqual'],
  549.             $productId
  550.         );
  551.     }
  552.     /**
  553.      * Normalize items
  554.      *
  555.      * @throws \Doctrine\ORM\ORMInvalidArgumentException
  556.      *
  557.      * @return void
  558.      */
  559.     public function normalizeItems()
  560.     {
  561.         // Normalize by key
  562.         $keys = [];
  563.         foreach ($this->getItems() as $item) {
  564.             $key $item->getKey();
  565.             if (isset($keys[$key])) {
  566.                 $keys[$key]->setAmount($keys[$key]->getAmount() + $item->getAmount());
  567.                 $this->getItems()->removeElement($item);
  568.                 if (\XLite\Core\Database::getEM()->contains($item)) {
  569.                     \XLite\Core\Database::getEM()->remove($item);
  570.                 }
  571.             } else {
  572.                 $keys[$key] = $item;
  573.             }
  574.         }
  575.         unset($keys);
  576.         // Remove invalid items
  577.         foreach ($this->getItems() as $item) {
  578.             if (!$item->isValid()) {
  579.                 $this->getItems()->removeElement($item);
  580.                 if (\XLite\Core\Database::getEM()->contains($item)) {
  581.                     \XLite\Core\Database::getEM()->remove($item);
  582.                 }
  583.             }
  584.         }
  585.     }
  586.     /**
  587.      * Return items number
  588.      *
  589.      * @return integer
  590.      */
  591.     public function countItems()
  592.     {
  593.         return count($this->getItems());
  594.     }
  595.     /**
  596.      * Return order items total quantity
  597.      *
  598.      * @return integer
  599.      */
  600.     public function countQuantity()
  601.     {
  602.         $quantity 0;
  603.         foreach ($this->getItems() as $item) {
  604.             $quantity += $item->getAmount();
  605.         }
  606.         return $quantity;
  607.     }
  608.     /**
  609.      * Get failure reason
  610.      *
  611.      * @return string
  612.      */
  613.     public function getFailureReason()
  614.     {
  615.         $transactions $this->getPaymentTransactions()->getValues();
  616.         /** @var \XLite\Model\Payment\Transaction $transaction */
  617.         foreach (array_reverse($transactions) as $transaction) {
  618.             if ($transaction->isFailed()) {
  619.                 if ($transaction->getNote() && $transaction->getNote() !== Transaction::getDefaultFailedReason()) {
  620.                     return $transaction->getNote();
  621.                 }
  622.                 $reason $transaction->getDataCell('status');
  623.                 if ($reason && $reason->getValue()) {
  624.                     return $reason->getValue();
  625.                 }
  626.             }
  627.         }
  628.         return null;
  629.     }
  630.     /**
  631.      * Checks whether the shopping cart/order is empty
  632.      *
  633.      * @return boolean
  634.      */
  635.     public function isEmpty()
  636.     {
  637.         return >= $this->countItems();
  638.     }
  639.     /**
  640.      * Check order subtotal
  641.      *
  642.      * @return boolean
  643.      */
  644.     public function isMinOrderAmountError()
  645.     {
  646.         return $this->getSubtotal() < \XLite\Core\Config::getInstance()->General->minimal_order_amount;
  647.     }
  648.     /**
  649.      * Check order subtotal
  650.      *
  651.      * @return boolean
  652.      */
  653.     public function isMaxOrderAmountError()
  654.     {
  655.         return $this->getSubtotal() > \XLite\Core\Config::getInstance()->General->maximal_order_amount;
  656.     }
  657.     /**
  658.      * Return printable order number
  659.      *
  660.      * @return string
  661.      */
  662.     public function getPrintableOrderNumber()
  663.     {
  664.         $orderNumber = (string) $this->getOrderNumber();
  665.         return '#' str_repeat('0'min(5strlen($orderNumber))) . $orderNumber;
  666.     }
  667.     /**
  668.      * Check - is order processed or not
  669.      *
  670.      * @return boolean
  671.      */
  672.     public function isProcessed()
  673.     {
  674.         return in_array(
  675.             $this->getPaymentStatusCode(),
  676.             [
  677.                 \XLite\Model\Order\Status\Payment::STATUS_PART_PAID,
  678.                 \XLite\Model\Order\Status\Payment::STATUS_PAID,
  679.             ],
  680.             true
  681.         );
  682.     }
  683.     /**
  684.      * Check - is order queued or not
  685.      *
  686.      * @return boolean
  687.      */
  688.     public function isQueued()
  689.     {
  690.         return $this->getPaymentStatusCode() === \XLite\Model\Order\Status\Payment::STATUS_QUEUED;
  691.     }
  692.     /**
  693.      * Check item amounts
  694.      *
  695.      * @return array
  696.      */
  697.     public function getItemsWithWrongAmounts()
  698.     {
  699.         $items = [];
  700.         foreach ($this->getItems() as $item) {
  701.             if ($item->hasWrongAmount()) {
  702.                 $items[] = $item;
  703.             }
  704.         }
  705.         return $items;
  706.     }
  707.     /**
  708.      * Set profile
  709.      *
  710.      * @param \XLite\Model\Profile $profile Profile OPTIONAL
  711.      *
  712.      * @throws \Doctrine\ORM\ORMInvalidArgumentException
  713.      *
  714.      * @return void
  715.      */
  716.     public function setProfile(\XLite\Model\Profile $profile null)
  717.     {
  718.         if (
  719.             $this->getProfile()
  720.             && $this->getProfile()->getOrder()
  721.             && (!$profile || $this->getProfile()->getProfileId() != $profile->getProfileId())
  722.         ) {
  723.             $this->getProfile()->setOrder(null);
  724.             if ($this->getProfile()->getAnonymous() && $profile && !$profile->getAnonymous()) {
  725.                 \XLite\Core\Database::getEM()->remove($this->getProfile());
  726.             }
  727.         }
  728.         $this->profile $profile;
  729.     }
  730.     /**
  731.      * Set original profile
  732.      * FIXME: is it really needed?
  733.      *
  734.      * @param \XLite\Model\Profile $profile Profile OPTIONAL
  735.      *
  736.      * @return void
  737.      */
  738.     public function setOrigProfile(\XLite\Model\Profile $profile null)
  739.     {
  740.         if (
  741.             $this->getOrigProfile()
  742.             && $this->getOrigProfile()->getOrder()
  743.             && (!$profile || $this->getOrigProfile()->getProfileId() != $profile->getProfileId())
  744.             && (!$this->getProfile() || $this->getOrigProfile()->getProfileId() != $this->getProfile()->getProfileId())
  745.         ) {
  746.             $this->getOrigProfile()->setOrder(null);
  747.         }
  748.         $this->orig_profile $profile;
  749.     }
  750.     /**
  751.      * Set profile copy
  752.      *
  753.      * @param \XLite\Model\Profile $profile Profile
  754.      *
  755.      * @return void
  756.      */
  757.     public function setProfileCopy(\XLite\Model\Profile $profile)
  758.     {
  759.         // Set profile as original profile
  760.         $this->setOrigProfile($profile);
  761.         // Clone profile and set as order profile
  762.         $clonedProfile $profile->cloneEntity();
  763.         $this->setProfile($clonedProfile);
  764.         $clonedProfile->setOrder($this);
  765.     }
  766.     // {{{ Clone
  767.     /**
  768.      * clone Order as temporary
  769.      *
  770.      * @return \XLite\Model\Order
  771.      */
  772.     public function cloneOrderAsTemporary()
  773.     {
  774.         $isTemporary $this->isTemporary();
  775.         $this->setTemporary(true);
  776.         $result $this->cloneEntity();
  777.         $result->setOrderNumber(null);
  778.         $result->setIsNotificationsAllowedFlag(null);
  779.         $result->setTemporary(true);
  780.         $this->setTemporary($isTemporary);
  781.         return $result;
  782.     }
  783.     /**
  784.      * Clone order and all related data
  785.      *
  786.      * @return \XLite\Model\Order
  787.      */
  788.     public function cloneEntity()
  789.     {
  790.         // Clone order
  791.         $newOrder parent::cloneEntity();
  792.         $this->cloneEntityAssignOrderNumber($newOrder);
  793.         $this->cloneEntityProfile($newOrder);
  794.         $this->cloneEntityCurrency($newOrder);
  795.         $this->cloneEntityOrderStatuses($newOrder);
  796.         $this->cloneOrderItems($newOrder);
  797.         $this->cloneEntityOrderDetails($newOrder);
  798.         $this->cloneEntityTrackingNumbers($newOrder);
  799.         $this->cloneEntityEvents($newOrder);
  800.         $this->cloneEntitySurcharges($newOrder);
  801.         $this->cloneEntityPaymentTransactions($newOrder);
  802.         return $newOrder;
  803.     }
  804.     /**
  805.      * @return string|null
  806.      */
  807.     public function getStockStatus()
  808.     {
  809.         return $this->stockStatus;
  810.     }
  811.     /**
  812.      * @param string|null $stockStatus
  813.      */
  814.     public function setStockStatus($stockStatus)
  815.     {
  816.         $this->stockStatus $stockStatus;
  817.     }
  818.     /**
  819.      * Assign order number for cloned entity
  820.      *
  821.      * @param \XLite\Model\Order $newOrder
  822.      * @throws \Doctrine\ORM\ORMException
  823.      */
  824.     protected function cloneEntityAssignOrderNumber($newOrder)
  825.     {
  826.         if ($this->getOrderNumber() && !$this->isTemporary()) {
  827.             $newOrder->setOrderNumber(
  828.                 \XLite\Core\Database::getRepo('XLite\Model\Order')->findNextOrderNumber()
  829.             );
  830.         }
  831.     }
  832.     /**
  833.      * Assign profiles for cloned entity
  834.      *
  835.      * @param \XLite\Model\Order $newOrder
  836.      */
  837.     protected function cloneEntityProfile($newOrder)
  838.     {
  839.         $newOrder->setOrigProfile($this->getOrigProfile());
  840.         if ($this->getProfile()) {
  841.             $clonedProfile $this->getProfile()->cloneEntity();
  842.             $newOrder->setProfile($clonedProfile);
  843.             $clonedProfile->setOrder($newOrder);
  844.         }
  845.     }
  846.     /**
  847.      * Assign currency for cloned entity
  848.      *
  849.      * @param \XLite\Model\Order $newOrder
  850.      */
  851.     protected function cloneEntityCurrency($newOrder)
  852.     {
  853.         $newOrder->setCurrency($this->getCurrency());
  854.     }
  855.     /**
  856.      * Assign order statuses for cloned entity
  857.      *
  858.      * @param \XLite\Model\Order $newOrder
  859.      */
  860.     protected function cloneEntityOrderStatuses($newOrder)
  861.     {
  862.         $newOrder->setPaymentStatus($this->getPaymentStatus());
  863.         $newOrder->setShippingStatus($this->getShippingStatus());
  864.     }
  865.     /**
  866.      * Assign order details for cloned entity
  867.      *
  868.      * @param \XLite\Model\Order $newOrder
  869.      */
  870.     protected function cloneEntityOrderDetails($newOrder)
  871.     {
  872.         foreach ($this->getDetails() as $detail) {
  873.             $clonedDetails $detail->cloneEntity();
  874.             $newOrder->addDetails($clonedDetails);
  875.             $clonedDetails->setOrder($newOrder);
  876.         }
  877.     }
  878.     /**
  879.      * Assign tracking numbers for cloned entity
  880.      *
  881.      * @param \XLite\Model\Order $newOrder
  882.      */
  883.     protected function cloneEntityTrackingNumbers($newOrder)
  884.     {
  885.         foreach ($this->getTrackingNumbers() as $tn) {
  886.             $clonedTN $tn->cloneEntity();
  887.             $newOrder->addTrackingNumbers($clonedTN);
  888.             $clonedTN->setOrder($newOrder);
  889.         }
  890.     }
  891.     /**
  892.      * Assign events for cloned entity
  893.      *
  894.      * @param \XLite\Model\Order $newOrder
  895.      */
  896.     protected function cloneEntityEvents($newOrder)
  897.     {
  898.         foreach ($this->getEvents() as $event) {
  899.             $cloned $event->cloneEntity();
  900.             $newOrder->addEvents($cloned);
  901.             $cloned->setOrder($newOrder);
  902.             $cloned->setAuthor($event->getAuthor());
  903.         }
  904.     }
  905.     /**
  906.      * Assign surcharges for cloned entity
  907.      *
  908.      * @param \XLite\Model\Order $newOrder
  909.      */
  910.     protected function cloneEntitySurcharges($newOrder)
  911.     {
  912.         foreach ($this->getSurcharges() as $surcharge) {
  913.             $cloned $surcharge->cloneEntity();
  914.             $newOrder->addSurcharges($cloned);
  915.             $cloned->setOwner($newOrder);
  916.         }
  917.     }
  918.     /**
  919.      * Assign payment transactions for cloned entity
  920.      *
  921.      * @param \XLite\Model\Order $newOrder
  922.      */
  923.     protected function cloneEntityPaymentTransactions($newOrder)
  924.     {
  925.         foreach ($this->getPaymentTransactions() as $pt) {
  926.             $cloned $pt->cloneEntity();
  927.             $newOrder->addPaymentTransactions($cloned);
  928.             $cloned->setOrder($newOrder);
  929.         }
  930.     }
  931.     /**
  932.      * Clone order items
  933.      *
  934.      * @param \XLite\Model\Order $newOrder New Order
  935.      *
  936.      * @return void
  937.      */
  938.     protected function cloneOrderItems($newOrder)
  939.     {
  940.         // Clone order items
  941.         foreach ($this->getItems() as $item) {
  942.             $clonedItem $item->cloneEntity();
  943.             $clonedItem->setOrder($newOrder);
  944.             $newOrder->addItems($clonedItem);
  945.         }
  946.     }
  947.     // }}}
  948.     /**
  949.      * Get shipping method name
  950.      *
  951.      * @return string
  952.      */
  953.     public function getShippingMethodName()
  954.     {
  955.         $shipping \XLite\Core\Database::getRepo('XLite\Model\Shipping\Method')->find($this->getShippingId());
  956.         return $shipping $shipping->getName() : $this->shipping_method_name;
  957.     }
  958.     /**
  959.      * Get payment method name
  960.      *
  961.      * @return string
  962.      */
  963.     public function getPaymentMethodName()
  964.     {
  965.         $paymentMethod $this->getPaymentMethod();
  966.         return $paymentMethod $paymentMethod->getName() : $this->payment_method_name;
  967.     }
  968.     /**
  969.      * Get payment method name
  970.      *
  971.      * @return string
  972.      */
  973.     public function getPaymentMethodId()
  974.     {
  975.         $paymentMethod $this->getPaymentMethod();
  976.         return $paymentMethod $paymentMethod->getMethodId() : null;
  977.     }
  978.     /**
  979.      * Set old payment status of the order (not stored in the DB)
  980.      *
  981.      * @param string $paymentStatus Payment status
  982.      *
  983.      * @return void
  984.      */
  985.     public function setOldPaymentStatus($paymentStatus)
  986.     {
  987.         $this->oldPaymentStatus $paymentStatus;
  988.     }
  989.     /**
  990.      * Set old shipping status of the order (not stored in the DB)
  991.      *
  992.      * @param string $shippingStatus Shipping status
  993.      *
  994.      * @return void
  995.      */
  996.     public function setOldShippingStatus($shippingStatus)
  997.     {
  998.         $this->oldShippingStatus $shippingStatus;
  999.     }
  1000.     /**
  1001.      * Get items list fingerprint by amount and key
  1002.      *
  1003.      * @return string
  1004.      */
  1005.     public function getItemsAmountKeyFingerprint()
  1006.     {
  1007.         $result false;
  1008.         if (!$this->isEmpty()) {
  1009.             $items = [];
  1010.             foreach ($this->getItems() as $item) {
  1011.                 $items[] = [
  1012.                     $item->getKey(),
  1013.                     $item->getAmount()
  1014.                 ];
  1015.             }
  1016.             $result md5(serialize($items));
  1017.         }
  1018.         return $result;
  1019.     }
  1020.     /**
  1021.      * Get items list fingerprint
  1022.      *
  1023.      * @return string
  1024.      */
  1025.     public function getItemsFingerprint()
  1026.     {
  1027.         $result false;
  1028.         if (!$this->isEmpty()) {
  1029.             $items = [];
  1030.             foreach ($this->getItems() as $item) {
  1031.                 $items[] = [
  1032.                     $item->getItemId(),
  1033.                     $item->getKey(),
  1034.                     $item->getAmount()
  1035.                 ];
  1036.             }
  1037.             $result md5(serialize($items));
  1038.         }
  1039.         return $result;
  1040.     }
  1041.     /**
  1042.      * Get fingerprint of cart items corresponding to specific product id
  1043.      *
  1044.      * @param integer $productId
  1045.      *
  1046.      * @return string
  1047.      */
  1048.     public function getItemsFingerprintByProductId($productId)
  1049.     {
  1050.         $result false;
  1051.         if (!$this->isEmpty()) {
  1052.             $items = [];
  1053.             foreach ($this->getItems() as $item) {
  1054.                 if ($item->getObject() && $item->getObject()->getId() == $productId) {
  1055.                     $items[] = [
  1056.                         $item->getItemId(),
  1057.                         $item->getKey(),
  1058.                         $item->getAmount(),
  1059.                     ];
  1060.                 }
  1061.             }
  1062.             $result md5(serialize($items));
  1063.         }
  1064.         return $result;
  1065.     }
  1066.     /**
  1067.      * Generate a string representation of the order
  1068.      * to send to a payment service
  1069.      *
  1070.      * @return string
  1071.      */
  1072.     public function getDescription()
  1073.     {
  1074.         $result = [];
  1075.         foreach ($this->getItems() as $item) {
  1076.             $result[] = $item->getDescription();
  1077.         }
  1078.         return implode("\n"$result);
  1079.     }
  1080.     /**
  1081.      * Get order fingerprint for event subsystem
  1082.      *
  1083.      * @param array $exclude Exclude kes OPTIONAL
  1084.      *
  1085.      * @return array
  1086.      */
  1087.     public function getEventFingerprint(array $exclude = [])
  1088.     {
  1089.         $keys array_diff($this->defineFingerprintKeys(), $exclude);
  1090.         $hash = [];
  1091.         foreach ($keys as $key) {
  1092.             $method 'getFingerprintBy' ucfirst($key);
  1093.             $hash[$key] = $this->$method();
  1094.         }
  1095.         return $hash;
  1096.     }
  1097.     /**
  1098.      * Returns delivery source address
  1099.      *
  1100.      * @return \XLite\Model\Address
  1101.      */
  1102.     public function getSourceAddress()
  1103.     {
  1104.         if ($this->sourceAddress === null) {
  1105.             $address = new \XLite\Model\Address();
  1106.             $config $this->getSourceAddressConfiguration();
  1107.             $address->setAddress1($config->origin_address1);
  1108.             $address->setAddress2($config->origin_address2);
  1109.             $address->setAddress3($config->origin_address3);
  1110.             $address->setCity($config->origin_city);
  1111.             $address->setCountryCode($config->origin_country);
  1112.             $address->setName($config->company_name);
  1113.             if ($config->origin_state) {
  1114.                 $address->setStateId($config->origin_state);
  1115.             }
  1116.             if ($config->origin_custom_state) {
  1117.                 $address->setCustomState($config->origin_custom_state);
  1118.             }
  1119.             $address->setZipcode($config->origin_zipcode);
  1120.             $address->setPhone($config->company_phone);
  1121.             $this->sourceAddress $address;
  1122.         }
  1123.         return $this->sourceAddress;
  1124.     }
  1125.     /**
  1126.      * Returns company configuration for getSourceAddress()
  1127.      *
  1128.      * @return \XLite\Core\ConfigCell
  1129.      */
  1130.     protected function getSourceAddressConfiguration()
  1131.     {
  1132.         return \XLite\Core\Config::getInstance()->Company;
  1133.     }
  1134.     /**
  1135.      * Returns company configuration
  1136.      *
  1137.      * @return \XLite\Core\ConfigCell
  1138.      */
  1139.     public function getCompanyConfiguration()
  1140.     {
  1141.         return \XLite\Core\Config::getInstance()->Company;
  1142.     }
  1143.     /**
  1144.      * Define fingerprint keys
  1145.      *
  1146.      * @return array
  1147.      */
  1148.     protected function defineFingerprintKeys()
  1149.     {
  1150.         return [
  1151.             'items',
  1152.             'itemsCount',
  1153.             'shippingTotal',
  1154.             'total',
  1155.             'shippingMethodId',
  1156.             'freeShippingNote',
  1157.             'paymentMethodId',
  1158.             'shippingMethodsHash',
  1159.             'paymentMethodsHash',
  1160.             'shippingAddressId',
  1161.             'billingAddressId',
  1162.             'shippingAddressFields',
  1163.             'billingAddressFields',
  1164.             'sameAddress',
  1165.         ];
  1166.     }
  1167.     /**
  1168.      * Get fingerprint by 'items' key
  1169.      *
  1170.      * @return array
  1171.      */
  1172.     protected function getFingerprintByItems()
  1173.     {
  1174.         $list = [];
  1175.         foreach ($this->getItems() as $item) {
  1176.             $event $item->getEventCell();
  1177.             // float is intended here to adopt fractional quantities via modules
  1178.             $event['quantity'] = (float) $item->getAmount();
  1179.             if ($this instanceof \XLite\Model\Cart && $this->isItemLimitReached($item)) {
  1180.                 $event['is_limit'] = 1;
  1181.             }
  1182.             $list[] = $event;
  1183.         }
  1184.         return $list;
  1185.     }
  1186.     /**
  1187.      * Return true if item amount limit is reached
  1188.      *
  1189.      * @param \XLite\Model\OrderItem $item Order item
  1190.      *
  1191.      * @return boolean
  1192.      */
  1193.     protected function isItemLimitReached($item)
  1194.     {
  1195.         $result false;
  1196.         $object $item->getObject();
  1197.         if ($object) {
  1198.             $result $object->getInventoryEnabled() && $object->getPublicAmount() <= $item->getAmount();
  1199.         }
  1200.         return $result;
  1201.     }
  1202.     /**
  1203.      * Get fingerprint by 'itemsCount' key
  1204.      *
  1205.      * @return int
  1206.      */
  1207.     protected function getFingerprintByItemsCount()
  1208.     {
  1209.         return (int) $this->countQuantity();
  1210.     }
  1211.     /**
  1212.      * Get fingerprint by 'shippingTotal' key
  1213.      *
  1214.      * @return float
  1215.      */
  1216.     protected function getFingerprintByShippingTotal()
  1217.     {
  1218.         /** @var \XLite\Logic\Order\Modifier\Shipping $shippingModifier */
  1219.         $shippingModifier $this->getModifier(\XLite\Model\Base\Surcharge::TYPE_SHIPPING'SHIPPING');
  1220.         if (!$shippingModifier) {
  1221.             return 0;
  1222.         }
  1223.         $rate $shippingModifier->getSelectedRate();
  1224.         return $rate ? (float)$rate->getTotalRate() : 0;
  1225.     }
  1226.     /**
  1227.      * Get fingerprint by 'total' key
  1228.      *
  1229.      * @return float
  1230.      */
  1231.     protected function getFingerprintByTotal()
  1232.     {
  1233.         return (float) $this->getTotal();
  1234.     }
  1235.     /**
  1236.      * Get fingerprint by 'freeShippingNote' key
  1237.      *
  1238.      * @return bool
  1239.      */
  1240.     protected function getFingerPrintByFreeShippingNote()
  1241.     {
  1242.         return $this->showFreeShippingNote();
  1243.     }
  1244.     /**
  1245.      * Get fingerprint by 'shippingMethodId' key
  1246.      *
  1247.      * @return integer
  1248.      */
  1249.     protected function getFingerprintByShippingMethodId()
  1250.     {
  1251.         /** @var \XLite\Logic\Order\Modifier\Shipping $shippingModifier */
  1252.         $shippingModifier $this->getModifier(\XLite\Model\Base\Surcharge::TYPE_SHIPPING'SHIPPING');
  1253.         if (!$shippingModifier) {
  1254.             return 0;
  1255.         }
  1256.         $rate $shippingModifier->getSelectedRate();
  1257.         return $rate $rate->getMethod()->getMethodId() : 0;
  1258.     }
  1259.     /**
  1260.      * Get fingerprint by 'paymentMethodId' key
  1261.      *
  1262.      * @return integer
  1263.      */
  1264.     protected function getFingerprintByPaymentMethodId()
  1265.     {
  1266.         return $this->getPaymentMethod()
  1267.                 ? $this->getPaymentMethod()->getMethodId()
  1268.                 : 0;
  1269.     }
  1270.     /**
  1271.      * Get fingerprint by 'shippingMethodsHash' key
  1272.      *
  1273.      * @return string
  1274.      */
  1275.     protected function getFingerprintByShippingMethodsHash()
  1276.     {
  1277.         /** @var \XLite\Logic\Order\Modifier\Shipping $shippingModifier */
  1278.         $shippingModifier $this->getModifier(\XLite\Model\Base\Surcharge::TYPE_SHIPPING'SHIPPING');
  1279.         $shippingMethodsHash = [];
  1280.         if ($shippingModifier) {
  1281.             $rates $shippingModifier->getRates();
  1282.             foreach ($rates as $rate) {
  1283.                 $methodId $rate->getMethod()->getMethodId();
  1284.                 $totalRate $rate->getTotalRate();
  1285.                 $shippingMethodsHash[] = "$methodId:$totalRate";
  1286.             }
  1287.         }
  1288.         return implode(';'$shippingMethodsHash);
  1289.     }
  1290.     /**
  1291.      * Get fingerprint by 'paymentMethodsHash' key
  1292.      *
  1293.      * @return string
  1294.      */
  1295.     protected function getFingerprintByPaymentMethodsHash()
  1296.     {
  1297.         $paymentMethodsHash = [];
  1298.         foreach ($this->getPaymentMethods() as $method) {
  1299.             $paymentMethodsHash[] = $method->getMethodId();
  1300.         }
  1301.         return implode(';'$paymentMethodsHash);
  1302.     }
  1303.     /**
  1304.      * Get fingerprint by 'shippingAddressId' key
  1305.      *
  1306.      * @return integer
  1307.      */
  1308.     protected function getFingerprintByShippingAddressId()
  1309.     {
  1310.         return $this->getProfile() && $this->getProfile()->getShippingAddress()
  1311.             ? $this->getProfile()->getShippingAddress()->getAddressId()
  1312.             : 0;
  1313.     }
  1314.     /**
  1315.      * Get fingerprint by 'billingAddressId' key
  1316.      *
  1317.      * @return integer
  1318.      */
  1319.     protected function getFingerprintByBillingAddressId()
  1320.     {
  1321.         return $this->getProfile() && $this->getProfile()->getBillingAddress()
  1322.             ? $this->getProfile()->getBillingAddress()->getAddressId()
  1323.             : 0;
  1324.     }
  1325.     /**
  1326.      * @return array
  1327.      */
  1328.     protected function getFingerprintByShippingAddressFields()
  1329.     {
  1330.         if ($this->getProfile() && $this->getProfile()->getShippingAddress()) {
  1331.             return $this->getProfile()->getShippingAddress()->serialize();
  1332.         }
  1333.         return [];
  1334.     }
  1335.     /**
  1336.      * @return array
  1337.      */
  1338.     protected function getFingerprintByBillingAddressFields()
  1339.     {
  1340.         if ($this->getProfile() && $this->getProfile()->getBillingAddress()) {
  1341.             return $this->getProfile()->getBillingAddress()->serialize();
  1342.         }
  1343.         return [];
  1344.     }
  1345.     /**
  1346.      * Get fingerprint by 'sameAddress' key
  1347.      *
  1348.      * @return boolean
  1349.      */
  1350.     protected function getFingerprintBySameAddress()
  1351.     {
  1352.         return $this->getProfile() && $this->getProfile()->isSameAddress();
  1353.     }
  1354.     /**
  1355.      * Get detail
  1356.      *
  1357.      * @param string $name Details cell name
  1358.      *
  1359.      * @return mixed
  1360.      */
  1361.     public function getDetail($name)
  1362.     {
  1363.         $details $this->getDetails();
  1364.         return \Includes\Utils\ArrayManager::findValue(
  1365.             $details,
  1366.             [$this'checkDetailName'],
  1367.             $name
  1368.         );
  1369.     }
  1370.     /**
  1371.      * Get detail value
  1372.      *
  1373.      * @param string $name Details cell name
  1374.      *
  1375.      * @return mixed
  1376.      */
  1377.     public function getDetailValue($name)
  1378.     {
  1379.         $detail $this->getDetail($name);
  1380.         return $detail $detail->getValue() : null;
  1381.     }
  1382.     /**
  1383.      * Set detail cell
  1384.      * To unset detail cell the value should be null
  1385.      *
  1386.      * @param string $name  Cell code
  1387.      * @param mixed  $value Cell value
  1388.      * @param string $label Cell label OPTIONAL
  1389.      *
  1390.      * @return void
  1391.      */
  1392.     public function setDetail($name$value$label null)
  1393.     {
  1394.         $detail $this->getDetail($name);
  1395.         if ($value === null) {
  1396.             if ($detail) {
  1397.                 $this->getDetails()->removeElement($detail);
  1398.                 \XLite\Core\Database::getEM()->remove($detail);
  1399.             }
  1400.         } else {
  1401.             if (!$detail) {
  1402.                 $detail = new \XLite\Model\OrderDetail();
  1403.                 $detail->setOrder($this);
  1404.                 $this->addDetails($detail);
  1405.                 $detail->setName($name);
  1406.             }
  1407.             $detail->setValue($value);
  1408.             $detail->setLabel($label);
  1409.         }
  1410.     }
  1411.     /**
  1412.      * Get meaning order details
  1413.      *
  1414.      * @return array
  1415.      */
  1416.     public function getMeaningDetails()
  1417.     {
  1418.         $result = [];
  1419.         foreach ($this->getDetails() as $detail) {
  1420.             if ($detail->getLabel()) {
  1421.                 $result[] = $detail;
  1422.             }
  1423.         }
  1424.         return $result;
  1425.     }
  1426.     /**
  1427.      * Called when an order successfully placed by a client
  1428.      *
  1429.      * @throws \Doctrine\ORM\NoResultException
  1430.      * @throws \Doctrine\ORM\NonUniqueResultException
  1431.      */
  1432.     public function processSucceed()
  1433.     {
  1434.         $this->markAsOrder();
  1435.         if ($this->canChangeStatusOnSucceed()) {
  1436.             $this->setShippingStatus(\XLite\Model\Order\Status\Shipping::STATUS_NEW);
  1437.         }
  1438.         $property \XLite\Core\Database::getRepo('XLite\Model\Order\Status\Property')->findOneBy([
  1439.             'paymentStatus'  => $this->getPaymentStatus(),
  1440.             'shippingStatus' => $this->getShippingStatus(),
  1441.         ]);
  1442.         $incStock $property $property->getIncStock() : null;
  1443.         if ($incStock === false) {
  1444.             $this->processBackorderedItems();
  1445.             $this->decreaseInventory();
  1446.             $this->setStockStatus('reduced');
  1447.         }
  1448.         // Register 'Place order' event in the order history
  1449.         $this->registerPlaceOrder();
  1450.         // Transform attributes
  1451.         $this->transformItemsAttributes();
  1452.         $this->sendOrderCreatedIfNeeded();
  1453.         $this->sendOrderWaitingForApproveIfNeeded();
  1454.     }
  1455.     /**
  1456.      * @return int
  1457.      * @throws \Doctrine\ORM\NoResultException
  1458.      * @throws \Doctrine\ORM\NonUniqueResultException
  1459.      */
  1460.     protected function processBackorderedItems()
  1461.     {
  1462.         $backstockCount 0;
  1463.         $items $this->getItems();
  1464.         foreach ($items as $item) {
  1465.             if ($item->isDeleted() || !$this->isInventoryEnabledForItem($item)) {
  1466.                 continue;
  1467.             }
  1468.             \XLite\Core\Database::getEM()->transactional(function (EntityManager $em) use ($item, &$backstockCount) {
  1469.                 $product $item->getProduct();
  1470.                 $em->lock($item->getProduct(), LockMode::PESSIMISTIC_READ);
  1471.                 $em->refresh($product);
  1472.                 $pa $this->getProductAmountForItem($item);
  1473.                 $this->reduceProductAmountForItem($item);
  1474.                 $ia $item->getAmount();
  1475.                 if ($pa $ia) {
  1476.                     $backstockCount += $ia $pa;
  1477.                     $item->setBackorderedAmount($ia $pa);
  1478.                 }
  1479.             });
  1480.         }
  1481.         if ($backstockCount) {
  1482.             \XLite\Core\Mailer::sendBackorderCreatedAdmin($this);
  1483.             $this->setShippingStatus(\XLite\Model\Order\Status\Shipping::STATUS_NEW_BACKORDERED);
  1484.             //Set oldShippingStatus to NBA
  1485.             $this->setShippingStatus(\XLite\Model\Order\Status\Shipping::STATUS_NEW_BACKORDERED);
  1486.             $this->assignBackorderCompetitors();
  1487.         }
  1488.         return $backstockCount;
  1489.     }
  1490.     /**
  1491.      * @throws \Doctrine\ORM\NoResultException
  1492.      * @throws \Doctrine\ORM\NonUniqueResultException
  1493.      */
  1494.     protected function assignBackorderCompetitors()
  1495.     {
  1496.         $repo Database::getRepo('XLite\Model\Order');
  1497.         $competitors $this->getBackorderCompetitors();
  1498.         foreach ($this->getItems() as $item) {
  1499.             if (
  1500.                 $item->getBackorderedAmount()
  1501.                 && $competitor $repo->getBackorderCompetitorByItem($item)
  1502.             ) {
  1503.                 if (!$competitors->contains($competitor)) {
  1504.                     $this->getBackorderCompetitors()->add($competitor);
  1505.                 }
  1506.             }
  1507.         }
  1508.     }
  1509.     /**
  1510.      * @return bool
  1511.      */
  1512.     public function isBackordered()
  1513.     {
  1514.         return $this->getShippingStatusCode() === \XLite\Model\Order\Status\Shipping::STATUS_NEW_BACKORDERED;
  1515.     }
  1516.     /**
  1517.      * @param OrderItem $item
  1518.      *
  1519.      * @return int
  1520.      */
  1521.     protected function getProductAmountForItem(OrderItem $item)
  1522.     {
  1523.         $key $this->getProductKeyForItem($item);
  1524.         if (!isset($this->backorderProductAmounts[$key])) {
  1525.             $this->backorderProductAmounts[$key] = $this->calculateProductAmountForItem($item);
  1526.         }
  1527.         return $this->backorderProductAmounts[$key];
  1528.     }
  1529.     /**
  1530.      * @param OrderItem $item
  1531.      *
  1532.      * @return int
  1533.      */
  1534.     protected function calculateProductAmountForItem(OrderItem $item)
  1535.     {
  1536.         return $item->getProduct()->getAmount();
  1537.     }
  1538.     /**
  1539.      * @param OrderItem $item
  1540.      *
  1541.      * @return static
  1542.      */
  1543.     protected function reduceProductAmountForItem(OrderItem $item)
  1544.     {
  1545.         $this->backorderProductAmounts[$this->getProductKeyForItem($item)]
  1546.             = max($this->getProductAmountForItem($item) - $item->getAmount(), 0);
  1547.         return $this;
  1548.     }
  1549.     /**
  1550.      * @param OrderItem $item
  1551.      *
  1552.      * @return boolean
  1553.      */
  1554.     protected function isInventoryEnabledForItem(OrderItem $item)
  1555.     {
  1556.         return $item->getProduct()->getInventoryEnabled();
  1557.     }
  1558.     /**
  1559.      * @param OrderItem $item
  1560.      *
  1561.      * @return string
  1562.      */
  1563.     protected function getProductKeyForItem(OrderItem $item)
  1564.     {
  1565.         return $item->getProduct()->getId();
  1566.     }
  1567.     protected function registerPlaceOrder()
  1568.     {
  1569.         \XLite\Core\OrderHistory::getInstance()->registerPlaceOrder($this->getOrderId());
  1570.     }
  1571.     /**
  1572.      * Send order created notification if needed
  1573.      */
  1574.     public function sendOrderCreatedIfNeeded()
  1575.     {
  1576.         if (
  1577.             $this->getPaymentStatus()
  1578.             && in_array(
  1579.                 $this->getPaymentStatus()->getCode(),
  1580.                 $this->getValidStatusesForCreateNotification(),
  1581.                 true
  1582.             )
  1583.         ) {
  1584.             \XLite\Core\Mailer::sendOrderCreated($this);
  1585.         }
  1586.     }
  1587.     /**
  1588.      * Send order awaiting approval notification if needed
  1589.      */
  1590.     public function sendOrderWaitingForApproveIfNeeded()
  1591.     {
  1592.         if (
  1593.             $this->getShippingStatus()
  1594.             && $this->getShippingStatus()->getCode() === \XLite\Model\Order\Status\Shipping::STATUS_WAITING_FOR_APPROVE
  1595.         ) {
  1596.             \XLite\Core\Mailer::sendOrderWaitingForApprove($this);
  1597.         }
  1598.     }
  1599.     /**
  1600.      * Valid not paid statuses
  1601.      *
  1602.      * @return array
  1603.      */
  1604.     public function getValidStatusesForCreateNotification()
  1605.     {
  1606.         return [
  1607.             \XLite\Model\Order\Status\Payment::STATUS_QUEUED,
  1608.             \XLite\Model\Order\Status\Payment::STATUS_AUTHORIZED,
  1609.         ];
  1610.     }
  1611.     /**
  1612.      * Mark cart as order
  1613.      *
  1614.      * @return void
  1615.      */
  1616.     public function markAsOrder()
  1617.     {
  1618.     }
  1619.     /**
  1620.      * Mark order as cart
  1621.      *
  1622.      * @return void
  1623.      */
  1624.     public function markAsCart()
  1625.     {
  1626.         $this->getRepository()->markAsCart($this->getOrderId());
  1627.     }
  1628.     /**
  1629.      * Refresh order items
  1630.      * TODO - rework after tax subsystem rework
  1631.      *
  1632.      * @return void
  1633.      */
  1634.     public function refreshItems()
  1635.     {
  1636.     }
  1637.     /**
  1638.      * Return removing status
  1639.      *
  1640.      * @return boolean
  1641.      */
  1642.     public function isRemoving()
  1643.     {
  1644.         return $this->isRemoving;
  1645.     }
  1646.     /**
  1647.      * Constructor
  1648.      *
  1649.      * @param array $data Entity properties OPTIONAL
  1650.      */
  1651.     public function __construct(array $data = [])
  1652.     {
  1653.         $this->details              = new ArrayCollection();
  1654.         $this->items                = new ArrayCollection();
  1655.         $this->surcharges           = new ArrayCollection();
  1656.         $this->payment_transactions = new ArrayCollection();
  1657.         $this->events               = new ArrayCollection();
  1658.         $this->trackingNumbers      = new ArrayCollection();
  1659.         $this->backorderCompetitors = new ArrayCollection();
  1660.         parent::__construct($data);
  1661.     }
  1662.     /**
  1663.      * Return list of available payment methods
  1664.      *
  1665.      * @return array
  1666.      */
  1667.     public function getPaymentMethods()
  1668.     {
  1669.         if (>= $this->getOpenTotal()) {
  1670.             return [];
  1671.         }
  1672.         $list \XLite\Core\Database::getRepo('XLite\Model\Payment\Method')
  1673.             ->findAllActive();
  1674.         foreach ($list as $i => $method) {
  1675.             if (!$method->isEnabled() || !$method->getProcessor()->isApplicable($this$method)) {
  1676.                 unset($list[$i]);
  1677.             }
  1678.         }
  1679.         if (\XLite\Core\Auth::getInstance()->isOperatingAsUserMode()) {
  1680.             $fakeMethods \XLite\Core\Auth::getInstance()->getOperateAsUserPaymentMethods();
  1681.             $filteredFakeMethods array_filter(
  1682.                 $fakeMethods,
  1683.                 static function ($fakeMethod) use ($list) {
  1684.                     $fakeServiceName $fakeMethod->getServiceName();
  1685.                     // Check if $list already contains fake method
  1686.                     return !array_reduce($list, static function ($carry$method) use ($fakeServiceName) {
  1687.                         return $carry ?: $method->getServiceName() === $fakeServiceName;
  1688.                     }, false);
  1689.                 }
  1690.             );
  1691.             $list array_merge(
  1692.                 $list,
  1693.                 $filteredFakeMethods
  1694.             );
  1695.         }
  1696.         return $list;
  1697.     }
  1698.     /**
  1699.      * Renew payment method
  1700.      *
  1701.      * @return void
  1702.      */
  1703.     public function renewPaymentMethod()
  1704.     {
  1705.         if ($this->isPaymentMethodRequired()) {
  1706.             $method $this->getPaymentMethod();
  1707.             if ($this->isPaymentMethodIsApplicable($method)) {
  1708.                 $this->setPaymentMethod($method);
  1709.             } else {
  1710.                 $first $this->getFirstPaymentMethod();
  1711.                 if ($first) {
  1712.                     if ($this->getProfile()) {
  1713.                         $this->getProfile()->setLastPaymentId($first->getMethodId());
  1714.                     }
  1715.                     $this->setPaymentMethod($first);
  1716.                 } else {
  1717.                     $this->unsetPaymentMethod();
  1718.                 }
  1719.             }
  1720.         } else {
  1721.             $this->unsetPaymentMethod();
  1722.         }
  1723.     }
  1724.     /**
  1725.      * Returns true if order is allowed to change status on succeed
  1726.      *
  1727.      * @return boolean
  1728.      */
  1729.     protected function canChangeStatusOnSucceed()
  1730.     {
  1731.         return $this->getShippingStatus() === null;
  1732.     }
  1733.     /**
  1734.      * Return true if payment method is required for the order
  1735.      *
  1736.      * @return boolean
  1737.      */
  1738.     protected function isPaymentMethodRequired()
  1739.     {
  1740.         return $this->getOpenTotal()
  1741.             && ($this instanceof \XLite\Model\Cart || $this->getOrderNumber() === null);
  1742.     }
  1743.     /**
  1744.      * Check if given payment method can be applied to the order
  1745.      *
  1746.      * @param  \XLite\Model\Payment\Method  $method
  1747.      * @return boolean
  1748.      */
  1749.     protected function isPaymentMethodIsApplicable($method)
  1750.     {
  1751.         return $method
  1752.             && $method->getProcessor()
  1753.             && $method->getProcessor()->isApplicable($this$method);
  1754.     }
  1755.     /**
  1756.      * Get payment method
  1757.      *
  1758.      * @return \XLite\Model\Payment\Method|null
  1759.      */
  1760.     public function getPaymentMethod()
  1761.     {
  1762.         $transaction $this->getFirstOpenPaymentTransaction();
  1763.         if (!$transaction) {
  1764.             $transaction $this->hasUnpaidTotal() || count($this->getPaymentTransactions()) === 0
  1765.                 $this->assignLastPaymentMethod()
  1766.                 : $this->getPaymentTransactions()->last();
  1767.         }
  1768.         return $transaction $transaction->getPaymentMethod() : null;
  1769.     }
  1770.     /**
  1771.      * Check item key equal
  1772.      *
  1773.      * @param \XLite\Model\OrderItem $item Item
  1774.      * @param string                 $key  Key
  1775.      *
  1776.      * @return boolean
  1777.      */
  1778.     public function checkItemKeyEqual(\XLite\Model\OrderItem $item$key)
  1779.     {
  1780.         return $item->getKey() == $key;
  1781.     }
  1782.     /**
  1783.      * Check item id equal
  1784.      *
  1785.      * @param \XLite\Model\OrderItem $item   Item
  1786.      * @param integer                $itemId Item id
  1787.      *
  1788.      * @return boolean
  1789.      */
  1790.     public function checkItemIdEqual(\XLite\Model\OrderItem $item$itemId)
  1791.     {
  1792.         return $item->getItemId() == $itemId;
  1793.     }
  1794.     /**
  1795.      * Check order detail name
  1796.      *
  1797.      * @param \XLite\Model\OrderDetail $detail Detail
  1798.      * @param string                   $name   Name
  1799.      *
  1800.      * @return boolean
  1801.      */
  1802.     public function checkDetailName(\XLite\Model\OrderDetail $detail$name)
  1803.     {
  1804.         return $detail->getName() === $name;
  1805.     }
  1806.     /**
  1807.      * Check payment transaction status
  1808.      *
  1809.      * @param \XLite\Model\Payment\Transaction $transaction Transaction
  1810.      * @param mixed                            $status      Status
  1811.      *
  1812.      * @return boolean
  1813.      */
  1814.     public function checkPaymentTransactionStatusEqual(\XLite\Model\Payment\Transaction $transaction$status)
  1815.     {
  1816.         return is_array($status)
  1817.             ? in_array($transaction->getStatus(), $statustrue)
  1818.             : $transaction->getStatus() === $status;
  1819.     }
  1820.     /**
  1821.      * Check payment transaction - open or not
  1822.      *
  1823.      * @param \XLite\Model\Payment\Transaction $transaction Payment transaction
  1824.      *
  1825.      * @return boolean
  1826.      */
  1827.     public function checkPaymentTransactionOpen(\XLite\Model\Payment\Transaction $transaction)
  1828.     {
  1829.         return $transaction->isOpen() || $transaction->isInProgress();
  1830.     }
  1831.     /**
  1832.      * Check - is item product id equal specified product id
  1833.      *
  1834.      * @param \XLite\Model\OrderItem $item      Item
  1835.      * @param integer                $productId Product id
  1836.      *
  1837.      * @return boolean
  1838.      */
  1839.     public function isItemProductIdEqual(\XLite\Model\OrderItem $item$productId)
  1840.     {
  1841.         return $item->getProductId() == $productId;
  1842.     }
  1843.     /**
  1844.      * Check last payment method
  1845.      *
  1846.      * @param \XLite\Model\Payment\Method $pmethod       Payment method
  1847.      * @param integer                     $lastPaymentId Last selected payment method id
  1848.      *
  1849.      * @return boolean
  1850.      */
  1851.     public function checkLastPaymentMethod(\XLite\Model\Payment\Method $pmethod$lastPaymentId)
  1852.     {
  1853.         $result $pmethod->getMethodId() == $lastPaymentId;
  1854.         if ($result) {
  1855.             $this->setPaymentMethod($pmethod);
  1856.             \XLite\Core\Database::getEM()->flush();
  1857.         }
  1858.         return $result;
  1859.     }
  1860.     // {{{ Payment method and transactions
  1861.     /**
  1862.      * Set payment method
  1863.      *
  1864.      * @param \XLite\Model\Payment\Method|null $paymentMethod Payment method
  1865.      * @param float                            $value         Payment transaction value OPTIONAL
  1866.      *
  1867.      * @return void
  1868.      */
  1869.     public function setPaymentMethod($paymentMethod$value null)
  1870.     {
  1871.         if ($paymentMethod && !($paymentMethod instanceof \XLite\Model\Payment\Method)) {
  1872.             $paymentMethod null;
  1873.         }
  1874.         $sameMethod false;
  1875.         if ($paymentMethod === null || $this->getFirstOpenPaymentTransaction()) {
  1876.             $transaction $this->getFirstOpenPaymentTransaction();
  1877.             if ($transaction) {
  1878.                 if ($paymentMethod === null) {
  1879.                     $this->unsetPaymentMethod();
  1880.                 } elseif ($transaction->isSameMethod($paymentMethod)) {
  1881.                     $transaction->updateValue($this);
  1882.                     $sameMethod true;
  1883.                 }
  1884.             }
  1885.         }
  1886.         if ($paymentMethod === null) {
  1887.             $this->setPaymentMethodName('');
  1888.         } elseif (!$sameMethod) {
  1889.             $this->unsetPaymentMethod();
  1890.             $this->addPaymentTransaction($paymentMethod$value);
  1891.             $this->setPaymentMethodName($paymentMethod->getName());
  1892.         }
  1893.     }
  1894.     /**
  1895.      * Unset payment method
  1896.      *
  1897.      * @throws \Doctrine\ORM\ORMInvalidArgumentException
  1898.      *
  1899.      * @return void
  1900.      */
  1901.     public function unsetPaymentMethod()
  1902.     {
  1903.         $transaction $this->getFirstOpenPaymentTransaction();
  1904.         $this->setPaymentMethodName('');
  1905.         if ($transaction) {
  1906.             $this->payment_transactions->removeElement($transaction);
  1907.             \XLite\Core\Database::getEM()->remove($transaction);
  1908.         }
  1909.     }
  1910.     /**
  1911.      * Get active payment transactions
  1912.      *
  1913.      * @return array
  1914.      */
  1915.     public function getActivePaymentTransactions()
  1916.     {
  1917.         $result = [];
  1918.         foreach ($this->getPaymentTransactions() as $t) {
  1919.             if ($t->isCompleted() || $t->isPending()) {
  1920.                 $result[] = $t;
  1921.             }
  1922.         }
  1923.         return $result;
  1924.     }
  1925.     /**
  1926.      * Get visible payment methods
  1927.      *
  1928.      * @return array
  1929.      */
  1930.     public function getVisiblePaymentMethods()
  1931.     {
  1932.         $result = [];
  1933.         foreach ($this->getActivePaymentTransactions() as $t) {
  1934.             if ($t->getPaymentMethod()) {
  1935.                 $result[] = $t->getPaymentMethod();
  1936.             }
  1937.         }
  1938.         if (count($result) === && count($this->getPaymentTransactions())) {
  1939.             $method $this->getPaymentTransactions()->last()->getPaymentMethod();
  1940.             if ($method) {
  1941.                 $result[] = $method;
  1942.             }
  1943.         }
  1944.         return $result;
  1945.     }
  1946.     /**
  1947.      * Get first open (not payed) payment transaction
  1948.      *
  1949.      * @return \XLite\Model\Payment\Transaction|null
  1950.      */
  1951.     public function getFirstOpenPaymentTransaction()
  1952.     {
  1953.         $transactions $this->getPaymentTransactions();
  1954.         return \Includes\Utils\ArrayManager::findValue(
  1955.             $transactions,
  1956.             [$this'checkPaymentTransactionOpen']
  1957.         );
  1958.     }
  1959.     /**
  1960.      * Get open (not-payed) total
  1961.      *
  1962.      * @return float
  1963.      */
  1964.     public function getOpenTotal()
  1965.     {
  1966.         $total $this->getCurrency()->roundValue($this->getTotal());
  1967.         $totalAsSurcharges $this->getCurrency()->roundValue($this->getSurchargesTotal());
  1968.         $totalsPaid $this->getPaidTotals();
  1969.         return max(
  1970.             $total $totalsPaid['total'],
  1971.             $totalAsSurcharges $totalsPaid['totalAsSurcharges']
  1972.         );
  1973.     }
  1974.     public function getPaidTotals()
  1975.     {
  1976.         $total $totalAsSurcharges 0;
  1977.         foreach ($this->getPaymentTransactions() as $t) {
  1978.             $total += $this->getCurrency()->roundValue($t->getChargeValueModifier());
  1979.             $totalAsSurcharges += $this->getCurrency()->roundValue($t->getChargeValueModifier());
  1980.         }
  1981.         return [
  1982.             'total'             => $total,
  1983.             'totalAsSurcharges' => $totalAsSurcharges,
  1984.         ];
  1985.     }
  1986.     /**
  1987.      * Get paid total
  1988.      *
  1989.      * @return float
  1990.      */
  1991.     public function getPaidTotal()
  1992.     {
  1993.         $totals $this->getPaidTotals();
  1994.         return max(
  1995.             $totals['total'],
  1996.             $totals['totalAsSurcharges']
  1997.         );
  1998.     }
  1999.     /**
  2000.      * Check - order is open (has initialized transactions and has open total) or not
  2001.      *
  2002.      * @return boolean
  2003.      */
  2004.     public function isOpen()
  2005.     {
  2006.         return $this->getFirstOpenPaymentTransaction() && $this->hasUnpaidTotal();
  2007.     }
  2008.     /**
  2009.      * Has unpaid total?
  2010.      *
  2011.      * @return boolean
  2012.      */
  2013.     public function hasUnpaidTotal()
  2014.     {
  2015.         return $this->getCurrency()->getMinimumValue() <= $this->getCurrency()->roundValue(abs($this->getOpenTotal()));
  2016.     }
  2017.     /**
  2018.      * Get totally payed total
  2019.      *
  2020.      * @return float
  2021.      */
  2022.     public function getPayedTotal()
  2023.     {
  2024.         $total $this->getCurrency()->roundValue($this->getTotal());
  2025.         foreach ($this->getPaymentTransactions() as $t) {
  2026.             if ($t->isCompleted() && ($t->isAuthorized() || $t->isCaptured())) {
  2027.                 $total -= $this->getCurrency()->roundValue($t->getChargeValueModifier());
  2028.             }
  2029.         }
  2030.         $sums $this->getRawPaymentTransactionSums();
  2031.         $total += $sums['refunded'];
  2032.         return $total;
  2033.     }
  2034.     /**
  2035.      * Check - order is payed or not
  2036.      * Payed - order has not open total and all payment transactions are failed or completed
  2037.      *
  2038.      * @return boolean
  2039.      */
  2040.     public function isPayed()
  2041.     {
  2042.         return >= $this->getPayedTotal();
  2043.     }
  2044.     /**
  2045.      * Check - order has in-progress payments or not
  2046.      *
  2047.      * @return boolean
  2048.      */
  2049.     public function hasInprogressPayments()
  2050.     {
  2051.         $result false;
  2052.         foreach ($this->getPaymentTransactions() as $t) {
  2053.             if ($t->isInProgress()) {
  2054.                 $result true;
  2055.                 break;
  2056.             }
  2057.         }
  2058.         return $result;
  2059.     }
  2060.     /**
  2061.      * Assign last used payment method
  2062.      *
  2063.      * @return \XLite\Model\Payment\Transaction|void
  2064.      */
  2065.     protected function assignLastPaymentMethod()
  2066.     {
  2067.         $found null;
  2068.         if ($this->isPaymentMethodRequired() && $this->getProfile() && $this->getProfile()->getLastPaymentId()) {
  2069.             $paymentMethods $this->getPaymentMethods();
  2070.             $found \Includes\Utils\ArrayManager::findValue(
  2071.                 $paymentMethods,
  2072.                 [$this'checkLastPaymentMethod'],
  2073.                 $this->getProfile()->getLastPaymentId()
  2074.             );
  2075.         }
  2076.         return $found $this->getFirstOpenPaymentTransaction() : null;
  2077.     }
  2078.     /**
  2079.      * Get first applicable payment method
  2080.      *
  2081.      * @return \XLite\Model\Payment\Method
  2082.      */
  2083.     protected function getFirstPaymentMethod()
  2084.     {
  2085.         $list $this->getPaymentMethods();
  2086.         return $list array_shift($list) : null;
  2087.     }
  2088.     /**
  2089.      * Add payment transaction
  2090.      * FIXME: move logic into \XLite\Model\Payment\Transaction
  2091.      *
  2092.      * @param \XLite\Model\Payment\Method $method Payment method
  2093.      * @param float                       $value  Value OPTIONAL
  2094.      *
  2095.      * @throws \Doctrine\ORM\ORMInvalidArgumentException
  2096.      *
  2097.      * @return void
  2098.      */
  2099.     protected function addPaymentTransaction(\XLite\Model\Payment\Method $method$value null)
  2100.     {
  2101.         if ($value === null || >= $value) {
  2102.             $value $this->getOpenTotal();
  2103.         } else {
  2104.             $value min($value$this->getOpenTotal());
  2105.         }
  2106.         // Do not add 0 or <0 transactions. This is for a "Payment not required" case.
  2107.         if ($value 0) {
  2108.             $transaction = new \XLite\Model\Payment\Transaction();
  2109.             $this->addPaymentTransactions($transaction);
  2110.             $transaction->setOrder($this);
  2111.             $transaction->setPaymentMethod($method);
  2112.             \XLite\Core\Database::getEM()->persist($method);
  2113.             $transaction->setCurrency($this->getCurrency());
  2114.             $transaction->setStatus($transaction::STATUS_INITIALIZED);
  2115.             $transaction->setValue($value);
  2116.             $transaction->setType($method->getProcessor()->getInitialTransactionType($method));
  2117.             if ($method->getProcessor()->isTestMode($method)) {
  2118.                 $transaction->setDataCell(
  2119.                     'test_mode',
  2120.                     true,
  2121.                     'Test mode'
  2122.                 );
  2123.             }
  2124.             \XLite\Core\Database::getEM()->persist($transaction);
  2125.         }
  2126.     }
  2127.     // }}}
  2128.     // {{{ Shipping
  2129.     /**
  2130.      * Returns last shipping method id
  2131.      *
  2132.      * @return integer|null
  2133.      */
  2134.     public function getLastShippingId()
  2135.     {
  2136.         /** @var \XLite\Model\Profile $profile */
  2137.         $profile $this->getProfile();
  2138.         return $profile $profile->getLastShippingId() : null;
  2139.     }
  2140.     /**
  2141.      * Sets last shipping method id used
  2142.      *
  2143.      * @param integer $value Method id
  2144.      *
  2145.      * @return void
  2146.      */
  2147.     public function setLastShippingId($value)
  2148.     {
  2149.         /** @var \XLite\Model\Profile $profile */
  2150.         $profile $this->getProfile();
  2151.         if ($profile !== null) {
  2152.             $profile->setLastShippingId($value);
  2153.         }
  2154.     }
  2155.     /**
  2156.      * Renew shipping method
  2157.      *
  2158.      * @return void
  2159.      */
  2160.     public function renewShippingMethod()
  2161.     {
  2162.         if (!\XLite\Model\Shipping::getInstance()->shouldAllowLongCalculations()) {
  2163.             return;
  2164.         }
  2165.         $this->updateEmptyShippingID();
  2166.         /** @var \XLite\Logic\Order\Modifier\Shipping $modifier */
  2167.         $modifier $this->getModifier(\XLite\Model\Base\Surcharge::TYPE_SHIPPING'SHIPPING');
  2168.         if ($modifier && $modifier->getSelectedRate() === null) {
  2169.             $method $this->getFirstShippingMethod();
  2170.             if ($method) {
  2171.                 $methodId $method->getMethodId();
  2172.                 $this->setLastShippingId($methodId);
  2173.                 $this->setShippingId($methodId);
  2174.             } else {
  2175.                 $this->setShippingId(0);
  2176.             }
  2177.         }
  2178.     }
  2179.     /**
  2180.      * Get the link for the detailed tracking information
  2181.      *
  2182.      * @return boolean|\XLite\Model\Shipping\Processor\AProcessor False if the shipping is not set or
  2183.      *                                                            shipping processor is absent
  2184.      */
  2185.     public function getShippingProcessor()
  2186.     {
  2187.         if ($this->shippingProcessor === null) {
  2188.             $shipping \XLite\Core\Database::getRepo('XLite\Model\Shipping\Method')->find($this->getShippingId());
  2189.             $this->shippingProcessor $shipping && $shipping->getProcessorObject()
  2190.                 ? $shipping->getProcessorObject()
  2191.                 : false;
  2192.         }
  2193.         return $this->shippingProcessor;
  2194.     }
  2195.     /**
  2196.      * Defines whether the form must be used for tracking information.
  2197.      * The 'getTrackingInformationURL' result will be used as tracking link instead
  2198.      *
  2199.      * @param string $trackingNumber Tracking number value
  2200.      *
  2201.      * @return boolean
  2202.      */
  2203.     public function isTrackingInformationForm($trackingNumber)
  2204.     {
  2205.         return $this->getShippingProcessor()
  2206.             ? $this->getShippingProcessor()->isTrackingInformationForm($trackingNumber)
  2207.             : null;
  2208.     }
  2209.     /**
  2210.      * Get the link for the detailed tracking information
  2211.      *
  2212.      * @param string $trackingNumber Tracking number value
  2213.      *
  2214.      * @return null|string
  2215.      */
  2216.     public function getTrackingInformationURL($trackingNumber)
  2217.     {
  2218.         return $this->getShippingProcessor()
  2219.             ? $this->getShippingProcessor()->getTrackingInformationURL($trackingNumber)
  2220.             : null;
  2221.     }
  2222.     /**
  2223.      * Get the form parameters for the detailed tracking information
  2224.      *
  2225.      * @param string $trackingNumber Tracking number value
  2226.      *
  2227.      * @return null|array
  2228.      */
  2229.     public function getTrackingInformationParams($trackingNumber)
  2230.     {
  2231.         return $this->getShippingProcessor()
  2232.             ? $this->getShippingProcessor()->getTrackingInformationParams($trackingNumber)
  2233.             : null;
  2234.     }
  2235.     /**
  2236.      * Get the form method for the detailed tracking information
  2237.      *
  2238.      * @param string $trackingNumber Tracking number value
  2239.      *
  2240.      * @return null|string
  2241.      */
  2242.     public function getTrackingInformationMethod($trackingNumber)
  2243.     {
  2244.         return $this->getShippingProcessor()
  2245.             ? $this->getShippingProcessor()->getTrackingInformationMethod($trackingNumber)
  2246.             : null;
  2247.     }
  2248.     /**
  2249.      * Get first shipping method
  2250.      *
  2251.      * @return \XLite\Model\Shipping\Method
  2252.      */
  2253.     protected function getFirstShippingMethod()
  2254.     {
  2255.         $method null;
  2256.         $modifier $this->getModifier(\XLite\Model\Base\Surcharge::TYPE_SHIPPING'SHIPPING');
  2257.         if ($modifier) {
  2258.             $rates $modifier->getRates();
  2259.             $rate array_shift($rates);
  2260.             if ($rate) {
  2261.                 $method $rate->getMethod();
  2262.             }
  2263.         }
  2264.         return $method;
  2265.     }
  2266.     // }}}
  2267.     // {{{ Mail notification
  2268.     /**
  2269.      * Set value of isNotificationSent flag and return old value
  2270.      *
  2271.      * @param boolean $value New value
  2272.      *
  2273.      * @return boolean
  2274.      */
  2275.     public function setIsNotificationSent($value)
  2276.     {
  2277.         $oldValue $this->isNotificationSent;
  2278.         $this->isNotificationSent $value;
  2279.         return $oldValue;
  2280.     }
  2281.     /**
  2282.      * Get value of isNotificationSent flag
  2283.      *
  2284.      * @return boolean
  2285.      */
  2286.     public function isNotificationSent()
  2287.     {
  2288.         return $this->isNotificationSent;
  2289.     }
  2290.     /**
  2291.      * Set value of isNotificationsAllowedFlag flag and return old value
  2292.      *
  2293.      * @param boolean $value New value
  2294.      *
  2295.      * @return boolean
  2296.      */
  2297.     public function setIsNotificationsAllowedFlag($value)
  2298.     {
  2299.         $oldValue $this->isNotificationsAllowedFlag;
  2300.         $this->isNotificationsAllowedFlag = (bool)$value;
  2301.         return $oldValue;
  2302.     }
  2303.     /**
  2304.      * Set value of ignoreCustomerNotifications flag and return old value
  2305.      *
  2306.      * @param boolean $value New value
  2307.      *
  2308.      * @return boolean
  2309.      */
  2310.     public function setIgnoreCustomerNotifications($value)
  2311.     {
  2312.         $oldValue $this->ignoreCustomerNotifications;
  2313.         $this->ignoreCustomerNotifications = (bool)$value;
  2314.         return $oldValue;
  2315.     }
  2316.     /**
  2317.      * Get value of isNotificationsAllowedFlag flag
  2318.      *
  2319.      * @return boolean
  2320.      */
  2321.     protected function isNotificationsAllowed()
  2322.     {
  2323.         return $this->isNotificationsAllowedFlag;
  2324.     }
  2325.     /**
  2326.      * Get value of ignoreCustomerNotifications flag
  2327.      *
  2328.      * @return boolean
  2329.      */
  2330.     protected function isIgnoreCustomerNotifications()
  2331.     {
  2332.         return $this->ignoreCustomerNotifications;
  2333.     }
  2334.     // }}}
  2335.     // {{{ Calculation
  2336.     /**
  2337.      * Get modifiers
  2338.      *
  2339.      * @return \XLite\DataSet\Collection\OrderModifier
  2340.      */
  2341.     public function getModifiers()
  2342.     {
  2343.         if ($this->modifiers === null) {
  2344.             $this->modifiers \XLite\Core\Database::getRepo('XLite\Model\Order\Modifier')->findActive();
  2345.             // Initialize
  2346.             foreach ($this->modifiers as $modifier) {
  2347.                 $modifier->initialize($this$this->modifiers);
  2348.             }
  2349.             // Preprocess modifiers
  2350.             foreach ($this->modifiers as $modifier) {
  2351.                 $modifier->preprocess();
  2352.             }
  2353.         }
  2354.         return $this->modifiers;
  2355.     }
  2356.     /**
  2357.      * Get modifier
  2358.      *
  2359.      * @param string $type Modifier type
  2360.      * @param string $code Modifier code
  2361.      *
  2362.      * @return \XLite\Model\Order\Modifier
  2363.      */
  2364.     public function getModifier($type$code)
  2365.     {
  2366.         $result null;
  2367.         foreach ($this->getModifiers() as $modifier) {
  2368.             if ($modifier->getType() === $type && $modifier->getCode() === $code) {
  2369.                 $result $modifier;
  2370.                 break;
  2371.             }
  2372.         }
  2373.         return $result;
  2374.     }
  2375.     /**
  2376.      * Check - modifier is exists or not (by type)
  2377.      *
  2378.      * @param string $type Type
  2379.      *
  2380.      * @return boolean
  2381.      */
  2382.     public function isModifierByType($type)
  2383.     {
  2384.         $result false;
  2385.         foreach ($this->getModifiers() as $modifier) {
  2386.             if ($modifier->getType() === $type) {
  2387.                 $result true;
  2388.                 break;
  2389.             }
  2390.         }
  2391.         return $result;
  2392.     }
  2393.     /**
  2394.      * Get modifiers by type
  2395.      *
  2396.      * @param string $type Modifier type
  2397.      *
  2398.      * @return array
  2399.      */
  2400.     public function getModifiersByType($type)
  2401.     {
  2402.         $list = [];
  2403.         foreach ($this->getModifiers() as $modifier) {
  2404.             if ($modifier->getType() === $type) {
  2405.                 $list[] = $modifier;
  2406.             }
  2407.         }
  2408.         return $list;
  2409.     }
  2410.     /**
  2411.      * Get items exclude surcharges info
  2412.      *
  2413.      * @return array
  2414.      */
  2415.     public function getItemsExcludeSurcharges()
  2416.     {
  2417.         $list = [];
  2418.         foreach ($this->getItems() as $item) {
  2419.             foreach ($item->getExcludeSurcharges() as $surcharge) {
  2420.                 if (!isset($list[$surcharge->getKey()])) {
  2421.                     $list[$surcharge->getKey()] = $surcharge->getName();
  2422.                 }
  2423.             }
  2424.         }
  2425.         return $list;
  2426.     }
  2427.     /**
  2428.      * Get items included surcharges totals
  2429.      *
  2430.      * @param boolean $forCartItems Flag: true - return values for cart items only, false - for cart totals and items
  2431.      *
  2432.      * @return array
  2433.      */
  2434.     public function getItemsIncludeSurchargesTotals($forCartItems false)
  2435.     {
  2436.         $list = [];
  2437.         if ($forCartItems) {
  2438.             // Add surcharges for cart items
  2439.             foreach ($this->getItems() as $item) {
  2440.                 foreach ($item->getIncludeSurcharges() as $surcharge) {
  2441.                     if (!isset($list[$surcharge->getKey()])) {
  2442.                         $list[$surcharge->getKey()] = [
  2443.                             'surcharge' => $surcharge,
  2444.                             'cost'      => 0,
  2445.                         ];
  2446.                     }
  2447.                     $list[$surcharge->getKey()]['cost'] += $surcharge->getValue();
  2448.                 }
  2449.             }
  2450.         } else {
  2451.             // Get global cart surcharges
  2452.             foreach ($this->getIncludeSurcharges() as $surcharge) {
  2453.                 if (!isset($list[$surcharge->getKey()])) {
  2454.                     $list[$surcharge->getKey()] = [
  2455.                         'surcharge' => $surcharge,
  2456.                         'cost'      => 0,
  2457.                     ];
  2458.                 }
  2459.                 $list[$surcharge->getKey()]['cost'] += $surcharge->getValue();
  2460.             }
  2461.         }
  2462.         return $list;
  2463.     }
  2464.     /**
  2465.      * Common method to update cart/order
  2466.      *
  2467.      * @return void
  2468.      */
  2469.     public function updateOrder()
  2470.     {
  2471.         $this->normalizeItems();
  2472.         $this->reinitializeCurrency();
  2473.         // If shipping method is not selected or shipping conditions is changed (remove order items or changed address)
  2474.         $this->renewShippingMethod();
  2475.         $this->calculateInitialValues();
  2476.         $this->calculate();
  2477.         $this->renewPaymentMethod();
  2478.     }
  2479.     /**
  2480.      * Calculate order
  2481.      *
  2482.      * @return void
  2483.      */
  2484.     public function calculate()
  2485.     {
  2486.         $oldSurcharges $this->resetSurcharges();
  2487.         $this->reinitializeCurrency();
  2488.         $this->calculateInitialValues();
  2489.         foreach ($this->getModifiers() as $modifier) {
  2490.             if ($modifier->canApply()) {
  2491.                 $modifier->calculate();
  2492.             }
  2493.         }
  2494.         $this->mergeSurcharges($oldSurcharges);
  2495.         $this->finalizeItemsCalculation();
  2496.         $this->setTotal(max($this->getSurchargesTotal(), 0));
  2497.     }
  2498.     /**
  2499.      * Recalculate edited order
  2500.      *
  2501.      * @return void
  2502.      */
  2503.     public function recalculate()
  2504.     {
  2505.         $this->reinitializeCurrency();
  2506.         $this->finalizeItemsCalculation();
  2507.         $this->setTotal($this->getSurchargesTotal());
  2508.     }
  2509.     /**
  2510.      * Renew order
  2511.      *
  2512.      * @return void
  2513.      */
  2514.     public function renew()
  2515.     {
  2516.         foreach ($this->getItems() as $item) {
  2517.             if (!$item->renew()) {
  2518.                 $this->getItems()->removeElement($item);
  2519.                 \XLite\Core\Database::getRepo('XLite\Model\OrderItem')->delete($item);
  2520.             }
  2521.         }
  2522.         $this->calculate();
  2523.     }
  2524.     /**
  2525.      * Soft renew
  2526.      *
  2527.      * @return void
  2528.      */
  2529.     public function renewSoft()
  2530.     {
  2531.         $this->reinitializeCurrency();
  2532.     }
  2533.     /**
  2534.      * Reinitialize currency
  2535.      *
  2536.      * @return void
  2537.      */
  2538.     protected function reinitializeCurrency()
  2539.     {
  2540.         $new $this->defineCurrency();
  2541.         $old $this->getCurrency();
  2542.         if (empty($old) || (!empty($new) && $old->getCode() !== $new->getCode())) {
  2543.             $this->setCurrency($new);
  2544.         }
  2545.     }
  2546.     /**
  2547.      * Define order currency
  2548.      *
  2549.      * @return \XLite\Model\Currency
  2550.      */
  2551.     protected function defineCurrency()
  2552.     {
  2553.         return \XLite::getInstance()->getCurrency();
  2554.     }
  2555.     /**
  2556.      * Reset surcharges list
  2557.      *
  2558.      * @return array
  2559.      */
  2560.     public function resetSurcharges()
  2561.     {
  2562.         $result = [
  2563.             'items' => [],
  2564.         ];
  2565.         $result['items'] = $this->resetItemsSurcharges($this->getItems()->toArray());
  2566.         $result['surcharges'] = parent::resetSurcharges();
  2567.         return $result;
  2568.     }
  2569.     /**
  2570.      * Reset order items surcharge
  2571.      *
  2572.      * @param \XLite\Model\OrderItem[] $items Order items
  2573.      *
  2574.      * @return array
  2575.      */
  2576.     protected function resetSelfSurcharges($items)
  2577.     {
  2578.         $result = [];
  2579.         foreach ($items as $item) {
  2580.             $result[$item->getItemId()] = $item->resetSurcharges();
  2581.         }
  2582.         return $result;
  2583.     }
  2584.     /**
  2585.      * Reset order items surcharge
  2586.      *
  2587.      * @param \XLite\Model\OrderItem[] $items Order items
  2588.      *
  2589.      * @return array
  2590.      */
  2591.     protected function resetItemsSurcharges($items)
  2592.     {
  2593.         $result = [];
  2594.         foreach ($items as $item) {
  2595.             $result[$item->getItemId() ?: spl_object_hash($item)] = $item->resetSurcharges();
  2596.         }
  2597.         return $result;
  2598.     }
  2599.     /**
  2600.      * Merge surcharges
  2601.      *
  2602.      * @param array $oldSurcharges Old surcharges
  2603.      *
  2604.      * @return void
  2605.      */
  2606.     protected function mergeSurcharges(array $oldSurcharges)
  2607.     {
  2608.         foreach ($this->getItems() as $item) {
  2609.             if (!empty($oldSurcharges['items'][$item->getItemId() ?: spl_object_hash($item)])) {
  2610.                 $item->compareSurcharges($oldSurcharges['items'][$item->getItemId() ?: spl_object_hash($item)]);
  2611.             }
  2612.         }
  2613.         $this->compareSurcharges($oldSurcharges['surcharges']);
  2614.     }
  2615.     /**
  2616.      * Calculate initial order values
  2617.      *
  2618.      * @return void
  2619.      */
  2620.     public function calculateInitialValues()
  2621.     {
  2622.         $subtotal 0;
  2623.         foreach ($this->getItems() as $item) {
  2624.             $item->calculate();
  2625.             $subtotal += $item->getSubtotal();
  2626.         }
  2627.         $subtotal $this->getCurrency()->roundValue($subtotal);
  2628.         $this->setSubtotal($subtotal);
  2629.         $this->setTotal($subtotal);
  2630.     }
  2631.     /**
  2632.      * Finalize items calculation
  2633.      *
  2634.      * @return void
  2635.      */
  2636.     protected function finalizeItemsCalculation()
  2637.     {
  2638.         $subtotal 0;
  2639.         foreach ($this->getItems() as $item) {
  2640.             $itemTotal $item->calculateTotal();
  2641.             $subtotal += $itemTotal;
  2642.             $item->setTotal($itemTotal);
  2643.         }
  2644.         $this->setSubtotal($subtotal);
  2645.         $this->setTotal($subtotal);
  2646.     }
  2647.     // }}}
  2648.     // {{{ Surcharges
  2649.     /**
  2650.      * Get surcharges by type
  2651.      *
  2652.      * @param string $type Surcharge type
  2653.      *
  2654.      * @return list<Surcharge>
  2655.      */
  2656.     public function getSurchargesByType($type null)
  2657.     {
  2658.         if ($type) {
  2659.             $list = [];
  2660.             foreach ($this->getSurcharges() as $surcharge) {
  2661.                 if ($surcharge->getType() === $type) {
  2662.                     $list[] = $surcharge;
  2663.                 }
  2664.             }
  2665.         } else {
  2666.             $list $this->getSurcharges();
  2667.         }
  2668.         return $list;
  2669.     }
  2670.     /**
  2671.      * Get surcharges subtotal with specified type
  2672.      *
  2673.      * @param string  $type    Surcharge type OPTIONAL
  2674.      * @param boolean $include Surcharge include flag OPTIONAL
  2675.      *
  2676.      * @return float
  2677.      */
  2678.     public function getSurchargesSubtotal($type null$include null)
  2679.     {
  2680.         $subtotal 0;
  2681.         $surcharges $this->getSurchargesByType($type);
  2682.         foreach ($surcharges as $surcharge) {
  2683.             if ($surcharge->getAvailable() && ($include === null || $surcharge->getInclude() === $include)) {
  2684.                 $subtotal += $this->getCurrency()->roundValue($surcharge->getValue());
  2685.             }
  2686.         }
  2687.         return $subtotal;
  2688.     }
  2689.     /**
  2690.      * Get subtotal for displaying
  2691.      *
  2692.      * @return float
  2693.      */
  2694.     public function getDisplaySubtotal()
  2695.     {
  2696.         return $this->getSubtotal();
  2697.     }
  2698.     /**
  2699.      * Get surcharges total with specified type
  2700.      *
  2701.      * @param string $type Surcharge type OPTIONAL
  2702.      *
  2703.      * @return float
  2704.      */
  2705.     public function getSurchargesTotal($type null)
  2706.     {
  2707.         return $this->getSubtotal() + $this->getSurchargesSubtotal($typefalse);
  2708.     }
  2709.     // }}}
  2710.     // {{{ Lifecycle callbacks
  2711.     /**
  2712.      * Prepare order before remove operation
  2713.      *
  2714.      * @return void
  2715.      */
  2716.     public function prepareBeforeRemove()
  2717.     {
  2718.         $profile $this->getProfile();
  2719.         $origProfile $this->getOrigProfile();
  2720.         try {
  2721.             if ($profile && (!$origProfile || $profile->getProfileId() !== $origProfile->getProfileId())) {
  2722.                 \XLite\Core\Database::getRepo('XLite\Model\Profile')->delete($profilefalse);
  2723.             }
  2724.         } catch (\Doctrine\ORM\EntityNotFoundException $e) {
  2725.         }
  2726.     }
  2727.     /**
  2728.      * Since Doctrine lifecycle callbacks do not allow to modify associations, we've added this method
  2729.      *
  2730.      * @param string $type Type of current operation
  2731.      *
  2732.      * @return void
  2733.      */
  2734.     public function prepareEntityBeforeCommit($type)
  2735.     {
  2736.         if ($type === static::ACTION_DELETE) {
  2737.             $this->setOldPaymentStatus(null);
  2738.             $this->setOldShippingStatus(null);
  2739.             $this->updateItemsSales(-1);
  2740.             $this->prepareBeforeRemove();
  2741.         }
  2742.     }
  2743.     // }}}
  2744.     // {{{ Change status routine
  2745.     /**
  2746.      * Get payment status code
  2747.      *
  2748.      * @return string
  2749.      */
  2750.     public function getPaymentStatusCode()
  2751.     {
  2752.         return $this->getPaymentStatus() && $this->getPaymentStatus()->getCode()
  2753.             ? $this->getPaymentStatus()->getCode()
  2754.             : '';
  2755.     }
  2756.     /**
  2757.      * Get shipping status code
  2758.      *
  2759.      * @return string
  2760.      */
  2761.     public function getShippingStatusCode()
  2762.     {
  2763.         return $this->getShippingStatus() && $this->getShippingStatus()->getCode()
  2764.             ? $this->getShippingStatus()->getCode()
  2765.             : '';
  2766.     }
  2767.     /**
  2768.      * Set payment status
  2769.      *
  2770.      * @param mixed $paymentStatus Payment status
  2771.      *
  2772.      * @return void
  2773.      */
  2774.     public function setPaymentStatus($paymentStatus null)
  2775.     {
  2776.         $this->processStatus($paymentStatus'payment');
  2777.     }
  2778.     /**
  2779.      * Set shipping status
  2780.      *
  2781.      * @param mixed $shippingStatus Shipping status
  2782.      *
  2783.      * @return void
  2784.      */
  2785.     public function setShippingStatus($shippingStatus null)
  2786.     {
  2787.         $this->processStatus($shippingStatus'shipping');
  2788.     }
  2789.     /**
  2790.      * Process order status
  2791.      *
  2792.      * @param mixed  $status Status
  2793.      * @param string $type   Type
  2794.      *
  2795.      * @return void
  2796.      */
  2797.     public function processStatus($status$type)
  2798.     {
  2799.         static $cache = [];
  2800.         if (is_scalar($status)) {
  2801.             if (!isset($cache[$type][$status])) {
  2802.                 $requestedStatus $status;
  2803.                 if (
  2804.                     is_int($status)
  2805.                     || (is_string($status)
  2806.                         && preg_match('/^[\d]+$/'$status)
  2807.                     )
  2808.                 ) {
  2809.                     $status \XLite\Core\Database::getRepo('XLite\Model\Order\Status\\' ucfirst($type))
  2810.                         ->find($status);
  2811.                 } elseif (is_string($status)) {
  2812.                     $status \XLite\Core\Database::getRepo('XLite\Model\Order\Status\\' ucfirst($type))
  2813.                         ->findOneByCode($status);
  2814.                 }
  2815.                 $cache[$type][$requestedStatus] = $status;
  2816.             } else {
  2817.                 $status $cache[$type][$status];
  2818.             }
  2819.         }
  2820.         $this->statusIsSet true;
  2821.         $this->{'old' ucfirst($type) . 'Status'} = $this->{$type 'Status'};
  2822.         $this->{$type 'Status'} = $status;
  2823.     }
  2824.     /**
  2825.      * Check order statuses
  2826.      *
  2827.      * @return boolean
  2828.      */
  2829.     public function checkStatuses()
  2830.     {
  2831.         $changed false;
  2832.         $em Database::getEM();
  2833.         if ($this->statusIsSet) {
  2834.             $this->statusIsSet false;
  2835.             $statusHandlers = [];
  2836.             foreach (['payment''shipping'] as $type) {
  2837.                 $status $this->{$type 'Status'};
  2838.                 $oldStatus $this->{'old' ucfirst($type) . 'Status'};
  2839.                 if (
  2840.                     $status
  2841.                     && $oldStatus
  2842.                     && $status->getId() !== $oldStatus->getId()
  2843.                 ) {
  2844.                     $em->addAfterFlushCallback(function () use ($oldStatus$status$type) {
  2845.                         \XLite\Core\OrderHistory::getInstance()->registerOrderChangeStatus(
  2846.                             $this->getOrderId(),
  2847.                             [
  2848.                                 'old'  => $oldStatus,
  2849.                                 'new'  => $status,
  2850.                                 'type' => $type,
  2851.                             ]
  2852.                         );
  2853.                         $this->processStatusAnyToAny($oldStatus$status$type);
  2854.                     });
  2855.                     $statusHandlers array_merge(
  2856.                         $this->getStatusHandlers($oldStatus$status$type),
  2857.                         $statusHandlers
  2858.                     );
  2859.                     $changed true;
  2860.                 } elseif (!$oldStatus) {
  2861.                     $this->{'old' ucfirst($type) . 'Status'} = $this->{$type 'Status'};
  2862.                 }
  2863.             }
  2864.             $em->addAfterFlushCallback(function () use ($changed$statusHandlers) {
  2865.                 if ($changed) {
  2866.                     $this->checkInventory();
  2867.                 }
  2868.                 if ($statusHandlers) {
  2869.                     foreach (array_unique($statusHandlers) as $handler) {
  2870.                         $this->{'process' ucfirst($handler)}();
  2871.                     }
  2872.                 }
  2873.                 foreach (['payment''shipping'] as $type) {
  2874.                     $this->{'old' ucfirst($type) . 'Status'} = null;
  2875.                 }
  2876.                 if ($changed) {
  2877.                     if (
  2878.                         !$this->isNotificationSent
  2879.                         && $this->isNotificationsAllowed()
  2880.                         && $this->getOrderNumber()
  2881.                     ) {
  2882.                         \XLite\Core\Mailer::sendOrderChanged($this$this->isIgnoreCustomerNotifications());
  2883.                     }
  2884.                 }
  2885.             });
  2886.         }
  2887.         return $changed;
  2888.     }
  2889.     /**
  2890.      * Check inventory
  2891.      *
  2892.      * @throws \Doctrine\ORM\OptimisticLockException
  2893.      */
  2894.     public function checkInventory()
  2895.     {
  2896.         $property \XLite\Core\Database::getRepo('XLite\Model\Order\Status\Property')->findOneBy(
  2897.             [
  2898.                 'paymentStatus'  => $this->paymentStatus,
  2899.                 'shippingStatus' => $this->shippingStatus,
  2900.             ]
  2901.         );
  2902.         $incStock $property $property->getIncStock() : null;
  2903.         if ($incStock !== null) {
  2904.             $stokStatus $this->getStockStatus();
  2905.             $oldIncStock null;
  2906.             if ($stokStatus === 'increased') {
  2907.                 $oldIncStock true;
  2908.             } elseif ($stokStatus === 'reduced') {
  2909.                 $oldIncStock false;
  2910.             }
  2911.             if ($oldIncStock === null) {
  2912.                 $property    \XLite\Core\Database::getRepo('XLite\Model\Order\Status\Property')->findOneBy(
  2913.                     [
  2914.                         'paymentStatus'  => $this->oldPaymentStatus,
  2915.                         'shippingStatus' => $this->oldShippingStatus,
  2916.                     ]
  2917.                 );
  2918.                 $oldIncStock $property $property->getIncStock() : null;
  2919.             }
  2920.             if ($oldIncStock !== null && $incStock !== $oldIncStock) {
  2921.                 if ($incStock) {
  2922.                     $this->processIncrease();
  2923.                     $this->setStockStatus('increased');
  2924.                 } else {
  2925.                     $this->processDecrease();
  2926.                     $this->setStockStatus('reduced');
  2927.                 }
  2928.             }
  2929.         }
  2930.         $this->updateSales();
  2931.         \XLite\Core\Database::getEM()->flush();
  2932.     }
  2933.     /**
  2934.      * Transform attributes: remove relations between order item attributes and product attribute values
  2935.      * to avoid data lost after product attribute values modification
  2936.      *
  2937.      * @return void
  2938.      */
  2939.     public function transformItemsAttributes()
  2940.     {
  2941.         foreach ($this->getItems() as $item) {
  2942.             if ($item->hasAttributeValues()) {
  2943.                 foreach ($item->getAttributeValues() as $av) {
  2944.                     if ($av->getAttributeValue()) {
  2945.                         $attributeValue $av->getAttributeValue();
  2946.                         $av->setName($attributeValue->getAttribute()->getName());
  2947.                         if (!($attributeValue instanceof \XLite\Model\AttributeValue\AttributeValueText)) {
  2948.                             $av->setValue($attributeValue->asString());
  2949.                         }
  2950.                         $av->setAttributeId($attributeValue->getAttribute()->getId());
  2951.                     }
  2952.                 }
  2953.             }
  2954.         }
  2955.     }
  2956.     /**
  2957.      * These handlers will be used in systemStatus1->...customStatus(es)..->systemStatus2 transition
  2958.      * by default, don't try to cast old statuses
  2959.      * @param string $type Type
  2960.      *
  2961.      * @return array
  2962.      */
  2963.     protected function getStatusHandlersForCast($type)
  2964.     {
  2965.         return [];
  2966.     }
  2967.     /**
  2968.      * Return base part of the certain "change status" handler name
  2969.      *
  2970.      * @param mixed  $oldStatus  Old order status
  2971.      * @param mixed  $newStatus  New order status
  2972.      * @param string $type Type
  2973.      *
  2974.      * @return string|array
  2975.      */
  2976.     protected function getStatusHandlers($oldStatus$newStatus$type)
  2977.     {
  2978.         $class '\XLite\Model\Order\Status\\' ucfirst($type);
  2979.         $oldCode $oldStatus->getCode();
  2980.         $newCode $newStatus->getCode();
  2981.         $statusHandlers $class::getStatusHandlers();
  2982.         $result = [];
  2983.         if ($oldCode && $newCode && isset($statusHandlers[$oldCode][$newCode])) {
  2984.             $result is_array($statusHandlers[$oldCode][$newCode])
  2985.                 ? array_unique($statusHandlers[$oldCode][$newCode])
  2986.                 : $statusHandlers[$oldCode][$newCode];
  2987.         }
  2988.         return $result;
  2989.     }
  2990.     /**
  2991.      * @param array $inStatusHandlers
  2992.      *
  2993.      * @return array
  2994.      */
  2995.     protected function getFlatStatuses($inStatusHandlers = [])
  2996.     {
  2997.         $flatStatuses array_reduce($inStatusHandlers, static function ($a$b) {
  2998.             return array_merge($a, (array) $b);
  2999.         }, []);
  3000.         return array_unique(array_merge(array_keys($flatStatuses), array_keys($inStatusHandlers)));
  3001.     }
  3002.     /**
  3003.      * A "change status" handler
  3004.      *
  3005.      * @return void
  3006.      */
  3007.     protected function processCheckout()
  3008.     {
  3009.     }
  3010.     /**
  3011.      * A "change status" handler
  3012.      *
  3013.      * @return void
  3014.      */
  3015.     protected function processDecrease()
  3016.     {
  3017.         $this->decreaseInventory();
  3018.     }
  3019.     /**
  3020.      * A "change status" handler
  3021.      *
  3022.      * @return void
  3023.      */
  3024.     protected function processUncheckout()
  3025.     {
  3026.     }
  3027.     /**
  3028.      * A "change status" handler
  3029.      *
  3030.      * @return void
  3031.      */
  3032.     protected function processQueue()
  3033.     {
  3034.     }
  3035.     /**
  3036.      * A "change status" handler
  3037.      *
  3038.      * @return void
  3039.      */
  3040.     protected function processAuthorize()
  3041.     {
  3042.         if ($this instanceof Cart) {
  3043.             $this->setIsNotificationsAllowedFlag(false);
  3044.         }
  3045.     }
  3046.     /**
  3047.      * A "change status" handler
  3048.      *
  3049.      * @return void
  3050.      * @throws \JsonException
  3051.      */
  3052.     protected function processProcess()
  3053.     {
  3054.         /** @var GmvTrackerDomain $gmvTracker */
  3055.         $gmvTracker \XCart\Container::getContainer()->get(GmvTrackerDomain::class);
  3056.         $gmvOrderData $gmvTracker->prepareOrderGmvData($this);
  3057.         $gmvTracker->saveOrderGmvData($gmvOrderData);
  3058.         if ($this->isNotificationsAllowed()) {
  3059.             \XLite\Core\Mailer::sendOrderProcessed($this$this->isIgnoreCustomerNotifications());
  3060.         }
  3061.         $this->setIsNotificationSent(true);
  3062.     }
  3063.     /**
  3064.      * A "change status" handler
  3065.      *
  3066.      * @return void
  3067.      */
  3068.     protected function processShip()
  3069.     {
  3070.         if ($this->isNotificationsAllowed() && !$this->isIgnoreCustomerNotifications()) {
  3071.             \XLite\Core\Mailer::sendOrderShipped($this);
  3072.         }
  3073.         $this->setIsNotificationSent(true);
  3074.     }
  3075.     /**
  3076.      * A "change status" handler
  3077.      *
  3078.      * @return void
  3079.      */
  3080.     protected function processWaitingForApprove()
  3081.     {
  3082.         if ($this->isNotificationsAllowed() && !$this->isIgnoreCustomerNotifications()) {
  3083.             \XLite\Core\Mailer::sendOrderWaitingForApprove($this);
  3084.         }
  3085.         $this->setIsNotificationSent(true);
  3086.     }
  3087.     /**
  3088.      * A "change status" handler
  3089.      *
  3090.      * @return void
  3091.      */
  3092.     protected function processReleaseBackorder()
  3093.     {
  3094.         foreach ($this->getItems() as $item) {
  3095.             $item->releaseBackorder();
  3096.         }
  3097.     }
  3098.     /**
  3099.      * A "change status" handler
  3100.      *
  3101.      * @return void
  3102.      */
  3103.     protected function processIncrease()
  3104.     {
  3105.         $this->increaseInventory();
  3106.     }
  3107.     /**
  3108.      * A "change status" handler
  3109.      *
  3110.      * @return void
  3111.      */
  3112.     protected function processDecline()
  3113.     {
  3114.     }
  3115.     /**
  3116.      * A "change status" handler
  3117.      *
  3118.      * @return void
  3119.      */
  3120.     protected function processFail()
  3121.     {
  3122.         if ($this->isNotificationsAllowed() && $this->getOrderNumber()) {
  3123.             \XLite\Core\Mailer::sendOrderFailed($this$this->isIgnoreCustomerNotifications());
  3124.         }
  3125.         $this->setIsNotificationSent(true);
  3126.     }
  3127.     /**
  3128.      * A "change status" handler
  3129.      *
  3130.      * @return void
  3131.      */
  3132.     protected function processCancel()
  3133.     {
  3134.         if ($this->isNotificationsAllowed()) {
  3135.             \XLite\Core\Mailer::sendOrderCanceled($this$this->isIgnoreCustomerNotifications());
  3136.         }
  3137.         $this->setIsNotificationSent(true);
  3138.     }
  3139.     /**
  3140.      * Status change handler for the "any to any" event.
  3141.      * 1)Allows to run handlers on systemStatus1->...customStatus(es)..->systemStatus2 transition
  3142.      *   Attention! handlers must include internal checking to avoid double execution
  3143.      *
  3144.      * @param $oldStatus
  3145.      * @param $newStatus
  3146.      * @param $type
  3147.      */
  3148.     protected function processStatusAnyToAny($oldStatus$newStatus$type)
  3149.     {
  3150.         // here is the key point to decide if we try to find an old system status in order history
  3151.         $statusHandlers2Cast $this->getStatusHandlersForCast($type);
  3152.         if (!$statusHandlers2Cast) {
  3153.             return;
  3154.         }
  3155.         // Transitions between these statuses are valued only
  3156.         $businessLogicStatuses $this->getFlatStatuses($statusHandlers2Cast);
  3157.         $inOldCode $oldStatus->getCode();
  3158.         $inNewCode $newStatus->getCode();
  3159.         // cycle is used for performance only, 'continue' below avoids unnecessary code execution
  3160.         foreach ($statusHandlers2Cast as $oldCode2Deal => $newCodes2Deal) {
  3161.             if (
  3162.                 $oldCode2Deal === $inOldCode // exact handler will be returned/launched by getStatusHandlers, do nothing
  3163.                 || !in_array($inNewCodearray_keys($newCodes2Deal)) // nothing to deal with here
  3164.                 || in_array($inOldCode$businessLogicStatuses// take into account transition from custom statuses to business statuses only
  3165.             ) {
  3166.                 continue;
  3167.             }
  3168.             // find the nearest business(system) status code from order history
  3169.             $oldSystemBusinessCode $this->getNearestOldBusinessCode($inOldCode$businessLogicStatuses$type);
  3170.             if (
  3171.                 $oldSystemBusinessCode !== $inOldCode
  3172.                 && isset($statusHandlers2Cast[$oldSystemBusinessCode][$inNewCode])
  3173.             ) {
  3174.                 // We have found an old business code before a bunch of meaningless ones
  3175.                 $castHandlers is_array($statusHandlers2Cast[$oldSystemBusinessCode][$inNewCode])
  3176.                     ? array_unique($statusHandlers2Cast[$oldSystemBusinessCode][$inNewCode])
  3177.                     : [$statusHandlers2Cast[$oldSystemBusinessCode][$inNewCode]];
  3178.                 foreach ($castHandlers as $castHandler) {
  3179.                     // here we run a handler for a cast oldBusinessStatus->...someStatuses...->newBusinessStatus transition
  3180.                     $methodOnStatusChange 'process' \Includes\Utils\Converter::convertToUpperCamelCase($castHandler);
  3181.                     if (method_exists($this$methodOnStatusChange)) {
  3182.                         $this->$methodOnStatusChange();
  3183.                     }
  3184.                 }
  3185.                 // typecast old code only once
  3186.                 break;
  3187.             }
  3188.         }
  3189.     }
  3190.     /**
  3191.      * Find backwards the nearest old business(system) code before a bunch of non-business logic codes
  3192.      *
  3193.      * @param $meaninglessOldCode
  3194.      * @param $businessLogicStatuses
  3195.      * @param $type
  3196.      *
  3197.      * @return string
  3198.      */
  3199.     protected function getNearestOldBusinessCode($meaninglessOldCode$businessLogicStatuses$type)
  3200.     {
  3201.         // this shared static cache is supposed to be used in other functions in the future. use the key ['OrderHistoryEvents', $this->getOrderId()]
  3202.         $historyList $this->executeCachedRuntime(function () {
  3203.             return \XLite\Core\Database::getRepo('XLite\Model\OrderHistoryEvents')->findAllByOrder($this) ?: [];
  3204.         }, ['OrderHistoryEvents'$this->getOrderId()]) ?: [];
  3205.         $eventCodeName $type === 'payment' \XLite\Core\OrderHistory::CODE_CHANGE_PAYMENT_STATUS_ORDER \XLite\Core\OrderHistory::CODE_CHANGE_SHIPPING_STATUS_ORDER;
  3206.         $historyList array_filter($historyList, static function ($event) use ($eventCodeName) {
  3207.             return $event->getCode() === $eventCodeName;
  3208.         });
  3209.         foreach ($historyList as $event) {
  3210.             $oldOrderStatus $event->getData()['oldStatusCode'];
  3211.             if (in_array($oldOrderStatus$businessLogicStatuses)) {
  3212.                 return $oldOrderStatus;
  3213.             }
  3214.         }
  3215.         return $meaninglessOldCode;
  3216.     }
  3217.     // }}}
  3218.     // {{{ Inventory tracking
  3219.     /**
  3220.      * Increase / decrease item products inventory
  3221.      *
  3222.      * @param integer $sign Flag; "1" or "-1"
  3223.      *
  3224.      * @return void
  3225.      */
  3226.     protected function changeItemsInventory($sign)
  3227.     {
  3228.         $registerAsGroup count($this->getItems());
  3229.         $data = [];
  3230.         foreach ($this->getItems() as $item) {
  3231.             $delta $sign $item->getAmount();
  3232.             if ($delta !== 0) {
  3233.                 $data[] = $this->getGroupedDataItem($item$delta);
  3234.             }
  3235.         }
  3236.         if ($registerAsGroup) {
  3237.             \XLite\Core\OrderHistory::getInstance()->registerChangeAmountGrouped(
  3238.                 $this->getOrderId(),
  3239.                 $data
  3240.             );
  3241.         }
  3242.         foreach ($this->getItems() as $item) {
  3243.             $this->changeItemInventory($item$sign, !$registerAsGroup);
  3244.         }
  3245.     }
  3246.     /**
  3247.      * Get grouped data item
  3248.      *
  3249.      * @param \XLite\Model\OrderItem    $item       Order item
  3250.      * @param integer                   $amount     Amount
  3251.      *
  3252.      * @return array
  3253.      */
  3254.     protected function getGroupedDataItem($item$amount)
  3255.     {
  3256.         return [
  3257.             'item'      => $item,
  3258.             'amount'    => $item->getProduct()->getPublicAmount(),
  3259.             'delta'     => $amount,
  3260.         ];
  3261.     }
  3262.     /**
  3263.      * Increase / decrease item product inventory
  3264.      *
  3265.      * @param \XLite\Model\OrderItem $item      Order item
  3266.      * @param integer                $sign      Flag; "1" or "-1"
  3267.      * @param boolean                $register  Register in order history OPTIONAL
  3268.      *
  3269.      * @return integer
  3270.      */
  3271.     protected function changeItemInventory($item$sign$register true)
  3272.     {
  3273.         $delta $sign $item->getAmount();
  3274.         $realDelta $delta && $item->getBackorderedAmount() > 0
  3275.             $delta $item->getBackorderedAmount()
  3276.             : $delta;
  3277.         if ($realDelta !== 0) {
  3278.             if ($register) {
  3279.                 $this->registerHistoryChangeItemAmount($item$realDelta);
  3280.             }
  3281.             $item->changeAmount($realDelta);
  3282.         }
  3283.         return $realDelta;
  3284.     }
  3285.     /**
  3286.      * @param \XLite\Model\OrderItem $item
  3287.      * @param integer                $delta
  3288.      */
  3289.     protected function registerHistoryChangeItemAmount($item$delta)
  3290.     {
  3291.         $history \XLite\Core\OrderHistory::getInstance();
  3292.         $history->registerChangeAmount($this->getOrderId(), $item->getProduct(), $delta);
  3293.     }
  3294.     /**
  3295.      * Order processed: decrease products inventory
  3296.      *
  3297.      * @return void
  3298.      */
  3299.     protected function decreaseInventory()
  3300.     {
  3301.         $this->changeItemsInventory(-1);
  3302.     }
  3303.     /**
  3304.      * Order declined: increase products inventory
  3305.      *
  3306.      * @return void
  3307.      */
  3308.     protected function increaseInventory()
  3309.     {
  3310.         $this->changeItemsInventory(1);
  3311.     }
  3312.     // }}}
  3313.     // {{{ Order actions
  3314.     /**
  3315.      * Get allowed actions
  3316.      *
  3317.      * @return array
  3318.      */
  3319.     public function getAllowedActions()
  3320.     {
  3321.         return [];
  3322.     }
  3323.     /**
  3324.      * Get allowed payment actions
  3325.      *
  3326.      * @return array
  3327.      */
  3328.     public function getAllowedPaymentActions()
  3329.     {
  3330.         $actions = [];
  3331.         $transactions $this->getPaymentTransactions();
  3332.         if ($transactions) {
  3333.             foreach ($transactions as $transaction) {
  3334.                 $processor $transaction->getPaymentMethod()
  3335.                     ? $transaction->getPaymentMethod()->getProcessor()
  3336.                     : null;
  3337.                 if ($processor) {
  3338.                     $allowedTransactions $processor->getAllowedTransactions();
  3339.                     foreach ($allowedTransactions as $transactionType) {
  3340.                         if ($processor->isTransactionAllowed($transaction$transactionType)) {
  3341.                             $actions[$transactionType] = $transaction->getTransactionId();
  3342.                         }
  3343.                     }
  3344.                 }
  3345.             }
  3346.         }
  3347.         return $actions;
  3348.     }
  3349.     /**
  3350.      * Get array of payment transaction sums (how much is authorized, captured and refunded)
  3351.      *
  3352.      * @return array
  3353.      */
  3354.     public function getPaymentTransactionSums()
  3355.     {
  3356.         $paymentTransactionSums $this->getRawPaymentTransactionSums();
  3357.         $lblAuth = (string) static::t('Authorized amount');
  3358.         $lblCapture = (string) static::t('Captured amount');
  3359.         $lblRefunded = (string) static::t('Refunded amount');
  3360.         $paymentTransactionSums = [
  3361.             $lblAuth     => $paymentTransactionSums['authorized'],
  3362.             $lblCapture  => $paymentTransactionSums['captured'],
  3363.             $lblRefunded => $paymentTransactionSums['refunded'],
  3364.         ];
  3365.         // Remove from array all zero sums
  3366.         foreach ($paymentTransactionSums as $k => $v) {
  3367.             if (0.01 >= $v) {
  3368.                 unset($paymentTransactionSums[$k]);
  3369.             }
  3370.         }
  3371.         return $paymentTransactionSums;
  3372.     }
  3373.     /**
  3374.      * Get array of raw payment transaction sums
  3375.      *
  3376.      * @param boolean $override Override cache OPTIONAL
  3377.      *
  3378.      * @return array
  3379.      */
  3380.     public function getRawPaymentTransactionSums($override false)
  3381.     {
  3382.         if ($this->paymentTransactionSums === null || $override) {
  3383.             $transactions $this->getPaymentTransactions();
  3384.             $this->paymentTransactionSums = [
  3385.                 'authorized' => 0,
  3386.                 'captured'   => 0,
  3387.                 'refunded'   => 0,
  3388.                 'sale'       => 0,
  3389.                 'blocked'    => 0,
  3390.             ];
  3391.             foreach ($transactions as $t) {
  3392.                 if ($t->isPersistent()) {
  3393.                     \XLite\Core\Database::getEM()->refresh($t);
  3394.                 }
  3395.                 $backendTransactions $t->getBackendTransactions();
  3396.                 $authorized 0;
  3397.                 if ($backendTransactions && count($backendTransactions) > 0) {
  3398.                     // By backend transactions
  3399.                     foreach ($backendTransactions as $bt) {
  3400.                         if ($bt->isCompleted()) {
  3401.                             switch ($bt->getType()) {
  3402.                                 case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_AUTH:
  3403.                                     $authorized += $bt->getValue();
  3404.                                     $this->paymentTransactionSums['blocked'] += $bt->getValue();
  3405.                                     break;
  3406.                                 case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_SALE:
  3407.                                     $this->paymentTransactionSums['blocked'] += $bt->getValue();
  3408.                                     $this->paymentTransactionSums['sale'] += $bt->getValue();
  3409.                                     break;
  3410.                                 case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_CAPTURE:
  3411.                                 case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_CAPTURE_PART:
  3412.                                     $this->paymentTransactionSums['captured'] += $bt->getValue();
  3413.                                     $authorized -= $bt->getValue();
  3414.                                     $this->paymentTransactionSums['sale'] += $bt->getValue();
  3415.                                     break;
  3416.                                 case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_REFUND:
  3417.                                 case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_REFUND_PART:
  3418.                                     $this->paymentTransactionSums['refunded'] += $bt->getValue();
  3419.                                     $this->paymentTransactionSums['blocked'] -= $bt->getValue();
  3420.                                     $this->paymentTransactionSums['sale'] -= $bt->getValue();
  3421.                                     break;
  3422.                                 case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_REFUND_MULTI:
  3423.                                     $this->paymentTransactionSums['refunded'] += $bt->getValue();
  3424.                                     $authorized -= $bt->getValue();
  3425.                                     $this->paymentTransactionSums['blocked'] -= $bt->getValue();
  3426.                                     $this->paymentTransactionSums['sale'] -= $bt->getValue();
  3427.                                     break;
  3428.                                 case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_VOID:
  3429.                                 case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_VOID_PART:
  3430.                                     $authorized -= $bt->getValue();
  3431.                                     $this->paymentTransactionSums['blocked'] -= $bt->getValue();
  3432.                                     break;
  3433.                                 default:
  3434.                             }
  3435.                         }
  3436.                     }
  3437.                 } else {
  3438.                     // By transaction
  3439.                     if ($t->isCompleted()) {
  3440.                         switch ($t->getType()) {
  3441.                             case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_AUTH:
  3442.                                 $authorized += $t->getValue();
  3443.                                 $this->paymentTransactionSums['blocked'] += $t->getValue();
  3444.                                 break;
  3445.                             case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_SALE:
  3446.                                 $this->paymentTransactionSums['blocked'] += $t->getValue();
  3447.                                 $this->paymentTransactionSums['sale'] += $t->getValue();
  3448.                                 break;
  3449.                             case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_CAPTURE:
  3450.                             case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_CAPTURE_PART:
  3451.                                 $this->paymentTransactionSums['captured'] += $t->getValue();
  3452.                                 $authorized -= $t->getValue();
  3453.                                 $this->paymentTransactionSums['sale'] += $t->getValue();
  3454.                                 break;
  3455.                             case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_REFUND:
  3456.                             case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_REFUND_PART:
  3457.                                 $this->paymentTransactionSums['refunded'] += $t->getValue();
  3458.                                 $this->paymentTransactionSums['blocked'] -= $t->getValue();
  3459.                                 $this->paymentTransactionSums['sale'] -= $t->getValue();
  3460.                                 break;
  3461.                             case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_REFUND_MULTI:
  3462.                                 $this->paymentTransactionSums['refunded'] += $t->getValue();
  3463.                                 $authorized -= $t->getValue();
  3464.                                 $this->paymentTransactionSums['blocked'] -= $t->getValue();
  3465.                                 $this->paymentTransactionSums['sale'] -= $t->getValue();
  3466.                                 break;
  3467.                             case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_VOID:
  3468.                             case \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_VOID_PART:
  3469.                                 $authorized -= $t->getValue();
  3470.                                 $this->paymentTransactionSums['blocked'] -= $t->getValue();
  3471.                                 break;
  3472.                             default:
  3473.                         }
  3474.                     }
  3475.                 }
  3476.                 if (
  3477.                     $authorized
  3478.                     && $this->paymentTransactionSums['captured']
  3479.                     && !$t->getPaymentMethod()->getProcessor()->isTransactionAllowed($t\XLite\Model\Payment\BackendTransaction::TRAN_TYPE_CAPTURE_MULTI)
  3480.                 ) {
  3481.                     // Do not take in consideration an authorized sum after capture
  3482.                     // if payment processor does not support multiple partial capture transactions
  3483.                     $authorized 0;
  3484.                 }
  3485.                 $this->paymentTransactionSums['authorized'] += $authorized;
  3486.             } // foreach
  3487.             $this->paymentTransactionSums array_map(function ($item) {
  3488.                 return $this->getCurrency()->roundValue($item);
  3489.             }, $this->paymentTransactionSums);
  3490.         }
  3491.         return $this->paymentTransactionSums;
  3492.     }
  3493.     // }}}
  3494.     // {{{ Common for several pages method to use in invoice templates
  3495.     /**
  3496.      * Return true if shipping section should be visible on the invoice
  3497.      *
  3498.      * @return boolean
  3499.      */
  3500.     public function isShippingSectionVisible()
  3501.     {
  3502.         $modifier $this->getModifier(\XLite\Model\Base\Surcharge::TYPE_SHIPPING'SHIPPING');
  3503.         return $modifier && $modifier->canApply();
  3504.     }
  3505.     /**
  3506.      * Return true if payment section should be visible on the invoice
  3507.      * (this section is always visible by the default)
  3508.      *
  3509.      * @return boolean
  3510.      */
  3511.     public function isPaymentSectionVisible()
  3512.     {
  3513.         return true;
  3514.     }
  3515.     /**
  3516.      * Return true if payment and/or shipping sections should be visible on the invoice
  3517.      *
  3518.      * @return boolean
  3519.      */
  3520.     public function isPaymentShippingSectionVisible()
  3521.     {
  3522.         return $this->isShippingSectionVisible() || $this->isPaymentSectionVisible();
  3523.     }
  3524.     // }}}
  3525.     /**
  3526.      * Renew payment status.
  3527.      * Return true if payment status has been changed
  3528.      *
  3529.      * @return boolean
  3530.      */
  3531.     public function renewPaymentStatus()
  3532.     {
  3533.         $result false;
  3534.         $status $this->getCalculatedPaymentStatus(true);
  3535.         if ($this->getPaymentStatusCode() !== $status) {
  3536.             $this->setPaymentStatus($status);
  3537.             $result true;
  3538.         }
  3539.         return $result;
  3540.     }
  3541.     /**
  3542.      * Get calculated payment status
  3543.      *
  3544.      * @param boolean $override Override calculation cache OPTIONAL
  3545.      *
  3546.      * @return string
  3547.      */
  3548.     public function getCalculatedPaymentStatus($override false)
  3549.     {
  3550.         $result \XLite\Model\Order\Status\Payment::STATUS_QUEUED;
  3551.         $sums $this->getRawPaymentTransactionSums($override);
  3552.         $total $this->getCurrency()->roundValue($this->getTotal());
  3553.         if ($total == 0.0) {
  3554.             $result \XLite\Model\Order\Status\Payment::STATUS_PAID;
  3555.         } elseif ($sums['authorized'] > && $sums['sale'] == && $sums['captured'] == 0) {
  3556.             $result \XLite\Model\Order\Status\Payment::STATUS_AUTHORIZED;
  3557.         } elseif ($sums['sale'] < $total) {
  3558.             if ($sums['sale'] > 0) {
  3559.                 $result \XLite\Model\Order\Status\Payment::STATUS_PART_PAID;
  3560.             } elseif ($sums['refunded'] > 0) {
  3561.                 $result \XLite\Model\Order\Status\Payment::STATUS_REFUNDED;
  3562.             }
  3563.         } else {
  3564.             if ($sums['sale'] > || $sums['captured'] > 0) {
  3565.                 $result \XLite\Model\Order\Status\Payment::STATUS_PAID;
  3566.             } elseif ($sums['refunded'] > 0) {
  3567.                 $result \XLite\Model\Order\Status\Payment::STATUS_REFUNDED;
  3568.             }
  3569.         }
  3570.         $lastTransaction $this->getPaymentTransactions()->last();
  3571.         if ($lastTransaction && $lastTransaction->isVoid()) {
  3572.             $result \XLite\Model\Order\Status\Payment::STATUS_CANCELED;
  3573.         }
  3574.         if ($result === \XLite\Model\Order\Status\Payment::STATUS_QUEUED) {
  3575.             if ($lastTransaction) {
  3576.                 if ($lastTransaction->isFailed() || $lastTransaction->isVoid()) {
  3577.                     $result \XLite\Model\Order\Status\Payment::STATUS_DECLINED;
  3578.                 }
  3579.                 if ($lastTransaction->isCanceled()) {
  3580.                     $result \XLite\Model\Order\Status\Payment::STATUS_CANCELED;
  3581.                 }
  3582.             }
  3583.         }
  3584.         return $result;
  3585.     }
  3586.     /**
  3587.      * Set order status by transaction
  3588.      *
  3589.      * @param \XLite\Model\Payment\Transaction $transaction Transaction which changes status
  3590.      *
  3591.      * @return void
  3592.      */
  3593.     public function setPaymentStatusByTransaction(\XLite\Model\Payment\Transaction $transaction)
  3594.     {
  3595.         if ($this->isPayed()) {
  3596.             $status $transaction->isCaptured()
  3597.                 ? \XLite\Model\Order\Status\Payment::STATUS_PAID
  3598.                 \XLite\Model\Order\Status\Payment::STATUS_AUTHORIZED;
  3599.         } else {
  3600.             if ($transaction->isRefunded()) {
  3601.                 $paymentTransactionSums $this->getRawPaymentTransactionSums(true);
  3602.                 $refunded $paymentTransactionSums['refunded'];
  3603.                 // Check if the whole refunded sum (along with the previous refunded transactions for the order)
  3604.                 // covers the whole total for order
  3605.                 $status $this->getCurrency()->roundValue($refunded) < $this->getCurrency()->roundValue($this->getTotal())
  3606.                     ? \XLite\Model\Order\Status\Payment::STATUS_PART_PAID
  3607.                     \XLite\Model\Order\Status\Payment::STATUS_REFUNDED;
  3608.             } elseif ($transaction->isFailed()) {
  3609.                 $status \XLite\Model\Order\Status\Payment::STATUS_DECLINED;
  3610.             } elseif ($transaction->isVoid()) {
  3611.                 $status \XLite\Model\Order\Status\Payment::STATUS_CANCELED;
  3612.             } elseif ($transaction->isCaptured()) {
  3613.                 $status \XLite\Model\Order\Status\Payment::STATUS_PART_PAID;
  3614.             } else {
  3615.                 $status \XLite\Model\Order\Status\Payment::STATUS_QUEUED;
  3616.             }
  3617.         }
  3618.         $this->setPaymentStatus($status);
  3619.     }
  3620.     /**
  3621.      * Checks whether order is shippable or not
  3622.      *
  3623.      * @return boolean
  3624.      */
  3625.     public function isShippable()
  3626.     {
  3627.         $result false;
  3628.         foreach ($this->getItems() as $item) {
  3629.             if ($item->isShippable()) {
  3630.                 $result true;
  3631.                 break;
  3632.             }
  3633.         }
  3634.         return $result;
  3635.     }
  3636.     /**
  3637.      * Get addresses list
  3638.      *
  3639.      * @return array
  3640.      */
  3641.     public function getAddresses()
  3642.     {
  3643.         $list $this->getProfile()->getAddresses()->toArray();
  3644.         if ($this->getOrigProfile()) {
  3645.             foreach ($this->getOrigProfile()->getAddresses() as $address) {
  3646.                 $equal false;
  3647.                 foreach ($list as $address2) {
  3648.                     if (!$equal && $address->isEqualAddress($address2)) {
  3649.                         $equal true;
  3650.                         break;
  3651.                     }
  3652.                 }
  3653.                 if (!$equal) {
  3654.                     $list[] = $address;
  3655.                 }
  3656.             }
  3657.         }
  3658.         return $list;
  3659.     }
  3660.     /**
  3661.      * Check if all order items in order is valid
  3662.      *
  3663.      * @return boolean
  3664.      */
  3665.     protected function isAllItemsValid()
  3666.     {
  3667.         $result true;
  3668.         foreach ($this->getItems() as $item) {
  3669.             if (!$item->isValid()) {
  3670.                 $result false;
  3671.                 break;
  3672.             }
  3673.         }
  3674.         return $result;
  3675.     }
  3676.     /**
  3677.      * Check if all order items in order is configured
  3678.      *
  3679.      * @return boolean
  3680.      */
  3681.     protected function isConfigured()
  3682.     {
  3683.         $isConfigured true;
  3684.         foreach ($this->getItems() as $item) {
  3685.             if (!$item->isConfigured()) {
  3686.                 $isConfigured false;
  3687.                 break;
  3688.             }
  3689.         }
  3690.         return $isConfigured;
  3691.     }
  3692.     /**
  3693.      * Get payment transaction data.
  3694.      * These data are displayed on the order page, invoice and packing slip
  3695.      *
  3696.      * @param boolean $isPrimary Flag: true - return only data fields marked by processor as 'primary', false - all fields OPTIONAL
  3697.      *
  3698.      * @return array
  3699.      */
  3700.     public function getPaymentTransactionData($isPrimary false)
  3701.     {
  3702.         $result = [];
  3703.         $transaction $this->getPaymentTransactions()
  3704.             ? $this->getPaymentTransactions()->last()
  3705.             : null;
  3706.         if ($transaction) {
  3707.             $method $transaction->getPaymentMethod() ?: $this->getPaymentMethod();
  3708.             $processor $method $method->getProcessor() : null;
  3709.             if ($processor) {
  3710.                 $result $processor->getTransactionData($transaction$isPrimary);
  3711.             }
  3712.         }
  3713.         return $result;
  3714.     }
  3715.     /**
  3716.      * Get last payment transaction ID.
  3717.      * This data is displayed on the order page, invoice and packing slip
  3718.      *
  3719.      * @return string|null
  3720.      */
  3721.     public function getPaymentTransactionId()
  3722.     {
  3723.         $transaction $this->getPaymentTransactions()
  3724.             ? $this->getPaymentTransactions()->last()
  3725.             : null;
  3726.         return $transaction
  3727.             $transaction->getPublicId()
  3728.             : null;
  3729.     }
  3730.     /**
  3731.      * @return bool
  3732.      */
  3733.     public function isOfflineProcessorUsed()
  3734.     {
  3735.         $processor $this->getPaymentProcessor();
  3736.         return $processor instanceof \XLite\Model\Payment\Processor\Offline;
  3737.     }
  3738.     /**
  3739.      * @return \XLite\Model\Payment\Base\Processor
  3740.      */
  3741.     public function getPaymentProcessor()
  3742.     {
  3743.         $processor null;
  3744.         $transaction $this->getPaymentTransactions()
  3745.             ? $this->getPaymentTransactions()->last()
  3746.             : null;
  3747.         if ($transaction) {
  3748.             $method $transaction->getPaymentMethod() ?: $this->getPaymentMethod();
  3749.             $processor $method $method->getProcessor() : null;
  3750.         }
  3751.         return $processor;
  3752.     }
  3753.     // {{{ Sales statistic
  3754.     /**
  3755.      * Returns old payment status code
  3756.      *
  3757.      * @return string
  3758.      */
  3759.     protected function getOldPaymentStatusCode()
  3760.     {
  3761.         $oldPaymentStatus $this->oldPaymentStatus;
  3762.         return $oldPaymentStatus && $oldPaymentStatus->getCode()
  3763.             ? $oldPaymentStatus->getCode()
  3764.             : '';
  3765.     }
  3766.     /**
  3767.      * Calculate sales delta
  3768.      *
  3769.      * @return integer|null
  3770.      */
  3771.     protected function getSalesDelta()
  3772.     {
  3773.         $result null;
  3774.         $newStatusCode $this->getPaymentStatusCode();
  3775.         if ($newStatusCode) {
  3776.             $oldStatusCode $this->getOldPaymentStatusCode();
  3777.             $paidStatuses \XLite\Model\Order\Status\Payment::getPaidStatuses();
  3778.             if (
  3779.                 (!$oldStatusCode || !in_array($oldStatusCode$paidStatusestrue))
  3780.                 && in_array($newStatusCode$paidStatusestrue)
  3781.             ) {
  3782.                 $result 1;
  3783.             } elseif (
  3784.                 $oldStatusCode
  3785.                 && in_array($oldStatusCode$paidStatusestrue)
  3786.                 && !in_array($newStatusCode$paidStatusestrue)
  3787.             ) {
  3788.                 $result = -1;
  3789.             }
  3790.         }
  3791.         return $result;
  3792.     }
  3793.     /**
  3794.      * Update sales statistics
  3795.      *
  3796.      * @param integer $delta Delta
  3797.      */
  3798.     protected function updateItemsSales($delta)
  3799.     {
  3800.         if ($delta === null) {
  3801.             return;
  3802.         }
  3803.         foreach ($this->getItems() as $item) {
  3804.             $product $item->getObject();
  3805.             if ($product === null) {
  3806.                 continue;
  3807.             }
  3808.             $product->setSales(
  3809.                 $product->getSales() + $delta $item->getAmount()
  3810.             );
  3811.         }
  3812.     }
  3813.     /**
  3814.      * Update sales
  3815.      */
  3816.     protected function updateSales()
  3817.     {
  3818.         $this->updateItemsSales($this->getSalesDelta());
  3819.     }
  3820.     // }}}
  3821.     /**
  3822.      * Get order_id
  3823.      *
  3824.      * @return integer
  3825.      */
  3826.     public function getOrderId()
  3827.     {
  3828.         return $this->order_id;
  3829.     }
  3830.     public function getPublicId(): ?string
  3831.     {
  3832.         return $this->public_id;
  3833.     }
  3834.     /**
  3835.      * Cannot be strict `string` type as there are 'public_id=null' orders after upgrade. #XCB-2685
  3836.      */
  3837.     public function setPublicId(?string $public_id): void
  3838.     {
  3839.         $this->public_id $public_id;
  3840.     }
  3841.     /**
  3842.      * Set shipping_id
  3843.      *
  3844.      * @param integer $shippingId
  3845.      * @return Order
  3846.      */
  3847.     public function setShippingId($shippingId)
  3848.     {
  3849.         $this->shipping_id $shippingId;
  3850.         return $this;
  3851.     }
  3852.     /**
  3853.      * Get shipping_id
  3854.      *
  3855.      * @return integer
  3856.      */
  3857.     public function getShippingId()
  3858.     {
  3859.         return $this->shipping_id;
  3860.     }
  3861.     /**
  3862.      * Set shipping_method_name
  3863.      *
  3864.      * @param string $shippingMethodName
  3865.      * @return Order
  3866.      */
  3867.     public function setShippingMethodName($shippingMethodName)
  3868.     {
  3869.         $this->shipping_method_name $shippingMethodName;
  3870.         return $this;
  3871.     }
  3872.     /**
  3873.      * Set payment_method_name
  3874.      *
  3875.      * @param string $paymentMethodName
  3876.      * @return Order
  3877.      */
  3878.     public function setPaymentMethodName($paymentMethodName)
  3879.     {
  3880.         $this->payment_method_name $paymentMethodName;
  3881.         return $this;
  3882.     }
  3883.     /**
  3884.      * Set tracking
  3885.      *
  3886.      * @param string $tracking
  3887.      * @return Order
  3888.      */
  3889.     public function setTracking($tracking)
  3890.     {
  3891.         $this->tracking $tracking;
  3892.         return $this;
  3893.     }
  3894.     /**
  3895.      * Get tracking
  3896.      *
  3897.      * @return string
  3898.      */
  3899.     public function getTracking()
  3900.     {
  3901.         return $this->tracking;
  3902.     }
  3903.     /**
  3904.      * Set date
  3905.      *
  3906.      * @param integer $date
  3907.      * @return Order
  3908.      */
  3909.     public function setDate($date)
  3910.     {
  3911.         $this->date $date;
  3912.         return $this;
  3913.     }
  3914.     /**
  3915.      * Get date
  3916.      *
  3917.      * @return integer
  3918.      */
  3919.     public function getDate()
  3920.     {
  3921.         return $this->date;
  3922.     }
  3923.     /**
  3924.      * Set lastRenewDate
  3925.      *
  3926.      * @param integer $lastRenewDate
  3927.      * @return Order
  3928.      */
  3929.     public function setLastRenewDate($lastRenewDate)
  3930.     {
  3931.         $this->lastRenewDate $lastRenewDate;
  3932.         return $this;
  3933.     }
  3934.     /**
  3935.      * Get lastRenewDate
  3936.      *
  3937.      * @return integer
  3938.      */
  3939.     public function getLastRenewDate()
  3940.     {
  3941.         return $this->lastRenewDate;
  3942.     }
  3943.     /**
  3944.      * Set notes
  3945.      *
  3946.      * @param string $notes
  3947.      *
  3948.      * @return Order
  3949.      */
  3950.     public function setNotes($notes)
  3951.     {
  3952.         $this->notes $notes;
  3953.         return $this;
  3954.     }
  3955.     /**
  3956.      * Get notes
  3957.      *
  3958.      * @return string
  3959.      */
  3960.     public function getNotes()
  3961.     {
  3962.         return $this->notes;
  3963.     }
  3964.     /**
  3965.      * Set adminNotes
  3966.      *
  3967.      * @param string $adminNotes
  3968.      *
  3969.      * @return Order
  3970.      */
  3971.     public function setAdminNotes($adminNotes)
  3972.     {
  3973.         $this->adminNotes $adminNotes;
  3974.         return $this;
  3975.     }
  3976.     /**
  3977.      * Get adminNotes
  3978.      *
  3979.      * @return string
  3980.      */
  3981.     public function getAdminNotes()
  3982.     {
  3983.         return $this->adminNotes;
  3984.     }
  3985.     public function getSalesChannel(): ?string
  3986.     {
  3987.         return $this->salesChannel;
  3988.     }
  3989.     public function setSalesChannel(?string $salesChannel): Order
  3990.     {
  3991.         $this->salesChannel $salesChannel;
  3992.         return $this;
  3993.     }
  3994.     /**
  3995.      * Set orderNumber
  3996.      *
  3997.      * @param string $orderNumber
  3998.      * @return Order
  3999.      */
  4000.     public function setOrderNumber($orderNumber)
  4001.     {
  4002.         $this->orderNumber $orderNumber;
  4003.         return $this;
  4004.     }
  4005.     /**
  4006.      * Get orderNumber
  4007.      *
  4008.      * @return string
  4009.      */
  4010.     public function getOrderNumber()
  4011.     {
  4012.         return $this->orderNumber;
  4013.     }
  4014.     /**
  4015.      * Set recent
  4016.      *
  4017.      * @param boolean $recent
  4018.      * @return Order
  4019.      */
  4020.     public function setRecent($recent)
  4021.     {
  4022.         $this->recent $recent;
  4023.         return $this;
  4024.     }
  4025.     /**
  4026.      * Get recent
  4027.      *
  4028.      * @return boolean
  4029.      */
  4030.     public function getRecent()
  4031.     {
  4032.         return $this->recent;
  4033.     }
  4034.     /**
  4035.      * Set xcPendingExport
  4036.      *
  4037.      * @param boolean $xcPendingExport
  4038.      * @return Order
  4039.      */
  4040.     public function setXcPendingExport($xcPendingExport)
  4041.     {
  4042.         $this->xcPendingExport $xcPendingExport;
  4043.         return $this;
  4044.     }
  4045.     /**
  4046.      * Get xcPendingExport
  4047.      *
  4048.      * @return boolean
  4049.      */
  4050.     public function getXcPendingExport()
  4051.     {
  4052.         return $this->xcPendingExport;
  4053.     }
  4054.     /**
  4055.      * Return BackorderCompetitors
  4056.      *
  4057.      * @return ArrayCollection
  4058.      */
  4059.     public function getBackorderCompetitors()
  4060.     {
  4061.         return $this->backorderCompetitors;
  4062.     }
  4063.     /**
  4064.      * Set BackorderCompetitors
  4065.      *
  4066.      * @param Order[] $backorderCompetitors
  4067.      *
  4068.      * @return $this
  4069.      */
  4070.     public function setBackorderCompetitors($backorderCompetitors)
  4071.     {
  4072.         $this->backorderCompetitors $backorderCompetitors;
  4073.         return $this;
  4074.     }
  4075.     /**
  4076.      * Get total
  4077.      *
  4078.      * @return float
  4079.      */
  4080.     public function getTotal()
  4081.     {
  4082.         return $this->total;
  4083.     }
  4084.     /**
  4085.      * Get subtotal
  4086.      *
  4087.      * @return float
  4088.      */
  4089.     public function getSubtotal()
  4090.     {
  4091.         return $this->subtotal;
  4092.     }
  4093.     /**
  4094.      * Get profile
  4095.      *
  4096.      * @return \XLite\Model\Profile
  4097.      */
  4098.     public function getProfile()
  4099.     {
  4100.         return $this->profile;
  4101.     }
  4102.     /**
  4103.      * Get orig_profile
  4104.      *
  4105.      * @return \XLite\Model\Profile
  4106.      */
  4107.     public function getOrigProfile()
  4108.     {
  4109.         return $this->orig_profile;
  4110.     }
  4111.     /**
  4112.      * Get paymentStatus
  4113.      *
  4114.      * @return \XLite\Model\Order\Status\Payment
  4115.      */
  4116.     public function getPaymentStatus()
  4117.     {
  4118.         return $this->paymentStatus;
  4119.     }
  4120.     /**
  4121.      * Get shippingStatus
  4122.      *
  4123.      * @return \XLite\Model\Order\Status\Shipping
  4124.      */
  4125.     public function getShippingStatus()
  4126.     {
  4127.         return $this->shippingStatus;
  4128.     }
  4129.     /**
  4130.      * Add details
  4131.      *
  4132.      * @param \XLite\Model\OrderDetail $details
  4133.      * @return Order
  4134.      */
  4135.     public function addDetails(\XLite\Model\OrderDetail $details)
  4136.     {
  4137.         $this->details[] = $details;
  4138.         return $this;
  4139.     }
  4140.     /**
  4141.      * Get details
  4142.      *
  4143.      * @return \Doctrine\Common\Collections\Collection|\XLite\Model\OrderDetail[]
  4144.      */
  4145.     public function getDetails()
  4146.     {
  4147.         return $this->details;
  4148.     }
  4149.     /**
  4150.      * Add trackingNumbers
  4151.      *
  4152.      * @param \XLite\Model\OrderTrackingNumber $trackingNumbers
  4153.      * @return Order
  4154.      */
  4155.     public function addTrackingNumbers(\XLite\Model\OrderTrackingNumber $trackingNumbers)
  4156.     {
  4157.         $this->trackingNumbers[] = $trackingNumbers;
  4158.         return $this;
  4159.     }
  4160.     /**
  4161.      * Get trackingNumbers
  4162.      *
  4163.      * @return \Doctrine\Common\Collections\Collection
  4164.      */
  4165.     public function getTrackingNumbers()
  4166.     {
  4167.         return $this->trackingNumbers;
  4168.     }
  4169.     /**
  4170.      * Add events
  4171.      *
  4172.      * @param \XLite\Model\OrderHistoryEvents $events
  4173.      * @return Order
  4174.      */
  4175.     public function addEvents(\XLite\Model\OrderHistoryEvents $events)
  4176.     {
  4177.         $this->events[] = $events;
  4178.         return $this;
  4179.     }
  4180.     /**
  4181.      * Get events
  4182.      *
  4183.      * @return \Doctrine\Common\Collections\Collection
  4184.      */
  4185.     public function getEvents()
  4186.     {
  4187.         return $this->events;
  4188.     }
  4189.     /**
  4190.      * Add item
  4191.      *
  4192.      * @param \XLite\Model\OrderItem $item
  4193.      * @return Order
  4194.      */
  4195.     public function addItems(\XLite\Model\OrderItem $item)
  4196.     {
  4197.         $this->items[] = $item;
  4198.         return $this;
  4199.     }
  4200.     /**
  4201.      * Get items
  4202.      *
  4203.      * @return list<OrderItem>
  4204.      */
  4205.     public function getItems()
  4206.     {
  4207.         return $this->items;
  4208.     }
  4209.     /**
  4210.      * Add surcharges
  4211.      *
  4212.      * @param \XLite\Model\Order\Surcharge $surcharges
  4213.      * @return Order
  4214.      */
  4215.     public function addSurcharges(\XLite\Model\Order\Surcharge $surcharges)
  4216.     {
  4217.         $this->surcharges[] = $surcharges;
  4218.         return $this;
  4219.     }
  4220.     /**
  4221.      * Get surcharges
  4222.      *
  4223.      * @return \Doctrine\Common\Collections\Collection
  4224.      */
  4225.     public function getSurcharges()
  4226.     {
  4227.         return $this->surcharges;
  4228.     }
  4229.     /**
  4230.      * Add payment_transactions
  4231.      *
  4232.      * @param \XLite\Model\Payment\Transaction $paymentTransactions
  4233.      * @return Order
  4234.      */
  4235.     public function addPaymentTransactions(\XLite\Model\Payment\Transaction $paymentTransactions)
  4236.     {
  4237.         $this->payment_transactions[] = $paymentTransactions;
  4238.         return $this;
  4239.     }
  4240.     /**
  4241.      * Get payment_transactions
  4242.      *
  4243.      * @return \Doctrine\Common\Collections\Collection|\XLite\Model\Payment\Transaction[]
  4244.      */
  4245.     public function getPaymentTransactions()
  4246.     {
  4247.         $result $this->payment_transactions;
  4248.         $compare = static function ($a$b) {
  4249.             /** @var \XLite\Model\Payment\Transaction $a */
  4250.             /** @var \XLite\Model\Payment\Transaction $b */
  4251.             return ($a->getTransactionId() < $b->getTransactionId()) ? -1;
  4252.         };
  4253.         if ($result instanceof \Doctrine\Common\Collections\Collection) {
  4254.             $iterator $result->getIterator();
  4255.             $iterator->uasort($compare);
  4256.             foreach ($iterator as $key => $item) {
  4257.                 $result->set($key$item);
  4258.             }
  4259.         } elseif (is_array($result)) {
  4260.             uasort($result$compare);
  4261.         }
  4262.         return $result;
  4263.     }
  4264.     /**
  4265.      * Set currency
  4266.      *
  4267.      * @param \XLite\Model\Currency $currency
  4268.      * @return Order
  4269.      */
  4270.     public function setCurrency(\XLite\Model\Currency $currency null)
  4271.     {
  4272.         $this->currency $currency;
  4273.         return $this;
  4274.     }
  4275.     /**
  4276.      * Get currency
  4277.      *
  4278.      * @return \XLite\Model\Currency
  4279.      */
  4280.     public function getCurrency()
  4281.     {
  4282.         return $this->currency;
  4283.     }
  4284.     /**
  4285.      * Check - block Paid is visible or not
  4286.      *
  4287.      * @return boolean
  4288.      */
  4289.     protected function blockPaidIsVisible()
  4290.     {
  4291.         return $this->getPaidTotal() > 0
  4292.             && $this->getOpenTotal() >= 0;
  4293.     }
  4294.     /**
  4295.      * Remember last shipping id
  4296.      *
  4297.      * @return bool
  4298.      */
  4299.     public function updateEmptyShippingID()
  4300.     {
  4301.         if (!$this->getShippingId() && $this->getProfile() && $this->getLastShippingId()) {
  4302.             $this->setShippingId($this->getLastShippingId());
  4303.             return true;
  4304.         }
  4305.         return false;
  4306.     }
  4307.     public function getSalesChannelBackground(): ?string
  4308.     {
  4309.         return '#F5F5F5';
  4310.     }
  4311.     public function hasZeroCostRate()
  4312.     {
  4313.         $currency $this->getCurrency();
  4314.         $modifier $this->getModifier(\XLite\Model\Base\Surcharge::TYPE_SHIPPING'SHIPPING');
  4315.         $rates $modifier->getRates();
  4316.         foreach ($rates as $rate) {
  4317.             if ($rate->isZeroCost($currency)) {
  4318.                 return true;
  4319.             }
  4320.         }
  4321.         return false;
  4322.     }
  4323.     public function isCurrentRateZero()
  4324.     {
  4325.         $currency $this->getCurrency();
  4326.         $modifier $this->getModifier(\XLite\Model\Base\Surcharge::TYPE_SHIPPING'SHIPPING');
  4327.         $rates $modifier->getRates();
  4328.         $currentRate $modifier->getSelectedRate() ?? reset($rates);
  4329.         if ($currentRate->isZeroCost($currency)) {
  4330.             return true;
  4331.         }
  4332.         return false;
  4333.     }
  4334.     /**
  4335.      * Show free shipping note
  4336.      *
  4337.      * @return boolean
  4338.      */
  4339.     public function showFreeShippingNote()
  4340.     {
  4341.         if (!\XLite\Core\Config::getInstance()->General->display_free_shipping_note) {
  4342.             return false;
  4343.         }
  4344.         if ($this->hasZeroCostRate() && !$this->isCurrentRateZero()) {
  4345.             return true;
  4346.         }
  4347.         return false;
  4348.     }
  4349. }