public function getTaxesAmount($price_before_tax, $price_after_tax = null, $round_precision = 2, $round_mode = null) { $amounts = array(); $total_base = 0; foreach ($this->getProductTaxes() as $row) { if (!array_key_exists($row['id_tax'], $amounts)) { $amounts[$row['id_tax']] = array('rate' => $row['rate'], 'base' => 0); } $amounts[$row['id_tax']]['base'] += $row['total_price_tax_excl']; $total_base += $row['total_price_tax_excl']; } $actual_tax = 0; foreach ($amounts as &$data) { $data = Tools::ps_round($price_before_tax * ($data['base'] / $total_base) * $data['rate'] / 100, $round_precision, $round_mode); $actual_tax += $data; } unset($data); if ($price_after_tax) { Tools::spreadAmount($price_after_tax - $price_before_tax - $actual_tax, $round_precision, $amounts, 'id_tax'); } return $amounts; }
/** * Returns the wrapping taxes breakdown * * @return array */ public function getWrappingTaxesBreakdown() { if ($this->total_wrapping_tax_excl == 0) { return array(); } $wrapping_tax_amount = $this->total_wrapping_tax_incl - $this->total_wrapping_tax_excl; $wrapping_breakdown = Db::getInstance()->executeS('SELECT t.id_tax, t.rate, oit.amount as total_amount FROM `' . _DB_PREFIX_ . 'tax` t INNER JOIN `' . _DB_PREFIX_ . 'order_invoice_tax` oit ON oit.id_tax = t.id_tax WHERE oit.type = "wrapping" AND oit.id_order_invoice = ' . (int) $this->id); $sum_of_split_taxes = 0; $sum_of_tax_bases = 0; $total_tax_rate = 0; foreach ($wrapping_breakdown as &$row) { if (Configuration::get('PS_ATCP_SHIPWRAP')) { $row['total_tax_excl'] = Tools::ps_round($row['total_amount'] / $row['rate'] * 100, _PS_PRICE_COMPUTE_PRECISION_, $this->getOrder()->round_mode); $sum_of_tax_bases += $row['total_tax_excl']; } else { $row['total_tax_excl'] = $this->total_wrapping_tax_excl; } $row['total_amount'] = Tools::ps_round($row['total_amount'], _PS_PRICE_COMPUTE_PRECISION_, $this->getOrder()->round_mode); $sum_of_split_taxes += $row['total_amount']; $total_tax_rate += (double) $row['rate']; } unset($row); $delta_amount = $wrapping_tax_amount - $sum_of_split_taxes; if ($delta_amount != 0) { Tools::spreadAmount($delta_amount, _PS_PRICE_COMPUTE_PRECISION_, $wrapping_breakdown, 'total_amount'); } $delta_base = $this->total_wrapping_tax_excl - $sum_of_tax_bases; if ($delta_base != 0) { Tools::spreadAmount($delta_base, _PS_PRICE_COMPUTE_PRECISION_, $wrapping_breakdown, 'total_tax_excl'); } if (!Configuration::get('PS_INVOICE_TAXES_BREAKDOWN') && !Configuration::get('PS_ATCP_SHIPWRAP')) { $wrapping_breakdown = array(array('total_tax_excl' => $this->total_wrapping_tax_excl, 'rate' => $total_tax_rate, 'total_amount' => $wrapping_tax_amount)); } return $wrapping_breakdown; }
/** * By default this function was made for invoice, to compute tax amounts and balance delta (because of computation made on round values). * If you provide $limitToOrderDetails, only these item will be taken into account. This option is usefull for order slip for example, * where only sublist of the order is refunded. * * @param $limitToOrderDetails Optional array of OrderDetails to take into account. False by default to take all OrderDetails from the current Order. * @return array A list of tax rows applied to the given OrderDetails (or all OrderDetails linked to the current Order). */ public function getProductTaxesDetails($limitToOrderDetails = false) { $round_type = $this->round_type; if ($round_type == 0) { // if this is 0, it means the field did not exist // at the time the order was made. // Set it to old type, which was closest to line. $round_type = Order::ROUND_LINE; } // compute products discount $order_discount_tax_excl = $this->total_discounts_tax_excl; $free_shipping_tax = 0; $product_specific_discounts = array(); $expected_total_base = $this->total_products - $this->total_discounts_tax_excl; foreach ($this->getCartRules() as $order_cart_rule) { if ($order_cart_rule['free_shipping'] && $free_shipping_tax === 0) { $free_shipping_tax = $this->total_shipping_tax_incl - $this->total_shipping_tax_excl; $order_discount_tax_excl -= $this->total_shipping_tax_excl; $expected_total_base += $this->total_shipping_tax_excl; } $cart_rule = new CartRule($order_cart_rule['id_cart_rule']); if ($cart_rule->reduction_product > 0) { if (empty($product_specific_discounts[$cart_rule->reduction_product])) { $product_specific_discounts[$cart_rule->reduction_product] = 0; } $product_specific_discounts[$cart_rule->reduction_product] += $order_cart_rule['value_tax_excl']; $order_discount_tax_excl -= $order_cart_rule['value_tax_excl']; } } $products_tax = $this->total_products_wt - $this->total_products; $discounts_tax = $this->total_discounts_tax_incl - $this->total_discounts_tax_excl; // We add $free_shipping_tax because when there is free shipping, the tax that would // be paid if there wasn't is included in $discounts_tax. $expected_total_tax = $products_tax - $discounts_tax + $free_shipping_tax; $actual_total_tax = 0; $actual_total_base = 0; $order_detail_tax_rows = array(); $breakdown = array(); // Get order_details $order_details = $limitToOrderDetails ? $limitToOrderDetails : $this->getOrderDetailList(); $order_ecotax_tax = 0; $tax_rates = array(); foreach ($order_details as $order_detail) { $id_order_detail = $order_detail['id_order_detail']; $tax_calculator = OrderDetail::getTaxCalculatorStatic($id_order_detail); // TODO: probably need to make an ecotax tax breakdown here instead, // but it seems unlikely there will be different tax rates applied to the // ecotax in the same order in the real world $unit_ecotax_tax = $order_detail['ecotax'] * $order_detail['ecotax_tax_rate'] / 100.0; $order_ecotax_tax += $order_detail['product_quantity'] * $unit_ecotax_tax; $discount_ratio = 0; if ($this->total_products > 0) { $discount_ratio = ($order_detail['unit_price_tax_excl'] + $order_detail['ecotax']) / $this->total_products; } // share of global discount $discounted_price_tax_excl = $order_detail['unit_price_tax_excl'] - $discount_ratio * $order_discount_tax_excl; // specific discount if (!empty($product_specific_discounts[$order_detail['product_id']])) { $discounted_price_tax_excl -= $product_specific_discounts[$order_detail['product_id']]; } $quantity = $order_detail['product_quantity']; foreach ($tax_calculator->taxes as $tax) { $tax_rates[$tax->id] = $tax->rate; } foreach ($tax_calculator->getTaxesAmount($discounted_price_tax_excl) as $id_tax => $unit_amount) { $total_tax_base = 0; switch ($round_type) { case Order::ROUND_ITEM: $total_tax_base = $quantity * Tools::ps_round($discounted_price_tax_excl, _PS_PRICE_COMPUTE_PRECISION_, $this->round_mode); $total_amount = $quantity * Tools::ps_round($unit_amount, _PS_PRICE_COMPUTE_PRECISION_, $this->round_mode); break; case Order::ROUND_LINE: $total_tax_base = Tools::ps_round($quantity * $discounted_price_tax_excl, _PS_PRICE_COMPUTE_PRECISION_, $this->round_mode); $total_amount = Tools::ps_round($quantity * $unit_amount, _PS_PRICE_COMPUTE_PRECISION_, $this->round_mode); break; case Order::ROUND_TOTAL: $total_tax_base = $quantity * $discounted_price_tax_excl; $total_amount = $quantity * $unit_amount; break; } if (!isset($breakdown[$id_tax])) { $breakdown[$id_tax] = array('tax_base' => 0, 'tax_amount' => 0); } $breakdown[$id_tax]['tax_base'] += $total_tax_base; $breakdown[$id_tax]['tax_amount'] += $total_amount; $order_detail_tax_rows[] = array('id_order_detail' => $id_order_detail, 'id_tax' => $id_tax, 'tax_rate' => $tax_rates[$id_tax], 'unit_tax_base' => $discounted_price_tax_excl, 'total_tax_base' => $total_tax_base, 'unit_amount' => $unit_amount, 'total_amount' => $total_amount); } } if (!empty($order_detail_tax_rows)) { foreach ($breakdown as $data) { $actual_total_tax += Tools::ps_round($data['tax_amount'], _PS_PRICE_COMPUTE_PRECISION_, $this->round_mode); $actual_total_base += Tools::ps_round($data['tax_base'], _PS_PRICE_COMPUTE_PRECISION_, $this->round_mode); } $order_ecotax_tax = Tools::ps_round($order_ecotax_tax, _PS_PRICE_COMPUTE_PRECISION_, $this->round_mode); $tax_rounding_error = $expected_total_tax - $actual_total_tax - $order_ecotax_tax; if ($tax_rounding_error !== 0) { Tools::spreadAmount($tax_rounding_error, _PS_PRICE_COMPUTE_PRECISION_, $order_detail_tax_rows, 'total_amount'); } $base_rounding_error = $expected_total_base - $actual_total_base; if ($base_rounding_error !== 0) { Tools::spreadAmount($base_rounding_error, _PS_PRICE_COMPUTE_PRECISION_, $order_detail_tax_rows, 'total_tax_base'); } } return $order_detail_tax_rows; }