classes/XLite/Model/Order.php line 82

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