1<?php 2/** 3 * Copyright since 2007 PrestaShop SA and Contributors 4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA 5 * 6 * NOTICE OF LICENSE 7 * 8 * This source file is subject to the Open Software License (OSL 3.0) 9 * that is bundled with this package in the file LICENSE.md. 10 * It is also available through the world-wide-web at this URL: 11 * https://opensource.org/licenses/OSL-3.0 12 * If you did not receive a copy of the license and are unable to 13 * obtain it through the world-wide-web, please send an email 14 * to license@prestashop.com so we can send you a copy immediately. 15 * 16 * DISCLAIMER 17 * 18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 19 * versions in the future. If you wish to customize PrestaShop for your 20 * needs please refer to https://devdocs.prestashop.com/ for more information. 21 * 22 * @author PrestaShop SA and Contributors <contact@prestashop.com> 23 * @copyright Since 2007 PrestaShop SA and Contributors 24 * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) 25 */ 26 27declare(strict_types=1); 28 29namespace PrestaShop\PrestaShop\Adapter\Order; 30 31use Address; 32use Cache; 33use Carrier; 34use Cart; 35use CartRule; 36use Currency; 37use Customer; 38use Order; 39use OrderCarrier; 40use OrderCartRule; 41use OrderDetail; 42use PrestaShop\Decimal\Number; 43use PrestaShop\PrestaShop\Adapter\Cart\Comparator\CartProductsComparator; 44use PrestaShop\PrestaShop\Adapter\Cart\Comparator\CartProductUpdate; 45use PrestaShop\PrestaShop\Adapter\ContextStateManager; 46use PrestaShop\PrestaShop\Adapter\Order\Refund\OrderProductRemover; 47use PrestaShop\PrestaShop\Core\Cart\CartRuleData; 48use PrestaShop\PrestaShop\Core\Domain\Configuration\ShopConfigurationInterface; 49use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderException; 50use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopConstraint; 51use PrestaShop\PrestaShop\Core\Localization\CLDR\ComputingPrecision; 52use PrestaShopDatabaseException; 53use PrestaShopException; 54use Product; 55use Shop; 56use Tools; 57use Validate; 58 59class OrderAmountUpdater 60{ 61 /** 62 * @var ShopConfigurationInterface 63 */ 64 private $shopConfiguration; 65 66 /** 67 * @var ContextStateManager 68 */ 69 private $contextStateManager; 70 71 /** 72 * @var OrderDetailUpdater 73 */ 74 private $orderDetailUpdater; 75 76 /** 77 * @var OrderProductRemover 78 */ 79 private $orderProductRemover; 80 81 /** 82 * @var array 83 */ 84 private $orderConstraints = []; 85 86 /** 87 * @var bool 88 */ 89 private $keepOrderPrices = true; 90 91 /** 92 * @param ShopConfigurationInterface $shopConfiguration 93 * @param ContextStateManager $contextStateManager 94 * @param OrderDetailUpdater $orderDetailUpdater 95 * @param OrderProductRemover $orderProductRemover 96 */ 97 public function __construct( 98 ShopConfigurationInterface $shopConfiguration, 99 ContextStateManager $contextStateManager, 100 OrderDetailUpdater $orderDetailUpdater, 101 OrderProductRemover $orderProductRemover 102 ) { 103 $this->shopConfiguration = $shopConfiguration; 104 $this->contextStateManager = $contextStateManager; 105 $this->orderDetailUpdater = $orderDetailUpdater; 106 $this->orderProductRemover = $orderProductRemover; 107 } 108 109 /** 110 * @param Order $order 111 * @param Cart $cart 112 * @param int|null $orderInvoiceId 113 * 114 * @throws OrderException 115 * @throws PrestaShopDatabaseException 116 * @throws PrestaShopException 117 */ 118 public function update( 119 Order $order, 120 Cart $cart, 121 ?int $orderInvoiceId = null 122 ): void { 123 $this->cleanCaches(); 124 125 $this->contextStateManager 126 ->saveCurrentContext() 127 ->setCart($cart) 128 ->setCurrency(new Currency($cart->id_currency)) 129 ->setCustomer(new Customer($cart->id_customer)) 130 ->setLanguage($cart->getAssociatedLanguage()) 131 ->setCountry($cart->getTaxCountry()) 132 ->setShop(new Shop($cart->id_shop)) 133 ; 134 135 try { 136 // @todo: use https://github.com/PrestaShop/decimal for price computations 137 $computingPrecision = $this->getPrecisionFromCart($cart); 138 139 // Update order details (if quantity or product price have been modified) 140 $this->updateOrderDetails($order, $cart); 141 142 $productsComparator = new CartProductsComparator($cart); 143 // Recalculate cart rules and Fix differences between cart's cartRules and order's cartRules 144 $this->updateOrderCartRules($order, $cart, $computingPrecision, $orderInvoiceId); 145 146 // Synchronize modified products with the order 147 $modifiedProducts = $productsComparator->getModifiedProducts(); 148 $this->updateOrderModifiedProducts($modifiedProducts, $cart, $order); 149 150 // Update order totals 151 $this->updateOrderTotals($order, $cart, $computingPrecision); 152 153 // Update carrier weight for shipping cost 154 $this->updateOrderCarrier($order, $cart); 155 156 // Order::update is called after previous functions so that we only call it once 157 if (!$order->update()) { 158 throw new OrderException('Could not update order invoice in database.'); 159 } 160 161 $this->updateOrderInvoices($order, $cart, $computingPrecision); 162 } finally { 163 $this->contextStateManager->restorePreviousContext(); 164 } 165 } 166 167 /** 168 * Synchronizes modified products from the cart with the order 169 * 170 * @param CartProductUpdate[] $modifiedProducts 171 * @param Cart $cart 172 * @param Order $order 173 * 174 * @throws OrderException 175 * @throws PrestaShopDatabaseException 176 * @throws PrestaShopException 177 */ 178 private function updateOrderModifiedProducts(array $modifiedProducts, Cart $cart, Order $order): void 179 { 180 $productsToAddToOrder = []; 181 foreach ($modifiedProducts as $modifiedProduct) { 182 $orderProduct = $this->findProductInOrder($modifiedProduct, $order); 183 $cartProduct = $this->findProductInCart($modifiedProduct, $cart); 184 if (null === $cartProduct) { 185 // The product is not in the cart anymore: delete it from the order 186 $orderDetail = new OrderDetail($orderProduct['id_order_detail']); 187 $this->orderProductRemover->deleteProductFromOrder($order, $orderDetail, false); 188 } elseif (null === $orderProduct) { 189 // The product is not in the order but in the cart: add it to the order 190 $productsToAddToOrder[] = $cartProduct; 191 } else { 192 // the product is both in the cart and the order: update its quantity and price with the cart values 193 $orderDetail = new OrderDetail($orderProduct['id_order_detail']); 194 $orderDetail->product_quantity = $cartProduct['cart_quantity']; 195 $this->orderDetailUpdater->updateOrderDetail( 196 $orderDetail, 197 $order, 198 new Number((string) $cartProduct['price_with_reduction_without_tax']), 199 new Number((string) $cartProduct['price_with_reduction']) 200 ); 201 } 202 } 203 if (count($productsToAddToOrder) > 0) { 204 $orderDetail = new OrderDetail(); 205 $orderDetail->createList($order, $cart, $order->getCurrentState(), $productsToAddToOrder); 206 } 207 } 208 209 /** 210 * @param CartProductUpdate $productUpdate 211 * @param Cart $cart 212 * 213 * @return array|null 214 */ 215 private function findProductInCart(CartProductUpdate $productUpdate, Cart $cart): ?array 216 { 217 $combinationId = null === $productUpdate->getCombinationId() 218 ? 0 219 : $productUpdate->getCombinationId()->getValue(); 220 foreach ($cart->getProducts() as $product) { 221 if ((int) $product['id_product'] === $productUpdate->getProductId()->getValue() 222 && (int) $product['id_product_attribute'] === $combinationId) { 223 return $product; 224 } 225 } 226 227 return null; 228 } 229 230 /** 231 * @param CartProductUpdate $productUpdate 232 * @param Order $order 233 * 234 * @return array|null 235 */ 236 private function findProductInOrder(CartProductUpdate $productUpdate, Order $order): ?array 237 { 238 $combinationId = null === $productUpdate->getCombinationId() 239 ? 0 240 : $productUpdate->getCombinationId()->getValue(); 241 foreach ($order->getProducts() as $product) { 242 if ((int) $product['product_id'] === $productUpdate->getProductId()->getValue() 243 && (int) $product['product_attribute_id'] === $combinationId) { 244 return $product; 245 } 246 } 247 248 return null; 249 } 250 251 /** 252 * There are many caches among legacy classes that can store previous prices 253 * we need to clean them to make sure the price is completely up to date 254 */ 255 private function cleanCaches(): void 256 { 257 // For many intermediate computations 258 Cart::resetStaticCache(); 259 260 // For discount computation 261 CartRule::resetStaticCache(); 262 Cache::clean('getContextualValue_*'); 263 264 // For shipping costs 265 Carrier::resetStaticCache(); 266 Cache::clean('getPackageShippingCost_*'); 267 } 268 269 /** 270 * @param Order $order 271 * @param Cart $cart 272 * @param int $computingPrecision 273 */ 274 private function updateOrderTotals(Order $order, Cart $cart, int $computingPrecision): void 275 { 276 $orderProducts = $order->getCartProducts(); 277 278 $carrierId = $order->id_carrier; 279 $order->total_discounts = (float) abs($cart->getOrderTotal(true, Cart::ONLY_DISCOUNTS, $orderProducts, $carrierId, false, $this->keepOrderPrices)); 280 $order->total_discounts_tax_excl = (float) abs($cart->getOrderTotal(false, Cart::ONLY_DISCOUNTS, $orderProducts, $carrierId, false, $this->keepOrderPrices)); 281 $order->total_discounts_tax_incl = (float) abs($cart->getOrderTotal(true, Cart::ONLY_DISCOUNTS, $orderProducts, $carrierId, false, $this->keepOrderPrices)); 282 283 // We should always use Cart::BOTH for the order total since it contains all products, shipping fees and cart rules 284 $order->total_paid = Tools::ps_round( 285 (float) $cart->getOrderTotal(true, Cart::BOTH, $orderProducts, $carrierId, false, $this->keepOrderPrices), 286 $computingPrecision 287 ); 288 $order->total_paid_tax_excl = Tools::ps_round( 289 (float) $cart->getOrderTotal(false, Cart::BOTH, $orderProducts, $carrierId, false, $this->keepOrderPrices), 290 $computingPrecision 291 ); 292 $order->total_paid_tax_incl = Tools::ps_round( 293 (float) $cart->getOrderTotal(true, Cart::BOTH, $orderProducts, $carrierId, false, $this->keepOrderPrices), 294 $computingPrecision 295 ); 296 297 $order->total_products = (float) $cart->getOrderTotal(false, Cart::ONLY_PRODUCTS, $orderProducts, $carrierId, false, $this->keepOrderPrices); 298 $order->total_products_wt = (float) $cart->getOrderTotal(true, Cart::ONLY_PRODUCTS, $orderProducts, $carrierId, false, $this->keepOrderPrices); 299 300 $order->total_wrapping = abs($cart->getOrderTotal(true, Cart::ONLY_WRAPPING, $orderProducts, $carrierId, false, $this->keepOrderPrices)); 301 $order->total_wrapping_tax_excl = abs($cart->getOrderTotal(false, Cart::ONLY_WRAPPING, $orderProducts, $carrierId, false, $this->keepOrderPrices)); 302 $order->total_wrapping_tax_incl = abs($cart->getOrderTotal(true, Cart::ONLY_WRAPPING, $orderProducts, $carrierId, false, $this->keepOrderPrices)); 303 304 $totalShippingTaxIncluded = $order->total_shipping_tax_incl; 305 $totalShippingTaxExcluded = $order->total_shipping_tax_excl; 306 307 $order->total_shipping = $cart->getOrderTotal(true, Cart::ONLY_SHIPPING, $orderProducts, $carrierId, false, $this->keepOrderPrices); 308 $order->total_shipping_tax_excl = $cart->getOrderTotal(false, Cart::ONLY_SHIPPING, $orderProducts, $carrierId, false, $this->keepOrderPrices); 309 $order->total_shipping_tax_incl = $cart->getOrderTotal(true, Cart::ONLY_SHIPPING, $orderProducts, $carrierId, false, $this->keepOrderPrices); 310 311 if (!$this->getOrderConfiguration('PS_ORDER_RECALCULATE_SHIPPING', $order)) { 312 $freeShipping = $this->isFreeShipping($order); 313 314 if ($freeShipping) { 315 $order->total_discounts = $order->total_discounts - $order->total_shipping_tax_incl + $totalShippingTaxIncluded; 316 $order->total_discounts_tax_excl = $order->total_discounts_tax_excl - $order->total_shipping_tax_excl + $totalShippingTaxExcluded; 317 $order->total_discounts_tax_incl = $order->total_discounts_tax_incl - $order->total_shipping_tax_incl + $totalShippingTaxIncluded; 318 } else { 319 $order->total_paid -= ($order->total_shipping_tax_incl - $totalShippingTaxIncluded); 320 $order->total_paid_tax_incl -= ($order->total_shipping_tax_incl - $totalShippingTaxIncluded); 321 $order->total_paid_tax_excl -= ($order->total_shipping_tax_excl - $totalShippingTaxExcluded); 322 } 323 324 $order->total_shipping = $totalShippingTaxIncluded; 325 $order->total_shipping_tax_incl = $totalShippingTaxIncluded; 326 $order->total_shipping_tax_excl = $totalShippingTaxExcluded; 327 } 328 } 329 330 /** 331 * @param Order $order 332 * @param Cart $cart 333 * 334 * @throws PrestaShopDatabaseException 335 * @throws PrestaShopException 336 */ 337 private function updateOrderCarrier(Order $order, Cart $cart): void 338 { 339 $orderCarrier = new OrderCarrier((int) $order->getIdOrderCarrier()); 340 341 if (Validate::isLoadedObject($orderCarrier)) { 342 $orderCarrier->weight = (float) $order->getTotalWeight(); 343 $orderCarrier->shipping_cost_tax_incl = (float) $order->total_shipping_tax_incl; 344 $orderCarrier->shipping_cost_tax_excl = (float) $order->total_shipping_tax_excl; 345 346 if ($orderCarrier->update()) { 347 $order->weight = sprintf('%.3f ' . $this->getOrderConfiguration('PS_WEIGHT_UNIT', $order), $orderCarrier->weight); 348 } 349 } 350 351 if (!$cart->isVirtualCart() && isset($order->id_carrier)) { 352 $carrier = new Carrier((int) $order->id_carrier, (int) $cart->id_lang); 353 if (null !== $carrier && Validate::isLoadedObject($carrier)) { 354 $taxAddressId = (int) $order->{$this->getOrderConfiguration('PS_TAX_ADDRESS_TYPE', $order)}; 355 $order->carrier_tax_rate = $carrier->getTaxesRate(new Address($taxAddressId)); 356 } 357 } 358 } 359 360 /** 361 * @param Order $order 362 * @param Cart $cart 363 * 364 * @throws OrderException 365 * @throws PrestaShopDatabaseException 366 * @throws PrestaShopException 367 */ 368 private function updateOrderDetails(Order $order, Cart $cart): void 369 { 370 // Get cart products with prices kept from order 371 $cartProducts = $cart->getProducts(true, false, null, true, $this->keepOrderPrices); 372 foreach ($order->getCartProducts() as $orderProduct) { 373 $orderDetail = new OrderDetail($orderProduct['id_order_detail'], null, $this->contextStateManager->getContext()); 374 $cartProduct = $this->getProductFromCart($cartProducts, (int) $orderDetail->product_id, (int) $orderDetail->product_attribute_id); 375 376 $this->orderDetailUpdater->updateOrderDetail( 377 $orderDetail, 378 $order, 379 new Number((string) $cartProduct['price_with_reduction_without_tax']), 380 new Number((string) $cartProduct['price_with_reduction']) 381 ); 382 } 383 } 384 385 /** 386 * @param array $cartProducts 387 * @param int $productId 388 * @param int $productAttributeId 389 * 390 * @return array 391 */ 392 private function getProductFromCart(array $cartProducts, int $productId, int $productAttributeId): array 393 { 394 $cartProduct = array_reduce($cartProducts, function ($carry, $item) use ($productId, $productAttributeId) { 395 if (null !== $carry) { 396 return $carry; 397 } 398 399 $productMatch = $item['id_product'] == $productId; 400 $combinationMatch = $item['id_product_attribute'] == $productAttributeId; 401 402 return $productMatch && $combinationMatch ? $item : null; 403 }); 404 405 // This shouldn't happen, if it does something was not done before updating the Order (removing an OrderDetail maybe) 406 if (null === $cartProduct) { 407 throw new OrderException('Could not find the product in cart, meaning Order and Cart are out of sync'); 408 } 409 410 return $cartProduct; 411 } 412 413 /** 414 * Update cart rules to be synced with current cart: 415 * - cart rules attached to new product may be added/removed 416 * - global shop cart rules may be added/removed 417 * - cart rules amount may vary because other cart rules have been added/removed 418 * 419 * @param Order $order 420 * @param Cart $cart 421 * @param int $computingPrecision 422 * @param int|null $orderInvoiceId 423 * 424 * @throws OrderException 425 * @throws PrestaShopDatabaseException 426 * @throws PrestaShopException 427 */ 428 private function updateOrderCartRules( 429 Order $order, 430 Cart $cart, 431 int $computingPrecision, 432 ?int $orderInvoiceId 433 ): void { 434 CartRule::autoAddToCart(null, true); 435 CartRule::autoRemoveFromCart(null, true); 436 $carrierId = $order->id_carrier; 437 438 $newCartRules = $cart->getCartRules(CartRule::FILTER_ACTION_ALL, false); 439 // We need the calculator to compute the discount on the whole products because they can interact with each 440 // other so they can't be computed independently, it needs to keep order prices 441 $calculator = $cart->newCalculator($cart->getProducts(), $newCartRules, $carrierId, $computingPrecision, $this->keepOrderPrices); 442 $calculator->processCalculation(); 443 444 foreach ($order->getCartRules() as $orderCartRuleData) { 445 /** @var CartRuleData $cartRuleData */ 446 foreach ($calculator->getCartRulesData() as $cartRuleData) { 447 $cartRule = $cartRuleData->getCartRule(); 448 if ($cartRule->id == $orderCartRuleData['id_cart_rule']) { 449 // Cart rule is still in the cart no need to remove it, but we update it as the amount may have changed 450 $orderCartRule = new OrderCartRule($orderCartRuleData['id_order_cart_rule']); 451 $orderCartRule->id_order = $order->id; 452 $orderCartRule->name = $cartRule->name; 453 $orderCartRule->free_shipping = $cartRule->free_shipping; 454 $orderCartRule->value = Tools::ps_round($cartRuleData->getDiscountApplied()->getTaxIncluded(), $computingPrecision); 455 $orderCartRule->value_tax_excl = Tools::ps_round($cartRuleData->getDiscountApplied()->getTaxExcluded(), $computingPrecision); 456 457 if ($orderCartRule->free_shipping && !$this->getOrderConfiguration('PS_ORDER_RECALCULATE_SHIPPING', $order)) { 458 $orderCartRule->value = $orderCartRule->value - $calculator->getFees()->getInitialShippingFees()->getTaxIncluded() + $order->total_shipping; 459 $orderCartRule->value_tax_excl = $orderCartRule->value_tax_excl - $calculator->getFees()->getInitialShippingFees()->getTaxExcluded() + $order->total_shipping_tax_excl; 460 } 461 462 $orderCartRule->save(); 463 continue 2; 464 } 465 } 466 467 // This one is no longer in the new cart rules so we delete it 468 $orderCartRule = new OrderCartRule($orderCartRuleData['id_order_cart_rule']); 469 // This one really needs to be deleted because it doesn't match the applied cart rules any more 470 // we don't use soft deleted here (unlike in the handler) but hard delete 471 if (!$orderCartRule->delete()) { 472 throw new OrderException('Could not delete order cart rule from database.'); 473 } 474 } 475 476 // Finally add the new cart rules that are not in the Order 477 foreach ($calculator->getCartRulesData() as $cartRuleData) { 478 $cartRule = $cartRuleData->getCartRule(); 479 foreach ($order->getCartRules() as $orderCartRuleData) { 480 if ($cartRule->id == $orderCartRuleData['id_cart_rule']) { 481 // This cart rule is already present no need to add it 482 continue 2; 483 } 484 } 485 486 // Add missing order cart rule 487 $orderCartRule = new OrderCartRule(); 488 $orderCartRule->id_order = $order->id; 489 $orderCartRule->id_cart_rule = $cartRule->id; 490 $orderCartRule->id_order_invoice = $orderInvoiceId ?? 0; 491 $orderCartRule->name = $cartRule->name; 492 $orderCartRule->free_shipping = $cartRule->free_shipping; 493 $orderCartRule->value = Tools::ps_round($cartRuleData->getDiscountApplied()->getTaxIncluded(), $computingPrecision); 494 $orderCartRule->value_tax_excl = Tools::ps_round($cartRuleData->getDiscountApplied()->getTaxExcluded(), $computingPrecision); 495 $orderCartRule->save(); 496 } 497 } 498 499 /** 500 * @param Order $order 501 * @param Cart $cart 502 * @param int $computingPrecision 503 * 504 * @throws OrderException 505 * @throws PrestaShopDatabaseException 506 * @throws PrestaShopException 507 */ 508 private function updateOrderInvoices(Order $order, Cart $cart, int $computingPrecision): void 509 { 510 $invoiceProducts = []; 511 foreach ($order->getCartProducts() as $orderProduct) { 512 if (!empty($orderProduct['id_order_invoice'])) { 513 $invoiceProducts[$orderProduct['id_order_invoice']][] = $orderProduct; 514 } 515 } 516 517 $invoiceCollection = $order->getInvoicesCollection(); 518 $firstInvoice = $invoiceCollection->getFirst(); 519 520 foreach ($invoiceCollection as $invoice) { 521 // If all the invoice's products have been removed the offset won't exist 522 $currentInvoiceProducts = isset($invoiceProducts[$invoice->id]) ? $invoiceProducts[$invoice->id] : []; 523 524 // Shipping are computed on first invoice only 525 $carrierId = $order->id_carrier; 526 $totalMethod = ($firstInvoice === false || $firstInvoice->id == $invoice->id) ? Cart::BOTH : Cart::BOTH_WITHOUT_SHIPPING; 527 $invoice->total_paid_tax_excl = Tools::ps_round( 528 (float) $cart->getOrderTotal(false, $totalMethod, $currentInvoiceProducts, $carrierId, false, $this->keepOrderPrices), 529 $computingPrecision 530 ); 531 $invoice->total_paid_tax_incl = Tools::ps_round( 532 (float) $cart->getOrderTotal(true, $totalMethod, $currentInvoiceProducts, $carrierId, false, $this->keepOrderPrices), 533 $computingPrecision 534 ); 535 536 $invoice->total_products = Tools::ps_round( 537 (float) $cart->getOrderTotal(false, Cart::ONLY_PRODUCTS, $currentInvoiceProducts, $carrierId, false, $this->keepOrderPrices), 538 $computingPrecision 539 ); 540 $invoice->total_products_wt = Tools::ps_round( 541 (float) $cart->getOrderTotal(true, Cart::ONLY_PRODUCTS, $currentInvoiceProducts, $carrierId, false, $this->keepOrderPrices), 542 $computingPrecision 543 ); 544 545 $invoice->total_discount_tax_excl = Tools::ps_round( 546 (float) $cart->getOrderTotal(false, Cart::ONLY_DISCOUNTS, $currentInvoiceProducts, $carrierId, false, $this->keepOrderPrices), 547 $computingPrecision 548 ); 549 550 $invoice->total_discount_tax_incl = Tools::ps_round( 551 (float) $cart->getOrderTotal(true, Cart::ONLY_DISCOUNTS, $currentInvoiceProducts, $carrierId, false, $this->keepOrderPrices), 552 $computingPrecision 553 ); 554 555 $totalShippingTaxIncluded = $invoice->total_shipping_tax_incl; 556 $totalShippingTaxExcluded = $invoice->total_shipping_tax_excl; 557 558 $invoice->total_shipping = $cart->getOrderTotal(true, Cart::ONLY_SHIPPING, $currentInvoiceProducts, $carrierId, false, $this->keepOrderPrices); 559 $invoice->total_shipping_tax_excl = $cart->getOrderTotal(false, Cart::ONLY_SHIPPING, $currentInvoiceProducts, $carrierId, false, $this->keepOrderPrices); 560 $invoice->total_shipping_tax_incl = $cart->getOrderTotal(true, Cart::ONLY_SHIPPING, $currentInvoiceProducts, $carrierId, false, $this->keepOrderPrices); 561 562 if (!$this->getOrderConfiguration('PS_ORDER_RECALCULATE_SHIPPING', $order)) { 563 $freeShipping = $this->isFreeShipping($order); 564 565 if ($freeShipping) { 566 $invoice->total_discount_tax_excl = $invoice->total_discount_tax_excl - $invoice->total_shipping_tax_excl + $totalShippingTaxExcluded; 567 $invoice->total_discount_tax_incl = $invoice->total_discount_tax_incl - $invoice->total_shipping_tax_incl + $totalShippingTaxIncluded; 568 } 569 570 $invoice->total_shipping_tax_incl = $totalShippingTaxIncluded; 571 $invoice->total_shipping_tax_excl = $totalShippingTaxExcluded; 572 573 if (!$freeShipping) { 574 $invoice->total_paid_tax_incl -= ($invoice->total_shipping_tax_incl - $totalShippingTaxIncluded); 575 $invoice->total_paid_tax_excl -= ($invoice->total_shipping_tax_excl - $totalShippingTaxExcluded); 576 } 577 } 578 579 if (!$invoice->update()) { 580 throw new OrderException('Could not update order invoice in database.'); 581 } 582 } 583 } 584 585 /** 586 * @param Order $order 587 * 588 * @return bool 589 */ 590 protected function isFreeShipping(Order $order): bool 591 { 592 foreach ($order->getCartRules() as $cartRule) { 593 if ($cartRule['free_shipping']) { 594 return true; 595 } 596 } 597 598 return false; 599 } 600 601 /** 602 * @param Cart $cart 603 * 604 * @return int 605 */ 606 private function getPrecisionFromCart(Cart $cart): int 607 { 608 $computingPrecision = new ComputingPrecision(); 609 $currency = new Currency((int) $cart->id_currency); 610 611 return $computingPrecision->getPrecision((int) $currency->precision); 612 } 613 614 /** 615 * @param string $key 616 * @param Order $order 617 * 618 * @return mixed 619 */ 620 private function getOrderConfiguration(string $key, Order $order) 621 { 622 return $this->shopConfiguration->get($key, null, $this->getOrderShopConstraint($order)); 623 } 624 625 /** 626 * @param Order $order 627 * 628 * @return ShopConstraint 629 */ 630 private function getOrderShopConstraint(Order $order): ShopConstraint 631 { 632 $constraintKey = $order->id_shop . '-' . $order->id_shop_group; 633 if (!isset($this->orderConstraints[$constraintKey])) { 634 $this->orderConstraints[$constraintKey] = ShopConstraint::shop((int) $order->id_shop); 635 } 636 637 return $this->orderConstraints[$constraintKey]; 638 } 639} 640