* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ class OrderDetailCore extends ObjectModel { /** @var int */ public $id_order_detail; /** @var int */ public $id_order; /** @var int */ public $id_order_invoice; /** @var int */ public $product_id; /** @var int */ public $id_shop; /** @var int */ public $product_attribute_id; /** @var int */ public $id_customization; /** @var string */ public $product_name; /** @var int */ public $product_quantity; /** @var int */ public $product_quantity_in_stock; /** @var int */ public $product_quantity_return; /** @var int */ public $product_quantity_refunded; /** @var int */ public $product_quantity_reinjected; /** * @deprecated since 1.5 Use unit_price_tax_excl instead * * @var float Without taxes, includes ecotax */ public $product_price; /** @var float */ public $original_product_price; /** @var float With taxes, includes ecotax */ public $unit_price_tax_incl; /** @var float Without taxes, includes ecotax */ public $unit_price_tax_excl; /** @var float With taxes, includes ecotax */ public $total_price_tax_incl; /** @var float Without taxes, includes ecotax */ public $total_price_tax_excl; /** @var float */ public $reduction_percent; /** @var float */ public $reduction_amount; /** @var float */ public $reduction_amount_tax_excl; /** @var float */ public $reduction_amount_tax_incl; /** @var float */ public $group_reduction; /** @var float */ public $product_quantity_discount; /** @var string */ public $product_ean13; /** @var string */ public $product_isbn; /** @var string */ public $product_upc; /** @var string */ public $product_mpn; /** @var string */ public $product_reference; /** @var string */ public $product_supplier_reference; /** @var float */ public $product_weight; /** @var float */ public $ecotax; /** @var float */ public $ecotax_tax_rate; /** @var int */ public $discount_quantity_applied; /** @var string|null */ public $download_hash; /** @var int */ public $download_nb; /** @var string */ public $download_deadline; /** * @var string * * @deprecated Order Detail Tax is saved in order_detail_tax table now */ public $tax_name; /** * @var float * * @deprecated Order Detail Tax is saved in order_detail_tax table now */ public $tax_rate; /** @var float */ public $tax_computation_method; /** @var int Id tax rules group */ public $id_tax_rules_group; /** @var int Id warehouse */ public $id_warehouse; /** @var float additional shipping price tax excl */ public $total_shipping_price_tax_excl; /** @var float additional shipping price tax incl */ public $total_shipping_price_tax_incl; /** @var float */ public $purchase_supplier_price; /** @var float */ public $original_wholesale_price; /** @var float */ public $total_refunded_tax_excl; /** @var float */ public $total_refunded_tax_incl; /** * @see ObjectModel::$definition */ public static $definition = [ 'table' => 'order_detail', 'primary' => 'id_order_detail', 'fields' => [ 'id_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], 'id_order_invoice' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_warehouse' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], 'id_shop' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], 'product_id' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'product_attribute_id' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_customization' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'product_name' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 4194303], 'product_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true], 'product_quantity_in_stock' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 'product_quantity_return' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 'product_quantity_refunded' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 'product_quantity_reinjected' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'], 'product_price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true], 'reduction_percent' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 'reduction_amount' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'reduction_amount_tax_incl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'reduction_amount_tax_excl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'group_reduction' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 'product_quantity_discount' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 'product_ean13' => ['type' => self::TYPE_STRING, 'validate' => 'isEan13'], 'product_isbn' => ['type' => self::TYPE_STRING, 'validate' => 'isIsbn'], 'product_upc' => ['type' => self::TYPE_STRING, 'validate' => 'isUpc'], 'product_mpn' => ['type' => self::TYPE_STRING, 'validate' => 'isMpn'], 'product_reference' => ['type' => self::TYPE_STRING, 'validate' => 'isReference'], 'product_supplier_reference' => ['type' => self::TYPE_STRING, 'validate' => 'isReference'], 'product_weight' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 'tax_name' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName'], 'tax_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 'tax_computation_method' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_tax_rules_group' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 'ecotax' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 'ecotax_tax_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat'], 'discount_quantity_applied' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 'download_hash' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName'], 'download_nb' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 'download_deadline' => ['type' => self::TYPE_DATE, 'validate' => 'isDateFormat'], 'unit_price_tax_incl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'unit_price_tax_excl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'total_price_tax_incl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'total_price_tax_excl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'total_shipping_price_tax_excl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'total_shipping_price_tax_incl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'purchase_supplier_price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'original_product_price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'original_wholesale_price' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'total_refunded_tax_excl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], 'total_refunded_tax_incl' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'], ], ]; protected $webserviceParameters = [ 'fields' => [ 'id_order' => ['xlink_resource' => 'orders'], 'product_id' => ['xlink_resource' => 'products'], 'product_attribute_id' => ['xlink_resource' => 'combinations'], 'product_quantity_reinjected' => [], 'group_reduction' => [], 'discount_quantity_applied' => [], 'download_hash' => [], 'download_deadline' => [], ], 'hidden_fields' => ['tax_rate', 'tax_name'], 'associations' => [ 'taxes' => ['resource' => 'tax', 'getter' => 'getWsTaxes', 'setter' => false, 'fields' => ['id' => []], ], ], ]; /** @var bool */ protected $outOfStock = false; /** @var TaxCalculator|null object */ protected $tax_calculator = null; /** @var Address object */ protected $vat_address = null; /** @var Address|null */ protected $specificPrice = null; /** @var Customer object */ protected $customer = null; /** @var Context object */ protected $context = null; public function __construct($id = null, $id_lang = null, $context = null) { $this->context = $context; $id_shop = null; if ($this->context != null && isset($this->context->shop)) { $id_shop = $this->context->shop->id; } parent::__construct($id, $id_lang, $id_shop); if ($context == null) { $context = Context::getContext(); } $this->context = $context->cloneContext(); } public function delete() { if (!$res = parent::delete()) { return false; } Db::getInstance()->delete('order_detail_tax', 'id_order_detail=' . (int) $this->id); return $res; } protected function setContext($id_shop) { if ($this->context->shop->id != $id_shop) { $this->context->shop = new Shop((int) $id_shop); } } public static function getDownloadFromHash($hash) { if ($hash == '') { return false; } $sql = 'SELECT * FROM `' . _DB_PREFIX_ . 'order_detail` od LEFT JOIN `' . _DB_PREFIX_ . 'product_download` pd ON (od.`product_id`=pd.`id_product`) WHERE od.`download_hash` = \'' . pSQL((string) $hash) . '\' AND pd.`active` = 1'; return Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql); } public static function incrementDownload($id_order_detail, $increment = 1) { $sql = 'UPDATE `' . _DB_PREFIX_ . 'order_detail` SET `download_nb` = `download_nb` + ' . (int) $increment . ' WHERE `id_order_detail`= ' . (int) $id_order_detail . ' LIMIT 1'; return Db::getInstance()->execute($sql); } /** * Returns the tax calculator associated to this order detail. * * @since 1.5.0.1 * * @return TaxCalculator */ public function getTaxCalculator() { return OrderDetail::getTaxCalculatorStatic($this->id); } /** * Return the tax calculator associated to this order_detail. * * @since 1.5.0.1 * * @param int $id_order_detail * * @return TaxCalculator */ public static function getTaxCalculatorStatic($id_order_detail) { $sql = 'SELECT t.*, d.`tax_computation_method` FROM `' . _DB_PREFIX_ . 'order_detail_tax` t LEFT JOIN `' . _DB_PREFIX_ . 'order_detail` d ON (d.`id_order_detail` = t.`id_order_detail`) WHERE d.`id_order_detail` = ' . (int) $id_order_detail; $computation_method = 1; $taxes = []; if ($results = Db::getInstance()->executeS($sql)) { foreach ($results as $result) { $taxes[] = new Tax((int) $result['id_tax']); $computation_method = $result['tax_computation_method']; } } return new TaxCalculator($taxes, $computation_method); } /** * Save the tax calculator. * * @since 1.5.0.1 * @deprecated Functionality moved to Order::updateOrderDetailTax * because we need the full order object to do a good job here. * Will no longer be supported after 1.6.1 * (Note: this one is not that deprecated because Order::updateOrderDetailTax * performs no update unless order_detail_tax is filled. So we rely on updateTaxAmount * which correctly builds the TaxCalculator with up to date taxes unlike getTaxCalculatorStatic) * * @return bool */ public function saveTaxCalculator(Order $order, $replace = false) { // Nothing to save if ($this->tax_calculator == null) { return true; } if (!($this->tax_calculator instanceof TaxCalculator)) { return false; } $shipping_tax_amount = 0; foreach ($order->getCartRules() as $cart_rule) { if ($cart_rule['free_shipping']) { $shipping_tax_amount = $order->total_shipping_tax_excl; break; } } $ratio = ($order->total_products > 0) ? ($this->unit_price_tax_excl / $order->total_products) : 1; $order_reduction_amount = ($order->total_discounts_tax_excl - $shipping_tax_amount) * $ratio; $discounted_price_tax_excl = $this->unit_price_tax_excl - $order_reduction_amount; $values = ''; foreach ($this->tax_calculator->getTaxesAmount($discounted_price_tax_excl) as $id_tax => $amount) { $unit_amount = $total_amount = 0; switch (Configuration::get('PS_ROUND_TYPE')) { case Order::ROUND_ITEM: $unit_amount = (float) Tools::ps_round($amount, Context::getContext()->getComputingPrecision()); $total_amount = $unit_amount * $this->product_quantity; break; case Order::ROUND_LINE: $unit_amount = $amount; $total_amount = Tools::ps_round($unit_amount * $this->product_quantity, Context::getContext()->getComputingPrecision()); break; case Order::ROUND_TOTAL: $unit_amount = $amount; $total_amount = $unit_amount * $this->product_quantity; break; } $values .= '(' . (int) $this->id . ',' . (int) $id_tax . ',' . (float) $unit_amount . ',' . (float) $total_amount . '),'; } if ($replace) { Db::getInstance()->execute('DELETE FROM `' . _DB_PREFIX_ . 'order_detail_tax` WHERE id_order_detail=' . (int) $this->id); } if (!empty($values)) { $values = rtrim($values, ','); $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'order_detail_tax` (id_order_detail, id_tax, unit_amount, total_amount) VALUES ' . $values; return Db::getInstance()->execute($sql); } return true; } public function updateTaxAmount($order) { $address = new Address((int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}); $this->tax_calculator = $this->getTaxCalculatorByAddress($address); return $this->saveTaxCalculator($order, true); } /** * Get a TaxCalculator adapted for the OrderDetail's product and the specified address * * @param Address $address * * @return TaxCalculator */ public function getTaxCalculatorByAddress(Address $address): TaxCalculator { $this->setContext((int) $this->id_shop); $tax_manager = TaxManagerFactory::getManager($address, $this->getTaxRulesGroupId()); return $tax_manager->getTaxCalculator(); } /** * Dynamically get the taxRulesGroupId instead of relying one the one saved in database * * @return int */ public function getTaxRulesGroupId(): int { return (int) Product::getIdTaxRulesGroupByIdProduct((int) $this->product_id, $this->context); } /** * Get a detailed order list of an id_order. * * @param int $id_order * * @return array */ public static function getList($id_order) { return Db::getInstance()->executeS('SELECT * FROM `' . _DB_PREFIX_ . 'order_detail` WHERE `id_order` = ' . (int) $id_order); } public function getTaxList() { return self::getTaxListStatic($this->id); } public static function getTaxListStatic($id_order_detail) { $sql = 'SELECT * FROM `' . _DB_PREFIX_ . 'order_detail_tax` WHERE `id_order_detail` = ' . (int) $id_order_detail; return Db::getInstance()->executeS($sql); } /** * Set virtual product information * * @param array $product */ protected function setVirtualProductInformation($product) { // Add some informations for virtual products $this->download_deadline = '0000-00-00 00:00:00'; $this->download_hash = null; if ($id_product_download = ProductDownload::getIdFromIdProduct((int) $product['id_product'])) { $product_download = new ProductDownload((int) $id_product_download); $this->download_deadline = $product_download->getDeadLine(); $this->download_hash = $product_download->getHash(); unset($product_download); } } /** * Updates product quantity in stock, according to order status. * * @param array $product * @param int $orderStateId */ protected function updateProductQuantityInStock($product, $orderStateId): void { $dismissOrderStateIds = Configuration::getMultiple([ 'PS_OS_CANCELED', 'PS_OS_ERROR', ]); if (in_array($orderStateId, $dismissOrderStateIds)) { return; } if (!StockAvailable::dependsOnStock($product['id_product'])) { $orderState = new OrderState($orderStateId, $this->id_lang); $isQuantityUpdated = StockAvailable::updateQuantity( $product['id_product'], $product['id_product_attribute'], -(int) $product['cart_quantity'], $product['id_shop'], // Add stock movement only if order state is flagged as shipped true === (bool) $orderState->shipped, [ 'id_order' => $this->id_order, // Only one stock movement reason fits a new order creation 'id_stock_mvt_reason' => Configuration::get('PS_STOCK_CUSTOMER_ORDER_REASON'), ] ); } else { $isQuantityUpdated = true; } if ($isQuantityUpdated === true) { $product['stock_quantity'] -= $product['cart_quantity']; } if ($product['stock_quantity'] < 0 && Configuration::get('PS_STOCK_MANAGEMENT')) { $this->outOfStock = true; } Product::updateDefaultAttribute($product['id_product']); } /** * Apply tax to the product. * * @param Order $order * @param array $product */ protected function setProductTax(Order $order, $product) { $this->ecotax = Tools::convertPrice((float) ($product['ecotax']), (int) ($order->id_currency)); // Exclude VAT if (!Tax::excludeTaxeOption()) { $this->setContext((int) $product['id_shop']); $this->id_tax_rules_group = (int) Product::getIdTaxRulesGroupByIdProduct((int) $product['id_product'], $this->context); $tax_manager = TaxManagerFactory::getManager($this->vat_address, $this->id_tax_rules_group); $this->tax_calculator = $tax_manager->getTaxCalculator(); $this->tax_computation_method = (int) $this->tax_calculator->computation_method; $this->tax_rate = (float) $this->tax_calculator->getTotalRate(); $this->tax_name = $this->tax_calculator->getTaxesName(); } $this->ecotax_tax_rate = 0; if (!empty($product['ecotax'])) { $this->ecotax_tax_rate = Tax::getProductEcotaxRate($order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}); } } /** * Set specific price of the product. * * @param Order $order * @param array|null $product */ protected function setSpecificPrice(Order $order, $product = null) { $this->reduction_amount = 0.00; $this->reduction_percent = 0.00; $this->reduction_amount_tax_incl = 0.00; $this->reduction_amount_tax_excl = 0.00; if ($this->specificPrice) { switch ($this->specificPrice['reduction_type']) { case 'percentage': $this->reduction_percent = (float) $this->specificPrice['reduction'] * 100; break; case 'amount': $price = Tools::convertPrice($this->specificPrice['reduction'], $order->id_currency); $this->reduction_amount = !$this->specificPrice['id_currency'] ? (float) $price : (float) $this->specificPrice['reduction']; if ($product !== null) { $this->setContext((int) $product['id_shop']); } $id_tax_rules = (int) Product::getIdTaxRulesGroupByIdProduct((int) $this->specificPrice['id_product'], $this->context); $tax_manager = TaxManagerFactory::getManager($this->vat_address, $id_tax_rules); $this->tax_calculator = $tax_manager->getTaxCalculator(); if ($this->specificPrice['reduction_tax']) { $this->reduction_amount_tax_incl = $this->reduction_amount; $this->reduction_amount_tax_excl = Tools::ps_round($this->tax_calculator->removeTaxes($this->reduction_amount), Context::getContext()->getComputingPrecision()); } else { $this->reduction_amount_tax_incl = Tools::ps_round($this->tax_calculator->addTaxes($this->reduction_amount), Context::getContext()->getComputingPrecision()); $this->reduction_amount_tax_excl = $this->reduction_amount; } break; } } } /** * Set detailed product price to the order detail. * * @param Order $order * @param Cart $cart * @param array $product */ protected function setDetailProductPrice(Order $order, Cart $cart, $product) { $this->setContext((int) $product['id_shop']); 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); $this->specificPrice = $specific_price; $this->original_product_price = Product::getPriceStatic( $product['id_product'], false, (int) $product['id_product_attribute'], 6, null, false, false, 1, false, null, null, null, $null, true, true, $this->context ); $this->unit_price_tax_incl = (float) $product['price_wt']; $this->product_price = $this->unit_price_tax_excl = (float) $product['price']; $this->total_price_tax_incl = (float) $product['total_wt']; $this->total_price_tax_excl = (float) $product['total']; $this->purchase_supplier_price = (float) $product['wholesale_price']; if ($product['id_supplier'] > 0 && ($supplier_price = ProductSupplier::getProductPrice((int) $product['id_supplier'], $product['id_product'], $product['id_product_attribute'], true)) > 0) { $this->purchase_supplier_price = (float) $supplier_price; } $this->setSpecificPrice($order, $product); $this->group_reduction = (float) Group::getReduction((int) $order->id_customer); $shop_id = $this->context->shop->id; $quantity_discount = SpecificPrice::getQuantityDiscount( (int) $product['id_product'], $shop_id, (int) $cart->id_currency, (int) $this->vat_address->id_country, (int) $this->customer->id_default_group, (int) $product['cart_quantity'], false, null ); $unit_price = Product::getPriceStatic( (int) $product['id_product'], true, ($product['id_product_attribute'] ? (int) ($product['id_product_attribute']) : null), 2, null, false, true, 1, false, (int) $order->id_customer, null, (int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}, $null, true, true, $this->context ); $this->product_quantity_discount = 0.00; if ($quantity_discount) { $this->product_quantity_discount = $unit_price; if (Product::getTaxCalculationMethod((int) $order->id_customer) == PS_TAX_EXC) { $this->product_quantity_discount = Tools::ps_round($unit_price, Context::getContext()->getComputingPrecision()); } if (isset($this->tax_calculator)) { $this->product_quantity_discount -= $this->tax_calculator->addTaxes($quantity_discount['price']); } } $this->discount_quantity_applied = (($this->specificPrice && $this->specificPrice['from_quantity'] > 1) ? 1 : 0); } /** * Create an order detail liable to an id_order. * * @param Order $order * @param Cart $cart * @param array $product * @param int $id_order_state * @param int $id_order_invoice * @param bool $use_taxes set to false if you don't want to use taxes * @param int $id_warehouse */ protected function create(Order $order, Cart $cart, $product, $id_order_state, $id_order_invoice, $use_taxes = true, $id_warehouse = 0) { if ($use_taxes) { $this->tax_calculator = new TaxCalculator(); } $this->id = null; $this->product_id = (int) $product['id_product']; $this->product_attribute_id = $product['id_product_attribute'] ? (int) $product['id_product_attribute'] : 0; $this->id_customization = $product['id_customization'] ? (int) $product['id_customization'] : 0; $this->product_name = $product['name'] . ((isset($product['attributes']) && $product['attributes'] != null) ? ' (' . $product['attributes'] . ')' : ''); $this->product_quantity = (int) $product['cart_quantity']; $this->product_ean13 = empty($product['ean13']) ? null : pSQL($product['ean13']); $this->product_isbn = empty($product['isbn']) ? null : pSQL($product['isbn']); $this->product_upc = empty($product['upc']) ? null : pSQL($product['upc']); $this->product_mpn = empty($product['mpn']) ? null : pSQL($product['mpn']); $this->product_reference = empty($product['reference']) ? null : pSQL($product['reference']); $this->product_supplier_reference = empty($product['supplier_reference']) ? null : pSQL($product['supplier_reference']); $this->product_weight = $product['id_product_attribute'] ? (float) $product['weight_attribute'] : (float) $product['weight']; $this->id_warehouse = $id_warehouse; // We get the real quantity of the product in stock and save how much of the ordered quantity was in stock $product_quantity_in_stock = (int) Product::getQuantity($this->product_id, $this->product_attribute_id); // Ordered 3 pcs, in stock 10 pcs, result is 3 // Ordered 3 pcs, in stock 1 pcs, result is 1 $this->product_quantity_in_stock = ($product_quantity_in_stock - (int) $product['cart_quantity'] < 0) ? $product_quantity_in_stock : (int) $product['cart_quantity']; $this->setVirtualProductInformation($product); $this->updateProductQuantityInStock($product, $id_order_state); if ($use_taxes) { $this->setProductTax($order, $product); } $this->setShippingCost($order, $product); $this->setDetailProductPrice($order, $cart, $product); // Set order invoice id $this->id_order_invoice = (int) $id_order_invoice; // Set shop id $this->id_shop = (int) $product['id_shop']; // Add new entry to the table $this->save(); if ($use_taxes) { $this->saveTaxCalculator($order); } unset($this->tax_calculator); } /** * Create a list of order detail for a specified id_order using cart. * * @param Order $order * @param Cart $cart * @param int $id_order_state * @param array $product_list * @param int $id_order_invoice * @param bool $use_taxes set to false if you don't want to use taxes * @param int $id_warehouse */ public function createList(Order $order, Cart $cart, $id_order_state, $product_list, $id_order_invoice = 0, $use_taxes = true, $id_warehouse = 0) { $this->vat_address = new Address((int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')}); $this->customer = new Customer((int) $order->id_customer); $this->id_order = $order->id; $this->outOfStock = false; foreach ($product_list as $product) { $this->create($order, $cart, $product, $id_order_state, $id_order_invoice, $use_taxes, $id_warehouse); } unset( $this->vat_address, $this->customer ); } /** * Get the state of the current stock product. * * @return bool */ public function getStockState() { return $this->outOfStock; } /** * Set the additional shipping information. * * @param Order $order * @param array $product */ public function setShippingCost(Order $order, $product) { $tax_rate = 0; $carrier = OrderInvoice::getCarrier((int) $this->id_order_invoice); if (isset($carrier) && Validate::isLoadedObject($carrier)) { $tax_rate = $carrier->getTaxesRate(new Address((int) $order->{Configuration::get('PS_TAX_ADDRESS_TYPE')})); } $this->total_shipping_price_tax_excl = (float) $product['additional_shipping_cost']; $this->total_shipping_price_tax_incl = (float) ($this->total_shipping_price_tax_excl * (1 + ($tax_rate / 100))); $this->total_shipping_price_tax_incl = Tools::ps_round($this->total_shipping_price_tax_incl, Context::getContext()->getComputingPrecision()); } public function getWsTaxes() { $query = new DbQuery(); $query->select('id_tax as id'); $query->from('order_detail_tax', 'tax'); $query->leftJoin('order_detail', 'od', 'tax.`id_order_detail` = od.`id_order_detail`'); $query->where('od.`id_order_detail` = ' . (int) $this->id_order_detail); return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query); } public static function getCrossSells($id_product, $id_lang, $limit = 12) { if (!$id_product || !$id_lang) { return; } $front = true; if (!in_array(Context::getContext()->controller->controller_type, ['front', 'modulefront'])) { $front = false; } $orders = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' SELECT o.id_order FROM ' . _DB_PREFIX_ . 'orders o LEFT JOIN ' . _DB_PREFIX_ . 'order_detail od ON (od.id_order = o.id_order) WHERE o.valid = 1 AND od.product_id = ' . (int) $id_product); if (count($orders)) { $list = ''; foreach ($orders as $order) { $list .= (int) $order['id_order'] . ','; } $list = rtrim($list, ','); $order_products = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' SELECT DISTINCT od.product_id, p.id_product, pl.name, pl.link_rewrite, p.reference, i.id_image, product_shop.show_price, 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' : '') . ' FROM ' . _DB_PREFIX_ . 'order_detail od LEFT JOIN ' . _DB_PREFIX_ . 'product p ON (p.id_product = od.product_id) ' . Shop::addSqlAssociation('product', 'p') . (Combination::isFeatureActive() ? 'LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute_shop` product_attribute_shop 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 . ')' : '') . ' LEFT JOIN ' . _DB_PREFIX_ . 'product_lang pl ON (pl.id_product = od.product_id' . Shop::addSqlRestrictionOnLang('pl') . ') LEFT JOIN ' . _DB_PREFIX_ . 'category_lang cl ON (cl.id_category = product_shop.id_category_default' . Shop::addSqlRestrictionOnLang('cl') . ') LEFT JOIN ' . _DB_PREFIX_ . 'image i ON (i.id_product = od.product_id) ' . Shop::addSqlAssociation('image', 'i', true, 'image_shop.cover=1') . ' WHERE od.id_order IN (' . $list . ') AND pl.id_lang = ' . (int) $id_lang . ' AND cl.id_lang = ' . (int) $id_lang . ' AND od.product_id != ' . (int) $id_product . ' AND product_shop.active = 1' . ($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '') . ' ORDER BY RAND() LIMIT ' . (int) $limit . ' ', true, false); $tax_calc = Product::getTaxCalculationMethod(); if (is_array($order_products)) { foreach ($order_products as &$order_product) { $order_product['image'] = Context::getContext()->link->getImageLink( $order_product['link_rewrite'], (int) $order_product['product_id'] . '-' . (int) $order_product['id_image'], ImageType::getFormattedName('medium') ); $order_product['link'] = Context::getContext()->link->getProductLink( (int) $order_product['product_id'], $order_product['link_rewrite'], $order_product['category'], $order_product['ean13'] ); if ($tax_calc == 0 || $tax_calc == 2) { $order_product['displayed_price'] = Product::getPriceStatic((int) $order_product['product_id'], true, null); } elseif ($tax_calc == 1) { $order_product['displayed_price'] = Product::getPriceStatic((int) $order_product['product_id'], false, null); } } return Product::getProductsProperties($id_lang, $order_products); } } } public function add($autodate = true, $null_values = false) { foreach ($this->def['fields'] as $field => $data) { if (!empty($data['required']) || !empty($data['lang'])) { continue; } if ($this->validateField($field, $this->$field) !== true) { $this->$field = ''; } } $this->original_wholesale_price = $this->getWholeSalePrice(); return parent::add($autodate = true, $null_values = false); } //return the product OR product attribute whole sale price public function getWholeSalePrice() { $product = new Product($this->product_id); $wholesale_price = $product->wholesale_price; if ($this->product_attribute_id) { $combination = new Combination((int) $this->product_attribute_id); if ($combination->wholesale_price != '0.000000') { $wholesale_price = $combination->wholesale_price; } } return $wholesale_price; } }