/** * Calculate rate based on default parameter * * @param int $productTaxClassID * @param int|null $customerId * @param string|null $storeId * @param bool $isDefault * @return float */ protected function getRate($productTaxClassID, $customerId = null, $storeId = null, $isDefault = false) { if (is_null($storeId)) { $storeId = $this->storeManager->getStore()->getStoreId(); } if (!$isDefault) { $addressRequestObject = $this->calculationTool->getRateRequest(null, null, null, $storeId, $customerId); } else { $addressRequestObject = $this->calculationTool->getDefaultRateRequest($storeId, $customerId); } $addressRequestObject->setProductClassId($productTaxClassID); return $this->calculationTool->getRate($addressRequestObject); }
/** * Calculate item price and row total including/excluding tax based on total price rounding level * * @param QuoteDetailsItem $item * @param \Magento\Framework\Object $taxRateRequest * @param int $storeId * @return TaxDetailsItem */ protected function totalBaseCalculation(QuoteDetailsItem $item, \Magento\Framework\Object $taxRateRequest, $storeId) { /** @var \Magento\Tax\Service\V1\Data\TaxDetails\AppliedTax[] $appliedTaxes */ $appliedTaxes = []; $appliedTaxBuilder = $this->taxDetailsItemBuilder->getAppliedTaxBuilder(); $appliedTaxRateBuilder = $appliedTaxBuilder->getAppliedTaxRateBuilder(); $taxRateRequest->setProductClassId($item->getTaxClassId()); $appliedRates = $this->calculator->getAppliedRates($taxRateRequest); $rate = $this->calculator->getRate($taxRateRequest); $quantity = $this->getTotalQuantity($item); $price = $priceInclTax = $this->calculator->round($item->getUnitPrice()); $rowTotal = $rowTotalInclTax = $taxableAmount = $this->calcRowTotal($item); $discountAmount = $item->getDiscountAmount(); $applyTaxAfterDiscount = $this->config->applyTaxAfterDiscount($storeId); $discountTaxCompensationAmount = 0; $isTotalBasedCalculation = $this->config->getAlgorithm($storeId) == Calculation::CALC_TOTAL_BASE; if ($item->getTaxIncluded()) { if ($taxRateRequest->getSameRateAsStore()) { $rowTaxExact = $this->calculator->calcTaxAmount($rowTotalInclTax, $rate, true, false); if ($isTotalBasedCalculation) { $rowTax = $this->deltaRound($rowTaxExact, $rate, true); } else { $rowTax = $this->calculator->round($rowTaxExact); } $rowTotal = $rowTotalInclTax - $rowTax; $price = $this->calculator->round($rowTotal / $quantity); } else { $storeRate = $this->calculator->getStoreRate($taxRateRequest, $storeId); $priceInclTax = $this->calculatePriceInclTax($price, $storeRate, $rate); $rowTotalInclTax = $priceInclTax * $quantity; $taxableAmount = $rowTotalInclTax; if ($isTotalBasedCalculation) { $rowTax = $this->deltaRound($this->calculator->calcTaxAmount($rowTotalInclTax, $rate, true, false), $rate, true); } else { $rowTax = $this->calculator->calcTaxAmount($rowTotalInclTax, $rate, true, true); } $rowTotal = $rowTotalInclTax - $rowTax; $price = $this->calculator->round($rowTotal / $quantity); } //Handle discount if ($discountAmount && $applyTaxAfterDiscount) { //TODO: handle originalDiscountAmount $taxableAmount = max($taxableAmount - $discountAmount, 0); $rowTaxAfterDiscount = $this->calculator->calcTaxAmount($taxableAmount, $rate, true, false); if ($isTotalBasedCalculation) { //Round the row tax using a different type so that we don't pollute the rounding deltas $rowTaxAfterDiscount = $this->deltaRound($rowTaxAfterDiscount, $rate, true, self::KEY_TAX_AFTER_DISCOUNT_DELTA_ROUNDING); } else { $rowTaxAfterDiscount = $this->calculator->round($rowTaxAfterDiscount); } // Set discount tax compensation $discountTaxCompensationAmount = $rowTax - $rowTaxAfterDiscount; $rowTax = $rowTaxAfterDiscount; } //save applied taxes $appliedTaxes = $this->getAppliedTaxes($appliedTaxBuilder, $rowTax, $rate, $appliedRates); } else { //catalog price does not include tax $appliedRates = $this->calculator->getAppliedRates($taxRateRequest); $rowTaxes = []; $rowTaxesBeforeDiscount = []; //Apply each tax rate separately foreach ($appliedRates as $appliedRate) { $taxId = $appliedRate['id']; $taxRate = $appliedRate['percent']; if ($isTotalBasedCalculation) { $rowTaxPerRate = $this->deltaRound($this->calculator->calcTaxAmount($rowTotal, $taxRate, false, false), $taxId, false); } else { $rowTaxPerRate = $this->calculator->calcTaxAmount($rowTotal, $taxRate, false, true); } $rowTaxAfterDiscount = $rowTaxPerRate; //Handle discount if ($discountAmount && $applyTaxAfterDiscount) { //TODO: handle originalDiscountAmount $rowTaxAfterDiscount = $this->calculator->calcTaxAmount(max($rowTotal - $discountAmount, 0), $taxRate, false, false); if ($isTotalBasedCalculation) { //Round the row tax using a different type so that we don't pollute the rounding deltas $rowTaxAfterDiscount = $this->deltaRound($rowTaxAfterDiscount, $taxRate, false, self::KEY_TAX_AFTER_DISCOUNT_DELTA_ROUNDING); } else { $rowTaxAfterDiscount = $this->calculator->round($rowTaxAfterDiscount); } } $appliedTaxes[$appliedRate['id']] = $this->getAppliedTax($appliedTaxBuilder, $rowTaxAfterDiscount, $appliedRate); $rowTaxes[] = $rowTaxAfterDiscount; $rowTaxesBeforeDiscount[] = $rowTaxPerRate; } $rowTax = array_sum($rowTaxes); $rowTaxBeforeDiscount = array_sum($rowTaxesBeforeDiscount); $rowTotalInclTax = $rowTotal + $rowTaxBeforeDiscount; $priceInclTax = $this->calculator->round($rowTotalInclTax / $quantity); } $this->taxDetailsItemBuilder->setCode($item->getCode()); $this->taxDetailsItemBuilder->setRowTax($rowTax); $this->taxDetailsItemBuilder->setPrice($price); $this->taxDetailsItemBuilder->setPriceInclTax($priceInclTax); $this->taxDetailsItemBuilder->setRowTotal($rowTotal); $this->taxDetailsItemBuilder->setRowTotalInclTax($rowTotalInclTax); $this->taxDetailsItemBuilder->setCode($item->getCode()); $this->taxDetailsItemBuilder->setType($item->getType()); $this->taxDetailsItemBuilder->setTaxPercent($rate); $this->taxDetailsItemBuilder->setDiscountTaxCompensationAmount($discountTaxCompensationAmount); $this->taxDetailsItemBuilder->setAppliedTaxes($appliedTaxes); return $this->taxDetailsItemBuilder->create(); }
/** * Apply Tax Rate * * @param int $classId * @param null $shippingAddress * @param null $billingAddress * @param null $customerTaxClass * @param int|null $customerId * @return float */ protected function applyRate($classId, $shippingAddress = null, $billingAddress = null, $customerTaxClass = null, $customerId = null) { $rateRequest = $this->calculation->getRateRequest($shippingAddress, $billingAddress, $customerTaxClass, null, $customerId); $rateRequest->setProductClassId($classId); return $this->calculation->getRate($rateRequest); }
/** * Calculate item fixed tax and prepare information for discount and regular taxation * * @param \Magento\Sales\Model\Quote\Address $address * @param \Magento\Sales\Model\Quote\Item\AbstractItem $item * @return void|$this */ protected function _process(\Magento\Sales\Model\Quote\Address $address, $item) { if (!$this->_weeeData->isEnabled($this->_store)) { return $this; } $attributes = $this->_weeeData->getProductWeeeAttributes($item->getProduct(), $address, $address->getQuote()->getBillingAddress(), $this->_store->getWebsiteId()); $applied = array(); $productTaxes = array(); $defaultRateRequest = $this->_calculator->getRateOriginRequest($this->_store); $rateRequest = $this->_calculator->getRateRequest($address, $address->getQuote()->getBillingAddress(), $address->getQuote()->getCustomerTaxClassId(), $this->_store); $totalValueInclTax = 0; $baseTotalValueInclTax = 0; $totalRowValueInclTax = 0; $baseTotalRowValueInclTax = 0; $totalValueExclTax = 0; $baseTotalValueExclTax = 0; $totalRowValueExclTax = 0; $baseTotalRowValueExclTax = 0; $priceIncludesTax = $this->_taxData->priceIncludesTax($this->_store); $calculationAlgorithm = $this->_taxData->getCalculationAgorithm($this->_store); $defaultPercent = $currentPercent = 0; //when FPT is not taxable foreach ($attributes as $key => $attribute) { $title = $attribute->getName(); $baseValue = $attribute->getAmount(); $value = $this->_store->convertPrice($baseValue); $value = $this->_store->roundPrice($value); if ($this->_weeeData->isTaxable($this->_store)) { $defaultPercent = $this->_calculator->getRate($defaultRateRequest->setProductClassId($item->getProduct()->getTaxClassId())); $currentPercent = $this->_calculator->getRate($rateRequest->setProductClassId($item->getProduct()->getTaxClassId())); } if ($priceIncludesTax) { //Make sure that price including tax is rounded first $baseValueInclTax = $baseValue / (100 + $defaultPercent) * (100 + $currentPercent); $baseValueInclTax = $this->_store->roundPrice($baseValueInclTax); $valueInclTax = $value / (100 + $defaultPercent) * (100 + $currentPercent); $valueInclTax = $this->_store->roundPrice($valueInclTax); $baseValueExclTax = $baseValueInclTax / (100 + $currentPercent) * 100; $valueExclTax = $valueInclTax / (100 + $currentPercent) * 100; if ($calculationAlgorithm == Calculation::CALC_UNIT_BASE) { $baseValueExclTax = $this->_store->roundPrice($baseValueExclTax); $valueExclTax = $this->_store->roundPrice($valueExclTax); } } else { $valueExclTax = $value; $baseValueExclTax = $baseValue; $valueInclTax = $valueExclTax * (100 + $currentPercent) / 100; $baseValueInclTax = $baseValueExclTax * (100 + $currentPercent) / 100; if ($calculationAlgorithm == Calculation::CALC_UNIT_BASE) { $baseValueInclTax = $this->_store->roundPrice($baseValueInclTax); $valueInclTax = $this->_store->roundPrice($valueInclTax); } } $rowValueInclTax = $this->_store->roundPrice($valueInclTax * $item->getTotalQty()); $baseRowValueInclTax = $this->_store->roundPrice($baseValueInclTax * $item->getTotalQty()); $rowValueExclTax = $this->_store->roundPrice($valueExclTax * $item->getTotalQty()); $baseRowValueExclTax = $this->_store->roundPrice($baseValueExclTax * $item->getTotalQty()); //Now, round the unit price just in case $valueExclTax = $this->_store->roundPrice($valueExclTax); $baseValueExclTax = $this->_store->roundPrice($baseValueExclTax); $valueInclTax = $this->_store->roundPrice($valueInclTax); $baseValueInclTax = $this->_store->roundPrice($baseValueInclTax); $totalValueInclTax += $valueInclTax; $baseTotalValueInclTax += $baseValueInclTax; $totalRowValueInclTax += $rowValueInclTax; $baseTotalRowValueInclTax += $baseRowValueInclTax; $totalValueExclTax += $valueExclTax; $baseTotalValueExclTax += $baseValueExclTax; $totalRowValueExclTax += $rowValueExclTax; $baseTotalRowValueExclTax += $baseRowValueExclTax; $productTaxes[] = array('title' => $title, 'base_amount' => $baseValueExclTax, 'amount' => $valueExclTax, 'row_amount' => $rowValueExclTax, 'base_row_amount' => $baseRowValueExclTax, 'base_amount_incl_tax' => $baseValueInclTax, 'amount_incl_tax' => $valueInclTax, 'row_amount_incl_tax' => $rowValueInclTax, 'base_row_amount_incl_tax' => $baseRowValueInclTax); //This include FPT as applied tax, since tax on FPT is calculated separately, we use value excluding tax $applied[] = array('id' => $attribute->getCode(), 'percent' => null, 'hidden' => $this->_weeeData->includeInSubtotal($this->_store), 'rates' => array(array('base_real_amount' => $baseRowValueExclTax, 'base_amount' => $baseRowValueExclTax, 'amount' => $rowValueExclTax, 'code' => $attribute->getCode(), 'title' => $title, 'percent' => null, 'position' => 1, 'priority' => -1000 + $key))); } $item->setWeeeTaxAppliedAmount($totalValueExclTax)->setBaseWeeeTaxAppliedAmount($baseTotalValueExclTax)->setWeeeTaxAppliedRowAmount($totalRowValueExclTax)->setBaseWeeeTaxAppliedRowAmnt($baseTotalRowValueExclTax); $item->setWeeeTaxAppliedAmountInclTax($totalValueInclTax)->setBaseWeeeTaxAppliedAmountInclTax($baseTotalValueInclTax)->setWeeeTaxAppliedRowAmountInclTax($totalRowValueInclTax)->setBaseWeeeTaxAppliedRowAmntInclTax($baseTotalRowValueInclTax); if ($priceIncludesTax) { $this->_processTaxSettings($item, $totalValueInclTax, $baseTotalValueInclTax, $totalRowValueInclTax, $baseTotalRowValueInclTax); } else { $this->_processTaxSettings($item, $totalValueExclTax, $baseTotalValueExclTax, $totalRowValueExclTax, $baseTotalRowValueExclTax); } $this->_processTotalAmount($address, $totalRowValueExclTax, $baseTotalRowValueExclTax, $totalRowValueInclTax, $baseTotalRowValueInclTax); $this->_weeeData->setApplied($item, array_merge($this->_weeeData->getApplied($item), $productTaxes)); //Update the applied taxes for the quote if ($applied) { $this->_saveAppliedTaxes($address, $applied, $item->getWeeeTaxAppliedAmount(), $item->getBaseWeeeTaxAppliedAmount(), null); } }