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 */ 26class OrderDetailCore extends ObjectModel 27{ 28 /** @var int */ 29 public $id_order_detail; 30 31 /** @var int */ 32 public $id_order; 33 34 /** @var int */ 35 public $id_order_invoice; 36 37 /** @var int */ 38 public $product_id; 39 40 /** @var int */ 41 public $id_shop; 42 43 /** @var int */ 44 public $product_attribute_id; 45 46 /** @var int */ 47 public $id_customization; 48 49 /** @var string */ 50 public $product_name; 51 52 /** @var int */ 53 public $product_quantity; 54 55 /** @var int */ 56 public $product_quantity_in_stock; 57 58 /** @var int */ 59 public $product_quantity_return; 60 61 /** @var int */ 62 public $product_quantity_refunded; 63 64 /** @var int */ 65 public $product_quantity_reinjected; 66 67 /** 68 * @deprecated since 1.5 Use unit_price_tax_excl instead 69 * 70 * @var float Without taxes, includes ecotax 71 */ 72 public $product_price; 73 74 /** @var float */ 75 public $original_product_price; 76 77 /** @var float With taxes, includes ecotax */ 78 public $unit_price_tax_incl; 79 80 /** @var float Without taxes, includes ecotax */ 81 public $unit_price_tax_excl; 82 83 /** @var float With taxes, includes ecotax */ 84 public $total_price_tax_incl; 85 86 /** @var float Without taxes, includes ecotax */ 87 public $total_price_tax_excl; 88 89 /** @var float */ 90 public $reduction_percent; 91 92 /** @var float */ 93 public $reduction_amount; 94 95 /** @var float */ 96 public $reduction_amount_tax_excl; 97 98 /** @var float */ 99 public $reduction_amount_tax_incl; 100 101 /** @var float */ 102 public $group_reduction; 103 104 /** @var float */ 105 public $product_quantity_discount; 106 107 /** @var string */ 108 public $product_ean13; 109 110 /** @var string */ 111 public $product_isbn; 112 113 /** @var string */ 114 public $product_upc; 115 116 /** @var string */ 117 public $product_mpn; 118 119 /** @var string */ 120 public $product_reference; 121 122 /** @var string */ 123 public $product_supplier_reference; 124 125 /** @var float */ 126 public $product_weight; 127 128 /** @var float */ 129 public $ecotax; 130 131 /** @var float */ 132 public $ecotax_tax_rate; 133 134 /** @var int */ 135 public $discount_quantity_applied; 136 137 /** @var string */ 138 public $download_hash; 139 140 /** @var int */ 141 public $download_nb; 142 143 /** @var datetime */ 144 public $download_deadline; 145 146 /** 147 * @var string @deprecated Order Detail Tax is saved in order_detail_tax table now 148 */ 149 public $tax_name; 150 151 /** 152 * @var float @deprecated Order Detail Tax is saved in order_detail_tax table now 153 */ 154 public $tax_rate; 155 156 /** @var float */ 157 public $tax_computation_method; 158 159 /** @var int Id tax rules group */ 160 public $id_tax_rules_group; 161 162 /** @var int Id warehouse */ 163 public $id_warehouse; 164 165 /** @var float additional shipping price tax excl */ 166 public $total_shipping_price_tax_excl; 167 168 /** @var float additional shipping price tax incl */ 169 public $total_shipping_price_tax_incl; 170 171 /** @var float */ 172 public $purchase_supplier_price; 173 174 /** @var float */ 175 public $original_wholesale_price; 176 177 /** @var float */ 178 public $total_refunded_tax_excl; 179 180 /** @var float */ 181 public $total_refunded_tax_incl; 182 183 /** 184 * @see ObjectModel::$definition 185 */ 186 public static $definition = [ 187 'table' => 'order_detail', 188 'primary' => 'id_order_detail', 189 'fields' => [ 190 'id_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], 191 'id_order_invoice' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 192 'id_warehouse' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], 193 'id_shop' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], 194 'product_id' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 195 'product_attribute_id' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 196 'id_customization' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 197 'product_name' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true], 198 'product_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true], 199 'product_quantity_in_stock' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 200 'product_quantity_return' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 201 'product_quantity_refunded' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 202 'product_quantity_reinjected' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 203 'product_price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true], 204 'reduction_percent' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 205 'reduction_amount' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 206 'reduction_amount_tax_incl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 207 'reduction_amount_tax_excl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 208 'group_reduction' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 209 'product_quantity_discount' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 210 'product_ean13' => ['type' => self::TYPE_STRING, 'validate' => 'isEan13'], 211 'product_isbn' => ['type' => self::TYPE_STRING, 'validate' => 'isIsbn'], 212 'product_upc' => ['type' => self::TYPE_STRING, 'validate' => 'isUpc'], 213 'product_mpn' => ['type' => self::TYPE_STRING, 'validate' => 'isMpn'], 214 'product_reference' => ['type' => self::TYPE_STRING, 'validate' => 'isReference'], 215 'product_supplier_reference' => ['type' => self::TYPE_STRING, 'validate' => 'isReference'], 216 'product_weight' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 217 'tax_name' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName'], 218 'tax_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 219 'tax_computation_method' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 220 'id_tax_rules_group' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 221 'ecotax' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 222 'ecotax_tax_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 223 'discount_quantity_applied' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 224 'download_hash' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName'], 225 'download_nb' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 226 'download_deadline' => ['type' => self::TYPE_DATE, 'validate' => 'isDateFormat'], 227 'unit_price_tax_incl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 228 'unit_price_tax_excl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 229 'total_price_tax_incl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 230 'total_price_tax_excl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 231 'total_shipping_price_tax_excl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 232 'total_shipping_price_tax_incl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 233 'purchase_supplier_price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 234 'original_product_price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 235 'original_wholesale_price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 236 'total_refunded_tax_excl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 237 'total_refunded_tax_incl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 238 ], 239 ]; 240 241 protected $webserviceParameters = [ 242 'fields' => [ 243 'id_order' => ['xlink_resource' => 'orders'], 244 'product_id' => ['xlink_resource' => 'products'], 245 'product_attribute_id' => ['xlink_resource' => 'combinations'], 246 'product_quantity_reinjected' => [], 247 'group_reduction' => [], 248 'discount_quantity_applied' => [], 249 'download_hash' => [], 250 'download_deadline' => [], 251 ], 252 'hidden_fields' => ['tax_rate', 'tax_name'], 253 'associations' => [ 254 'taxes' => ['resource' => 'tax', 'getter' => 'getWsTaxes', 'setter' => false, 255 'fields' => ['id' => []], 256 ], 257 ], 258 ]; 259 260 /** @var bool */ 261 protected $outOfStock = false; 262 263 /** @var TaxCalculator object */ 264 protected $tax_calculator = null; 265 266 /** @var Address object */ 267 protected $vat_address = null; 268 269 /** @var Address object */ 270 protected $specificPrice = null; 271 272 /** @var Customer object */ 273 protected $customer = null; 274 275 /** @var Context object */ 276 protected $context = null; 277 278 public function __construct($id = null, $id_lang = null, $context = null) 279 { 280 $this->context = $context; 281 $id_shop = null; 282 if ($this->context != null && isset($this->context->shop)) { 283 $id_shop = $this->context->shop->id; 284 } 285 parent::__construct($id, $id_lang, $id_shop); 286 287 if ($context == null) { 288 $context = Context::getContext(); 289 } 290 $this->context = $context->cloneContext(); 291 } 292 293 public function delete() 294 { 295 if (!$res = parent::delete()) { 296 return false; 297 } 298 299 Db::getInstance()->delete('order_detail_tax', 'id_order_detail=' . (int) $this->id); 300 301 return $res; 302 } 303 304 protected function setContext($id_shop) 305 { 306 if ($this->context->shop->id != $id_shop) { 307 $this->context->shop = new Shop((int) $id_shop); 308 } 309 } 310 311 public static function getDownloadFromHash($hash) 312 { 313 if ($hash == '') { 314 return false; 315 } 316 $sql = 'SELECT * 317 FROM `' . _DB_PREFIX_ . 'order_detail` od 318 LEFT JOIN `' . _DB_PREFIX_ . 'product_download` pd ON (od.`product_id`=pd.`id_product`) 319 WHERE od.`download_hash` = \'' . pSQL((string) $hash) . '\' 320 AND pd.`active` = 1'; 321 322 return Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql); 323 } 324 325 public static function incrementDownload($id_order_detail, $increment = 1) 326 { 327 $sql = 'UPDATE `' . _DB_PREFIX_ . 'order_detail` 328 SET `download_nb` = `download_nb` + ' . (int) $increment . ' 329 WHERE `id_order_detail`= ' . (int) $id_order_detail . ' 330 LIMIT 1'; 331 332 return Db::getInstance()->execute($sql); 333 } 334 335 /** 336 * Returns the tax calculator associated to this order detail. 337 * 338 * @since 1.5.0.1 339 * 340 * @return TaxCalculator 341 */ 342 public function getTaxCalculator() 343 { 344 return OrderDetail::getTaxCalculatorStatic($this->id); 345 } 346 347 /** 348 * Return the tax calculator associated to this order_detail. 349 * 350 * @since 1.5.0.1 351 * 352 * @param int $id_order_detail 353 * 354 * @return TaxCalculator 355 */ 356 public static function getTaxCalculatorStatic($id_order_detail) 357 { 358 $sql = 'SELECT t.*, d.`tax_computation_method` 359 FROM `' . _DB_PREFIX_ . 'order_detail_tax` t 360 LEFT JOIN `' . _DB_PREFIX_ . 'order_detail` d ON (d.`id_order_detail` = t.`id_order_detail`) 361 WHERE d.`id_order_detail` = ' . (int) $id_order_detail; 362 363 $computation_method = 1; 364 $taxes = []; 365 if ($results = Db::getInstance()->executeS($sql)) { 366 foreach ($results as $result) { 367 $taxes[] = new Tax((int) $result['id_tax']); 368 $computation_method = $result['tax_computation_method']; 369 } 370 } 371 372 return new TaxCalculator($taxes, $computation_method); 373 } 374 375 /** 376 * Save the tax calculator. 377 * 378 * @since 1.5.0.1 379 * @deprecated Functionality moved to Order::updateOrderDetailTax 380 * because we need the full order object to do a good job here. 381 * Will no longer be supported after 1.6.1 382 * (Note: this one is not that deprecated because Order::updateOrderDetailTax 383 * performs no update unless order_detail_tax is filled. So we rely on updateTaxAmount 384 * which correctly builds the TaxCalculator with up to date taxes unlike getTaxCalculatorStatic) 385 * 386 * @return bool 387 */ 388 public function saveTaxCalculator(Order $order, $replace = false) 389 { 390 // Nothing to save 391 if ($this->tax_calculator == null) { 392 return true; 393 } 394 395 if (!($this->tax_calculator instanceof TaxCalculator)) { 396 return false; 397 } 398 399 $shipping_tax_amount = 0; 400 401 foreach ($order->getCartRules() as $cart_rule) { 402 if ($cart_rule['free_shipping']) { 403 $shipping_tax_amount = $order->total_shipping_tax_excl; 404 405 break; 406 } 407 } 408 409 $ratio = ($order->total_products > 0) ? ($this->unit_price_tax_excl / $order->total_products) : 1; 410 411 $order_reduction_amount = ($order->total_discounts_tax_excl - $shipping_tax_amount) * $ratio; 412 $discounted_price_tax_excl = $this->unit_price_tax_excl - $order_reduction_amount; 413 414 $values = ''; 415 foreach ($this->tax_calculator->getTaxesAmount($discounted_price_tax_excl) as $id_tax => $amount) { 416 switch (Configuration::get('PS_ROUND_TYPE')) { 417 case Order::ROUND_ITEM: 418 $unit_amount = (float) Tools::ps_round($amount, Context::getContext()->getComputingPrecision()); 419 $total_amount = $unit_amount * $this->product_quantity; 420 421 break; 422 case Order::ROUND_LINE: 423 $unit_amount = $amount; 424 $total_amount = Tools::ps_round($unit_amount * $this->product_quantity, Context::getContext()->getComputingPrecision()); 425 426 break; 427 case Order::ROUND_TOTAL: 428 $unit_amount = $amount; 429 $total_amount = $unit_amount * $this->product_quantity; 430 431 break; 432 } 433 434 $values .= '(' . (int) $this->id . ',' . (int) $id_tax . ',' . (float) $unit_amount . ',' . (float) $total_amount . '),'; 435 } 436 437 if ($replace) { 438 Db::getInstance()->execute('DELETE FROM `' . _DB_PREFIX_ . 'order_detail_tax` WHERE id_order_detail=' . (int) $this->id); 439 } 440 441 if (!empty($values)) { 442 $values = rtrim($values, ','); 443 $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'order_detail_tax` (id_order_detail, id_tax, unit_amount, total_amount) 444 VALUES ' . $values; 445 446 return Db::getInstance()->execute($sql); 447 } 448 449 return true; 450 } 451 452 public function updateTaxAmount($order) 453 { 454 $address = new Address((int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}); 455 $this->tax_calculator = $this->getTaxCalculatorByAddress($address); 456 457 return $this->saveTaxCalculator($order, true); 458 } 459 460 /** 461 * Get a TaxCalculator adapted for the OrderDetail's product and the specified address 462 * 463 * @param Address $address 464 * 465 * @return TaxCalculator 466 */ 467 public function getTaxCalculatorByAddress(Address $address): TaxCalculator 468 { 469 $this->setContext((int) $this->id_shop); 470 $tax_manager = TaxManagerFactory::getManager($address, $this->getTaxRulesGroupId()); 471 472 return $tax_manager->getTaxCalculator(); 473 } 474 475 /** 476 * Dynamically get the taxRulesGroupId instead of relying one the one saved in database 477 * 478 * @return int 479 */ 480 public function getTaxRulesGroupId(): int 481 { 482 return (int) Product::getIdTaxRulesGroupByIdProduct((int) $this->product_id, $this->context); 483 } 484 485 /** 486 * Get a detailed order list of an id_order. 487 * 488 * @param int $id_order 489 * 490 * @return array 491 */ 492 public static function getList($id_order) 493 { 494 return Db::getInstance()->executeS('SELECT * FROM `' . _DB_PREFIX_ . 'order_detail` WHERE `id_order` = ' . (int) $id_order); 495 } 496 497 public function getTaxList() 498 { 499 return self::getTaxListStatic($this->id); 500 } 501 502 public static function getTaxListStatic($id_order_detail) 503 { 504 $sql = 'SELECT * FROM `' . _DB_PREFIX_ . 'order_detail_tax` 505 WHERE `id_order_detail` = ' . (int) $id_order_detail; 506 507 return Db::getInstance()->executeS($sql); 508 } 509 510 /** 511 * Set virtual product information 512 * 513 * @param array $product 514 */ 515 protected function setVirtualProductInformation($product) 516 { 517 // Add some informations for virtual products 518 $this->download_deadline = '0000-00-00 00:00:00'; 519 $this->download_hash = null; 520 521 if ($id_product_download = ProductDownload::getIdFromIdProduct((int) $product['id_product'])) { 522 $product_download = new ProductDownload((int) $id_product_download); 523 $this->download_deadline = $product_download->getDeadLine(); 524 $this->download_hash = $product_download->getHash(); 525 526 unset($product_download); 527 } 528 } 529 530 /** 531 * Check the order status. 532 * 533 * @param array $product 534 * @param int $id_order_state 535 */ 536 protected function checkProductStock($product, $id_order_state) 537 { 538 if ($id_order_state != Configuration::get('PS_OS_CANCELED') && $id_order_state != Configuration::get('PS_OS_ERROR')) { 539 $update_quantity = true; 540 if (!StockAvailable::dependsOnStock($product['id_product'])) { 541 $update_quantity = StockAvailable::updateQuantity($product['id_product'], $product['id_product_attribute'], -(int) $product['cart_quantity'], $product['id_shop'], true); 542 } 543 544 if ($update_quantity) { 545 $product['stock_quantity'] -= $product['cart_quantity']; 546 } 547 548 if ($product['stock_quantity'] < 0 && Configuration::get('PS_STOCK_MANAGEMENT')) { 549 $this->outOfStock = true; 550 } 551 Product::updateDefaultAttribute($product['id_product']); 552 } 553 } 554 555 /** 556 * Apply tax to the product. 557 * 558 * @param object $order 559 * @param array $product 560 */ 561 protected function setProductTax(Order $order, $product) 562 { 563 $this->ecotax = Tools::convertPrice((float) ($product['ecotax']), (int) ($order->id_currency)); 564 565 // Exclude VAT 566 if (!Tax::excludeTaxeOption()) { 567 $this->setContext((int) $product['id_shop']); 568 $this->id_tax_rules_group = (int) Product::getIdTaxRulesGroupByIdProduct((int) $product['id_product'], $this->context); 569 570 $tax_manager = TaxManagerFactory::getManager($this->vat_address, $this->id_tax_rules_group); 571 $this->tax_calculator = $tax_manager->getTaxCalculator(); 572 $this->tax_computation_method = (int) $this->tax_calculator->computation_method; 573 $this->tax_rate = (float) $this->tax_calculator->getTotalRate(); 574 $this->tax_name = $this->tax_calculator->getTaxesName(); 575 } 576 577 $this->ecotax_tax_rate = 0; 578 if (!empty($product['ecotax'])) { 579 $this->ecotax_tax_rate = Tax::getProductEcotaxRate($order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}); 580 } 581 } 582 583 /** 584 * Set specific price of the product. 585 * 586 * @param object $order 587 */ 588 protected function setSpecificPrice(Order $order, $product = null) 589 { 590 $this->reduction_amount = 0.00; 591 $this->reduction_percent = 0.00; 592 $this->reduction_amount_tax_incl = 0.00; 593 $this->reduction_amount_tax_excl = 0.00; 594 595 if ($this->specificPrice) { 596 switch ($this->specificPrice['reduction_type']) { 597 case 'percentage': 598 $this->reduction_percent = (float) $this->specificPrice['reduction'] * 100; 599 600 break; 601 602 case 'amount': 603 $price = Tools::convertPrice($this->specificPrice['reduction'], $order->id_currency); 604 $this->reduction_amount = !$this->specificPrice['id_currency'] ? (float) $price : (float) $this->specificPrice['reduction']; 605 if ($product !== null) { 606 $this->setContext((int) $product['id_shop']); 607 } 608 $id_tax_rules = (int) Product::getIdTaxRulesGroupByIdProduct((int) $this->specificPrice['id_product'], $this->context); 609 $tax_manager = TaxManagerFactory::getManager($this->vat_address, $id_tax_rules); 610 $this->tax_calculator = $tax_manager->getTaxCalculator(); 611 612 if ($this->specificPrice['reduction_tax']) { 613 $this->reduction_amount_tax_incl = $this->reduction_amount; 614 $this->reduction_amount_tax_excl = Tools::ps_round($this->tax_calculator->removeTaxes($this->reduction_amount), Context::getContext()->getComputingPrecision()); 615 } else { 616 $this->reduction_amount_tax_incl = Tools::ps_round($this->tax_calculator->addTaxes($this->reduction_amount), Context::getContext()->getComputingPrecision()); 617 $this->reduction_amount_tax_excl = $this->reduction_amount; 618 } 619 620 break; 621 } 622 } 623 } 624 625 /** 626 * Set detailed product price to the order detail. 627 * 628 * @param object $order 629 * @param object $cart 630 * @param array $product 631 */ 632 protected function setDetailProductPrice(Order $order, Cart $cart, $product) 633 { 634 $this->setContext((int) $product['id_shop']); 635 Product::getPriceStatic((int) $product['id_product'], true, (int) $product['id_product_attribute'], 6, null, false, true, $product['cart_quantity'], false, (int) $order->id_customer, (int) $order->id_cart, (int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}, $specific_price, true, true, $this->context); 636 $this->specificPrice = $specific_price; 637 $this->original_product_price = Product::getPriceStatic( 638 $product['id_product'], 639 false, 640 (int) $product['id_product_attribute'], 641 6, 642 null, 643 false, 644 false, 645 1, 646 false, 647 null, 648 null, 649 null, 650 $null, 651 true, 652 true, 653 $this->context 654 ); 655 $this->unit_price_tax_incl = (float) $product['price_wt']; 656 $this->product_price = $this->unit_price_tax_excl = (float) $product['price']; 657 $this->total_price_tax_incl = (float) $product['total_wt']; 658 $this->total_price_tax_excl = (float) $product['total']; 659 660 $this->purchase_supplier_price = (float) $product['wholesale_price']; 661 if ($product['id_supplier'] > 0 && ($supplier_price = ProductSupplier::getProductPrice((int) $product['id_supplier'], $product['id_product'], $product['id_product_attribute'], true)) > 0) { 662 $this->purchase_supplier_price = (float) $supplier_price; 663 } 664 665 $this->setSpecificPrice($order, $product); 666 667 $this->group_reduction = (float) Group::getReduction((int) $order->id_customer); 668 669 $shop_id = $this->context->shop->id; 670 671 $quantity_discount = SpecificPrice::getQuantityDiscount( 672 (int) $product['id_product'], 673 $shop_id, 674 (int) $cart->id_currency, 675 (int) $this->vat_address->id_country, 676 (int) $this->customer->id_default_group, 677 (int) $product['cart_quantity'], 678 false, 679 null, 680 null, 681 $null, 682 true, 683 true, 684 $this->context 685 ); 686 687 $unit_price = Product::getPriceStatic( 688 (int) $product['id_product'], 689 true, 690 ($product['id_product_attribute'] ? (int) ($product['id_product_attribute']) : null), 691 2, 692 null, 693 false, 694 true, 695 1, 696 false, 697 (int) $order->id_customer, 698 null, 699 (int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}, 700 $null, 701 true, 702 true, 703 $this->context 704 ); 705 $this->product_quantity_discount = 0.00; 706 if ($quantity_discount) { 707 $this->product_quantity_discount = $unit_price; 708 if (Product::getTaxCalculationMethod((int) $order->id_customer) == PS_TAX_EXC) { 709 $this->product_quantity_discount = Tools::ps_round($unit_price, Context::getContext()->getComputingPrecision()); 710 } 711 712 if (isset($this->tax_calculator)) { 713 $this->product_quantity_discount -= $this->tax_calculator->addTaxes($quantity_discount['price']); 714 } 715 } 716 717 $this->discount_quantity_applied = (($this->specificPrice && $this->specificPrice['from_quantity'] > 1) ? 1 : 0); 718 } 719 720 /** 721 * Create an order detail liable to an id_order. 722 * 723 * @param object $order 724 * @param object $cart 725 * @param array $product 726 * @param int $id_order_status 727 * @param int $id_order_invoice 728 * @param bool $use_taxes set to false if you don't want to use taxes 729 */ 730 protected function create(Order $order, Cart $cart, $product, $id_order_state, $id_order_invoice, $use_taxes = true, $id_warehouse = 0) 731 { 732 if ($use_taxes) { 733 $this->tax_calculator = new TaxCalculator(); 734 } 735 736 $this->id = null; 737 738 $this->product_id = (int) $product['id_product']; 739 $this->product_attribute_id = $product['id_product_attribute'] ? (int) $product['id_product_attribute'] : 0; 740 $this->id_customization = $product['id_customization'] ? (int) $product['id_customization'] : 0; 741 $this->product_name = $product['name'] . 742 ((isset($product['attributes']) && $product['attributes'] != null) ? 743 ' (' . $product['attributes'] . ')' : ''); 744 745 $this->product_quantity = (int) $product['cart_quantity']; 746 $this->product_ean13 = empty($product['ean13']) ? null : pSQL($product['ean13']); 747 $this->product_isbn = empty($product['isbn']) ? null : pSQL($product['isbn']); 748 $this->product_upc = empty($product['upc']) ? null : pSQL($product['upc']); 749 $this->product_mpn = empty($product['mpn']) ? null : pSQL($product['mpn']); 750 $this->product_reference = empty($product['reference']) ? null : pSQL($product['reference']); 751 $this->product_supplier_reference = empty($product['supplier_reference']) ? null : pSQL($product['supplier_reference']); 752 $this->product_weight = $product['id_product_attribute'] ? (float) $product['weight_attribute'] : (float) $product['weight']; 753 $this->id_warehouse = $id_warehouse; 754 755 $product_quantity = (int) Product::getQuantity($this->product_id, $this->product_attribute_id, null, $cart); 756 $this->product_quantity_in_stock = ($product_quantity - (int) $product['cart_quantity'] < 0) ? 757 $product_quantity : (int) $product['cart_quantity']; 758 759 $this->setVirtualProductInformation($product); 760 $this->checkProductStock($product, $id_order_state); 761 762 if ($use_taxes) { 763 $this->setProductTax($order, $product); 764 } 765 $this->setShippingCost($order, $product); 766 $this->setDetailProductPrice($order, $cart, $product); 767 768 // Set order invoice id 769 $this->id_order_invoice = (int) $id_order_invoice; 770 771 // Set shop id 772 $this->id_shop = (int) $product['id_shop']; 773 774 // Add new entry to the table 775 $this->save(); 776 777 if ($use_taxes) { 778 $this->saveTaxCalculator($order); 779 } 780 unset($this->tax_calculator); 781 } 782 783 /** 784 * Create a list of order detail for a specified id_order using cart. 785 * 786 * @param object $order 787 * @param object $cart 788 * @param int $id_order_status 789 * @param int $id_order_invoice 790 * @param bool $use_taxes set to false if you don't want to use taxes 791 */ 792 public function createList(Order $order, Cart $cart, $id_order_state, $product_list, $id_order_invoice = 0, $use_taxes = true, $id_warehouse = 0) 793 { 794 $this->vat_address = new Address((int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}); 795 $this->customer = new Customer((int) $order->id_customer); 796 797 $this->id_order = $order->id; 798 $this->outOfStock = false; 799 800 foreach ($product_list as $product) { 801 $this->create($order, $cart, $product, $id_order_state, $id_order_invoice, $use_taxes, $id_warehouse); 802 } 803 804 unset( 805 $this->vat_address, 806 $this->customer 807 ); 808 } 809 810 /** 811 * Get the state of the current stock product. 812 * 813 * @return array 814 */ 815 public function getStockState() 816 { 817 return $this->outOfStock; 818 } 819 820 /** 821 * Set the additional shipping information. 822 * 823 * @param Order $order 824 * @param $product 825 */ 826 public function setShippingCost(Order $order, $product) 827 { 828 $tax_rate = 0; 829 830 $carrier = OrderInvoice::getCarrier((int) $this->id_order_invoice); 831 if (isset($carrier) && Validate::isLoadedObject($carrier)) { 832 $tax_rate = $carrier->getTaxesRate(new Address((int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')})); 833 } 834 835 $this->total_shipping_price_tax_excl = (float) $product['additional_shipping_cost']; 836 $this->total_shipping_price_tax_incl = (float) ($this->total_shipping_price_tax_excl * (1 + ($tax_rate / 100))); 837 $this->total_shipping_price_tax_incl = Tools::ps_round($this->total_shipping_price_tax_incl, Context::getContext()->getComputingPrecision()); 838 } 839 840 public function getWsTaxes() 841 { 842 $query = new DbQuery(); 843 $query->select('id_tax as id'); 844 $query->from('order_detail_tax', 'tax'); 845 $query->leftJoin('order_detail', 'od', 'tax.`id_order_detail` = od.`id_order_detail`'); 846 $query->where('od.`id_order_detail` = ' . (int) $this->id_order_detail); 847 848 return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query); 849 } 850 851 public static function getCrossSells($id_product, $id_lang, $limit = 12) 852 { 853 if (!$id_product || !$id_lang) { 854 return; 855 } 856 857 $front = true; 858 if (!in_array(Context::getContext()->controller->controller_type, ['front', 'modulefront'])) { 859 $front = false; 860 } 861 862 $orders = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' 863 SELECT o.id_order 864 FROM ' . _DB_PREFIX_ . 'orders o 865 LEFT JOIN ' . _DB_PREFIX_ . 'order_detail od ON (od.id_order = o.id_order) 866 WHERE o.valid = 1 AND od.product_id = ' . (int) $id_product); 867 868 if (count($orders)) { 869 $list = ''; 870 foreach ($orders as $order) { 871 $list .= (int) $order['id_order'] . ','; 872 } 873 $list = rtrim($list, ','); 874 875 $order_products = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' 876 SELECT DISTINCT od.product_id, p.id_product, pl.name, pl.link_rewrite, p.reference, i.id_image, product_shop.show_price, 877 cl.link_rewrite category, p.ean13, p.isbn, p.out_of_stock, p.id_category_default ' . (Combination::isFeatureActive() ? ', IFNULL(product_attribute_shop.id_product_attribute,0) id_product_attribute' : '') . ' 878 FROM ' . _DB_PREFIX_ . 'order_detail od 879 LEFT JOIN ' . _DB_PREFIX_ . 'product p ON (p.id_product = od.product_id) 880 ' . Shop::addSqlAssociation('product', 'p') . 881 (Combination::isFeatureActive() ? 'LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute_shop` product_attribute_shop 882 ON (p.`id_product` = product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop=' . (int) Context::getContext()->shop->id . ')' : '') . ' 883 LEFT JOIN ' . _DB_PREFIX_ . 'product_lang pl ON (pl.id_product = od.product_id' . Shop::addSqlRestrictionOnLang('pl') . ') 884 LEFT JOIN ' . _DB_PREFIX_ . 'category_lang cl ON (cl.id_category = product_shop.id_category_default' . Shop::addSqlRestrictionOnLang('cl') . ') 885 LEFT JOIN ' . _DB_PREFIX_ . 'image i ON (i.id_product = od.product_id) 886 ' . Shop::addSqlAssociation('image', 'i', true, 'image_shop.cover=1') . ' 887 WHERE od.id_order IN (' . $list . ') 888 AND pl.id_lang = ' . (int) $id_lang . ' 889 AND cl.id_lang = ' . (int) $id_lang . ' 890 AND od.product_id != ' . (int) $id_product . ' 891 AND product_shop.active = 1' 892 . ($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '') . ' 893 ORDER BY RAND() 894 LIMIT ' . (int) $limit . ' 895 ', true, false); 896 897 $tax_calc = Product::getTaxCalculationMethod(); 898 if (is_array($order_products)) { 899 foreach ($order_products as &$order_product) { 900 $order_product['image'] = Context::getContext()->link->getImageLink( 901 $order_product['link_rewrite'], 902 (int) $order_product['product_id'] . '-' . (int) $order_product['id_image'], 903 ImageType::getFormattedName('medium') 904 ); 905 $order_product['link'] = Context::getContext()->link->getProductLink( 906 (int) $order_product['product_id'], 907 $order_product['link_rewrite'], 908 $order_product['category'], 909 $order_product['ean13'] 910 ); 911 if ($tax_calc == 0 || $tax_calc == 2) { 912 $order_product['displayed_price'] = Product::getPriceStatic((int) $order_product['product_id'], true, null); 913 } elseif ($tax_calc == 1) { 914 $order_product['displayed_price'] = Product::getPriceStatic((int) $order_product['product_id'], false, null); 915 } 916 } 917 918 return Product::getProductsProperties($id_lang, $order_products); 919 } 920 } 921 } 922 923 public function add($autodate = true, $null_values = false) 924 { 925 foreach ($this->def['fields'] as $field => $data) { 926 if (!empty($data['required']) || !empty($data['lang'])) { 927 continue; 928 } 929 if ($this->validateField($field, $this->$field) !== true) { 930 $this->$field = ''; 931 } 932 } 933 934 $this->original_wholesale_price = $this->getWholeSalePrice(); 935 936 return parent::add($autodate = true, $null_values = false); 937 } 938 939 //return the product OR product attribute whole sale price 940 public function getWholeSalePrice() 941 { 942 $product = new Product($this->product_id); 943 $wholesale_price = $product->wholesale_price; 944 945 if ($this->product_attribute_id) { 946 $combination = new Combination((int) $this->product_attribute_id); 947 if ($combination && $combination->wholesale_price != '0.000000') { 948 $wholesale_price = $combination->wholesale_price; 949 } 950 } 951 952 return $wholesale_price; 953 } 954} 955