vendor/thelia/core/lib/Thelia/Model/Order.php line 27

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Thelia package.
  4.  * http://www.thelia.net
  5.  *
  6.  * (c) OpenStudio <info@thelia.net>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Thelia\Model;
  12. use Propel\Runtime\ActiveQuery\Criteria;
  13. use Propel\Runtime\Connection\ConnectionInterface;
  14. use Propel\Runtime\Propel;
  15. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  16. use Thelia\Core\Event\Payment\ManageStockOnCreationEvent;
  17. use Thelia\Core\Event\TheliaEvents;
  18. use Thelia\Exception\TheliaProcessException;
  19. use Thelia\Model\Base\Order as BaseOrder;
  20. use Thelia\Model\Map\OrderProductTableMap;
  21. use Thelia\Model\Map\OrderProductTaxTableMap;
  22. use Thelia\TaxEngine\Calculator;
  23. class Order extends BaseOrder
  24. {
  25.     /** @var int|null */
  26.     protected $choosenDeliveryAddress;
  27.     /** @var int|null */
  28.     protected $choosenInvoiceAddress;
  29.     protected $disableVersioning false;
  30.     /**
  31.      * @param int $choosenDeliveryAddress the choosen delivery address ID
  32.      *
  33.      * @return $this
  34.      */
  35.     public function setChoosenDeliveryAddress($choosenDeliveryAddress)
  36.     {
  37.         $this->choosenDeliveryAddress $choosenDeliveryAddress;
  38.         return $this;
  39.     }
  40.     /**
  41.      * @param bool $disableVersioning
  42.      *
  43.      * @return $this
  44.      */
  45.     public function setDisableVersioning($disableVersioning)
  46.     {
  47.         $this->disableVersioning = (bool) $disableVersioning;
  48.         return $this;
  49.     }
  50.     public function isVersioningDisable()
  51.     {
  52.         return $this->disableVersioning;
  53.     }
  54.     public function isVersioningNecessary($con null)
  55.     {
  56.         if ($this->isVersioningDisable()) {
  57.             return false;
  58.         }
  59.         return parent::isVersioningNecessary($con);
  60.     }
  61.     /**
  62.      * @return int|null the choosen delivery address ID
  63.      */
  64.     public function getChoosenDeliveryAddress()
  65.     {
  66.         return $this->choosenDeliveryAddress;
  67.     }
  68.     /**
  69.      * @param int $choosenInvoiceAddress the choosen invoice address
  70.      *
  71.      * @return $this
  72.      */
  73.     public function setChoosenInvoiceAddress($choosenInvoiceAddress)
  74.     {
  75.         $this->choosenInvoiceAddress $choosenInvoiceAddress;
  76.         return $this;
  77.     }
  78.     /**
  79.      * @return int|null the choosen invoice address ID
  80.      */
  81.     public function getChoosenInvoiceAddress()
  82.     {
  83.         return $this->choosenInvoiceAddress;
  84.     }
  85.     /**
  86.      * {@inheritDoc}
  87.      *
  88.      * @throws \Propel\Runtime\Exception\PropelException
  89.      */
  90.     public function preSave(ConnectionInterface $con null)
  91.     {
  92.         if ($this->isPaid(false) && null === $this->getInvoiceDate()) {
  93.             $this
  94.                 ->setInvoiceDate(time());
  95.         }
  96.         return parent::preSave($con);
  97.     }
  98.     /**
  99.      * @throws \Propel\Runtime\Exception\PropelException
  100.      */
  101.     public function postInsert(ConnectionInterface $con null): void
  102.     {
  103.         parent::postInsert($con);
  104.         $this->setRef($this->generateRef())
  105.             ->setDisableVersioning(true)
  106.             ->save($con);
  107.     }
  108.     public function generateRef()
  109.     {
  110.         return sprintf('ORD%s'str_pad($this->getId(), 120\STR_PAD_LEFT));
  111.     }
  112.     /**
  113.      * Compute this order amount with taxes. The tax amount is returned in the $tax parameter.
  114.      *
  115.      * The order amount is only available once the order is persisted in database.
  116.      * During invoice process, use all cart methods instead of order methods (the order doest not exists at this moment)
  117.      *
  118.      * @param float|int $tax             (output only) returns the tax amount for this order
  119.      * @param bool      $includePostage  if true, the postage cost is included to the total
  120.      * @param bool      $includeDiscount if true, the discount will be included to the total
  121.      *
  122.      * @return float
  123.      *
  124.      * @throws \Propel\Runtime\Exception\PropelException
  125.      */
  126.     public function getTotalAmount(&$tax 0$includePostage true$includeDiscount true)
  127.     {
  128.         // To prevent price changes in pre-2.4 orders, use the legacy calculation method
  129.         if ($this->getId() <= ConfigQuery::read('last_legacy_rounding_order_id'0)) {
  130.             return $this->getTotalAmountLegacy($tax$includePostage$includeDiscount);
  131.         }
  132.         // Cache the query result. Wa have to une and array indexed on the order ID, as the cache ios static
  133.         // and may cache results for several orders, for example in the order list in the back-office.
  134.         static $queryResult = [];
  135.         $id $this->getId();
  136.         if (!isset($queryResult[$id]) || null === $queryResult[$id]) {
  137.             // Shoud be the same rounding method as in CartItem::getTotalTaxedPrice()
  138.             // For each order line, we round quantity x taxed price.
  139.             $query '
  140.                 SELECT
  141.                     SUM(
  142.                         '.OrderProductTableMap::COL_QUANTITY.'
  143.                         *
  144.                         ROUND(
  145.                             (
  146.                                 IF('.OrderProductTableMap::COL_WAS_IN_PROMO.'=1, '.OrderProductTableMap::COL_PROMO_PRICE.', '.OrderProductTableMap::COL_PRICE.')
  147.                                 +
  148.                                 (
  149.                                     SELECT COALESCE(SUM(IF('.OrderProductTableMap::COL_WAS_IN_PROMO.'=1, '.OrderProductTaxTableMap::COL_PROMO_AMOUNT.', '.OrderProductTaxTableMap::COL_AMOUNT.')), 0)
  150.                                     FROM '.OrderProductTaxTableMap::TABLE_NAME.'
  151.                                     WHERE '.OrderProductTaxTableMap::COL_ORDER_PRODUCT_ID.' = '.OrderProductTableMap::COL_ID.'
  152.                                 )
  153.                             ), 2
  154.                         )
  155.                     ) as total_taxed_price,
  156.                     SUM(
  157.                         '.OrderProductTableMap::COL_QUANTITY.'
  158.                         *
  159.                         ROUND(
  160.                             IF(
  161.                                 '.OrderProductTableMap::COL_WAS_IN_PROMO.'=1,
  162.                                 '.OrderProductTableMap::COL_PROMO_PRICE.',
  163.                                 '.OrderProductTableMap::COL_PRICE.'
  164.                             ), 2
  165.                         )
  166.                     ) as total_untaxed_price
  167.                 from
  168.                     '.OrderProductTableMap::TABLE_NAME.'
  169.                 where
  170.                     '.OrderProductTableMap::COL_ORDER_ID.'=:order_id
  171.             ';
  172.             $con Propel::getConnection();
  173.             $stmt $con->prepare($query);
  174.             if (false === $stmt->execute([':order_id' => $this->getId()])) {
  175.                 throw new TheliaProcessException(
  176.                     sprintf(
  177.                         'Failed to get order total and order tax: %s (%s)',
  178.                         $stmt->errorInfo(),
  179.                         $stmt->errorCode()
  180.                     )
  181.                 );
  182.             }
  183.             $queryResult[$id] = $stmt->fetch(\PDO::FETCH_OBJ);
  184.         }
  185.         $total = (float) $queryResult[$id]->total_taxed_price;
  186.         $tax $total - (float) $queryResult[$id]->total_untaxed_price;
  187.         if (true === $includeDiscount) {
  188.             $total -= $this->getDiscount();
  189.             $tax -= $this->getDiscount() - Calculator::getUntaxedOrderDiscount($this);
  190.             if ($total 0) {
  191.                 $total 0;
  192.             }
  193.             if ($tax 0) {
  194.                 $tax 0;
  195.             }
  196.         }
  197.         if (false !== $includePostage) {
  198.             $total += (float) $this->getPostage();
  199.             $tax += (float) $this->getPostageTax();
  200.         }
  201.         return $total;
  202.     }
  203.     /**
  204.      * This is thge legacy way of computing this order amount with taxes. The tax amount is returned in the $tax parameter.
  205.      *
  206.      * The order amount is only available once the order is persisted in database.
  207.      * During invoice process, use all cart methods instead of order methods (the order doest not exists at this moment)
  208.      *
  209.      * @param float|int $tax             (output only) returns the tax amount for this order
  210.      * @param bool      $includePostage  if true, the postage cost is included to the total
  211.      * @param bool      $includeDiscount if true, the discount will be included to the total
  212.      *
  213.      * @return float
  214.      *
  215.      * @throws \Propel\Runtime\Exception\PropelException
  216.      */
  217.     public function getTotalAmountLegacy(&$tax 0$includePostage true$includeDiscount true)
  218.     {
  219.         $amount = (float) OrderProductQuery::create()
  220.             ->filterByOrderId($this->getId())
  221.             ->withColumn('SUM(
  222.                     '.OrderProductTableMap::COL_QUANTITY.'
  223.                     * IF('.OrderProductTableMap::COL_WAS_IN_PROMO.' = 1, '.OrderProductTableMap::COL_PROMO_PRICE.', '.OrderProductTableMap::COL_PRICE.')
  224.                 )''total_amount')
  225.             ->select(['total_amount'])
  226.             ->findOne();
  227.         $tax = (float) OrderProductTaxQuery::create()
  228.             ->useOrderProductQuery()
  229.             ->filterByOrderId($this->getId())
  230.             ->endUse()
  231.             ->withColumn('SUM(
  232.                     '.OrderProductTableMap::COL_QUANTITY.'
  233.                     * IF('.OrderProductTableMap::COL_WAS_IN_PROMO.' = 1, '.OrderProductTaxTableMap::COL_PROMO_AMOUNT.', '.OrderProductTaxTableMap::COL_AMOUNT.')
  234.                 )''total_tax')
  235.             ->select(['total_tax'])
  236.             ->findOne();
  237.         $total $amount $tax;
  238.         // @todo : manage discount : free postage ?
  239.         if (true === $includeDiscount) {
  240.             $total -= $this->getDiscount();
  241.             if ($total 0) {
  242.                 $total 0;
  243.             }
  244.         }
  245.         if (false !== $includePostage) {
  246.             $total += (float) ($this->getPostage());
  247.             $tax += (float) ($this->getPostageTax());
  248.         }
  249.         return $total;
  250.     }
  251.     /**
  252.      * Compute this order weight.
  253.      *
  254.      * The order weight is only available once the order is persisted in database.
  255.      * During invoice process, use all cart methods instead of order methods (the order doest not exists at this moment)
  256.      *
  257.      * @return float
  258.      *
  259.      * @throws \Propel\Runtime\Exception\PropelException
  260.      */
  261.     public function getWeight()
  262.     {
  263.         $weight 0;
  264.         /* browse all products */
  265.         foreach ($this->getOrderProducts() as $orderProduct) {
  266.             $weight += $orderProduct->getQuantity() * (float) $orderProduct->getWeight();
  267.         }
  268.         return $weight;
  269.     }
  270.     /**
  271.      * Return the postage without tax.
  272.      *
  273.      * @return float|int
  274.      */
  275.     public function getUntaxedPostage()
  276.     {
  277.         if ($this->getPostageTax()) {
  278.             $untaxedPostage $this->getPostage() - $this->getPostageTax();
  279.         } else {
  280.             $untaxedPostage $this->getPostage();
  281.         }
  282.         return $untaxedPostage;
  283.     }
  284.     /**
  285.      * Check if the current order contains at less 1 virtual product with a file to download.
  286.      *
  287.      * @return bool true if this order have at less 1 file to download, false otherwise
  288.      */
  289.     public function hasVirtualProduct()
  290.     {
  291.         $virtualProductCount OrderProductQuery::create()
  292.             ->filterByOrderId($this->getId())
  293.             ->filterByVirtual(1Criteria::EQUAL)
  294.             ->count()
  295.         ;
  296.         return $virtualProductCount !== 0;
  297.     }
  298.     /**
  299.      * Set the status of the current order to NOT PAID.
  300.      *
  301.      * @throws \Propel\Runtime\Exception\PropelException
  302.      */
  303.     public function setNotPaid(): void
  304.     {
  305.         $this->setStatusHelper(OrderStatus::CODE_NOT_PAID);
  306.     }
  307.     /**
  308.      * Check if the current status of this order is NOT PAID.
  309.      *
  310.      * @param bool $exact if true, the status should be the exact required status, not a derived one
  311.      *
  312.      * @return bool true if this order is NOT PAID, false otherwise
  313.      *
  314.      * @throws \Propel\Runtime\Exception\PropelException
  315.      */
  316.     public function isNotPaid($exact true)
  317.     {
  318.         return $this->getOrderStatus()->isNotPaid($exact);
  319.     }
  320.     /**
  321.      * Set the status of the current order to PAID.
  322.      *
  323.      * @throws \Propel\Runtime\Exception\PropelException
  324.      */
  325.     public function setPaid(): void
  326.     {
  327.         $this->setStatusHelper(OrderStatus::CODE_PAID);
  328.     }
  329.     /**
  330.      * Check if the current status of this order is PAID.
  331.      *
  332.      * @param bool $exact if true, the status should be the exact required status, not a derived one
  333.      *
  334.      * @return bool true if this order is PAID, false otherwise
  335.      *
  336.      * @throws \Propel\Runtime\Exception\PropelException
  337.      */
  338.     public function isPaid($exact true)
  339.     {
  340.         return $this->getOrderStatus()->isPaid($exact);
  341.     }
  342.     /**
  343.      * Set the status of the current order to PROCESSING.
  344.      *
  345.      * @throws \Propel\Runtime\Exception\PropelException
  346.      */
  347.     public function setProcessing(): void
  348.     {
  349.         $this->setStatusHelper(OrderStatus::CODE_PROCESSING);
  350.     }
  351.     /**
  352.      * Check if the current status of this order is PROCESSING.
  353.      *
  354.      * @param bool $exact if true, the status should be the exact required status, not a derived one
  355.      *
  356.      * @return bool true if this order is PROCESSING, false otherwise
  357.      *
  358.      * @throws \Propel\Runtime\Exception\PropelException
  359.      */
  360.     public function isProcessing($exact true)
  361.     {
  362.         return $this->getOrderStatus()->isProcessing($exact);
  363.     }
  364.     /**
  365.      * Set the status of the current order to SENT.
  366.      *
  367.      * @throws \Propel\Runtime\Exception\PropelException
  368.      */
  369.     public function setSent(): void
  370.     {
  371.         $this->setStatusHelper(OrderStatus::CODE_SENT);
  372.     }
  373.     /**
  374.      * Check if the current status of this order is SENT.
  375.      *
  376.      * @param bool $exact if true, the status should be the exact required status, not a derived one
  377.      *
  378.      * @return bool true if this order is SENT, false otherwise
  379.      *
  380.      * @throws \Propel\Runtime\Exception\PropelException
  381.      */
  382.     public function isSent($exact true)
  383.     {
  384.         return $this->getOrderStatus()->isSent($exact);
  385.     }
  386.     /**
  387.      * Set the status of the current order to CANCELED.
  388.      *
  389.      * @throws \Propel\Runtime\Exception\PropelException
  390.      */
  391.     public function setCancelled(): void
  392.     {
  393.         $this->setStatusHelper(OrderStatus::CODE_CANCELED);
  394.     }
  395.     /**
  396.      * Check if the current status of this order is CANCELED.
  397.      *
  398.      * @param bool $exact if true, the status should be the exact required status, not a derived one
  399.      *
  400.      * @return bool true if this order is CANCELED, false otherwise
  401.      *
  402.      * @throws \Propel\Runtime\Exception\PropelException
  403.      */
  404.     public function isCancelled($exact true)
  405.     {
  406.         return $this->getOrderStatus()->isCancelled($exact);
  407.     }
  408.     /**
  409.      * Set the status of the current order to REFUNDED.
  410.      *
  411.      * @throws \Propel\Runtime\Exception\PropelException
  412.      */
  413.     public function setRefunded(): void
  414.     {
  415.         $this->setStatusHelper(OrderStatus::CODE_REFUNDED);
  416.     }
  417.     /**
  418.      * Check if the current status of this order is REFUNDED.
  419.      *
  420.      * @param bool $exact if true, the status should be the exact required status, not a derived one
  421.      *
  422.      * @return bool true if this order is REFUNDED, false otherwise
  423.      *
  424.      * @throws \Propel\Runtime\Exception\PropelException
  425.      */
  426.     public function isRefunded($exact true)
  427.     {
  428.         return $this->getOrderStatus()->isRefunded($exact);
  429.     }
  430.     /**
  431.      * Set the status of the current order to the provided status.
  432.      *
  433.      * @param string $statusCode the status code, one of OrderStatus::CODE_xxx constants
  434.      *
  435.      * @throws \Propel\Runtime\Exception\PropelException
  436.      */
  437.     public function setStatusHelper($statusCode): void
  438.     {
  439.         if (null !== $ordeStatus OrderStatusQuery::create()->findOneByCode($statusCode)) {
  440.             $this->setOrderStatus($ordeStatus)->save();
  441.         }
  442.     }
  443.     /**
  444.      * Get an instance of the payment module.
  445.      *
  446.      * @return \Thelia\Module\PaymentModuleInterface
  447.      *
  448.      * @throws TheliaProcessException
  449.      */
  450.     public function getPaymentModuleInstance()
  451.     {
  452.         if (null === $paymentModule ModuleQuery::create()->findPk($this->getPaymentModuleId())) {
  453.             throw new TheliaProcessException('Payment module ID='.$this->getPaymentModuleId().' was not found.');
  454.         }
  455.         return $paymentModule->createInstance();
  456.     }
  457.     /**
  458.      * Get an instance of the delivery module.
  459.      *
  460.      * @return \Thelia\Module\BaseModuleInterface
  461.      *
  462.      * @throws TheliaProcessException
  463.      */
  464.     public function getDeliveryModuleInstance()
  465.     {
  466.         if (null === $deliveryModule ModuleQuery::create()->findPk($this->getDeliveryModuleId())) {
  467.             throw new TheliaProcessException('Delivery module ID='.$this->getDeliveryModuleId().' was not found.');
  468.         }
  469.         return $deliveryModule->createInstance();
  470.     }
  471.     /**
  472.      * Check if stock was decreased at stock creation for this order.
  473.      * TODO : we definitely have to store modules in an order_modules table juste like order_product and other order related information.
  474.      *
  475.      * @return bool true if the stock was decreased at order creation, false otherwise
  476.      */
  477.     public function isStockManagedOnOrderCreation(EventDispatcherInterface $dispatcher)
  478.     {
  479.         $paymentModule $this->getPaymentModuleInstance();
  480.         $event = new ManageStockOnCreationEvent($paymentModule);
  481.         $dispatcher->dispatch(
  482.             $event,
  483.             TheliaEvents::getModuleEvent(
  484.                 TheliaEvents::MODULE_PAYMENT_MANAGE_STOCK,
  485.                 $paymentModule->getCode()
  486.             )
  487.         );
  488.         return (null !== $event->getManageStock())
  489.             ? $event->getManageStock()
  490.             : $paymentModule->manageStockOnCreation();
  491.     }
  492. }