/** * Check if tax calculation type and price display settings are compatible * * Invalid settings if * Tax Calculation Method Based On 'Total' or 'Row' * and at least one Price Display Settings has 'Including and Excluding Tax' value * * @param null|int|bool|string|\Magento\Store\Model\Store $store $store * @return bool */ public function checkDisplaySettings($store = null) { if ($this->taxConfig->getAlgorithm($store) == \Magento\Tax\Model\Calculation::CALC_UNIT_BASE) { return true; } return $this->taxConfig->getPriceDisplayType($store) != \Magento\Tax\Model\Config::DISPLAY_TYPE_BOTH && $this->taxConfig->getShippingPriceDisplayType($store) != \Magento\Tax\Model\Config::DISPLAY_TYPE_BOTH && !$this->taxConfig->displayCartPricesBoth($store) && !$this->taxConfig->displayCartSubtotalBoth($store) && !$this->taxConfig->displayCartShippingBoth($store) && !$this->taxConfig->displaySalesPricesBoth($store) && !$this->taxConfig->displaySalesSubtotalBoth($store) && !$this->taxConfig->displaySalesShippingBoth($store); }
/** * {@inheritdoc} */ public function calculateTax(\Magento\Tax\Api\Data\QuoteDetailsInterface $quoteDetails, $storeId = null, $round = true) { if ($storeId === null) { $storeId = $this->storeManager->getStore()->getStoreId(); } // initial TaxDetails data $taxDetailsData = [TaxDetailsInterface::KEY_SUBTOTAL => 0.0, TaxDetailsInterface::KEY_TAX_AMOUNT => 0.0, TaxDetailsInterface::KEY_DISCOUNT_TAX_COMPENSATION_AMOUNT => 0.0, TaxDetailsInterface::KEY_APPLIED_TAXES => [], TaxDetailsInterface::KEY_ITEMS => []]; $items = $quoteDetails->getItems(); if (empty($items)) { return $this->taxDetailsDataObjectFactory->create()->setSubtotal(0.0)->setTaxAmount(0.0)->setDiscountTaxCompensationAmount(0.0)->setAppliedTaxes([])->setItems([]); } $this->computeRelationships($items); $calculator = $this->calculatorFactory->create($this->config->getAlgorithm($storeId), $storeId, $quoteDetails->getBillingAddress(), $quoteDetails->getShippingAddress(), $this->taxClassManagement->getTaxClassId($quoteDetails->getCustomerTaxClassKey(), 'customer'), $quoteDetails->getCustomerId()); $processedItems = []; /** @var QuoteDetailsItemInterface $item */ foreach ($this->keyedItems as $item) { if (isset($this->parentToChildren[$item->getCode()])) { $processedChildren = []; foreach ($this->parentToChildren[$item->getCode()] as $child) { $processedItem = $this->processItem($child, $calculator, $round); $taxDetailsData = $this->aggregateItemData($taxDetailsData, $processedItem); $processedItems[$processedItem->getCode()] = $processedItem; $processedChildren[] = $processedItem; } $processedItem = $this->calculateParent($processedChildren, $item->getQuantity()); $processedItem->setCode($item->getCode()); $processedItem->setType($item->getType()); } else { $processedItem = $this->processItem($item, $calculator, $round); $taxDetailsData = $this->aggregateItemData($taxDetailsData, $processedItem); } $processedItems[$processedItem->getCode()] = $processedItem; } $taxDetailsDataObject = $this->taxDetailsDataObjectFactory->create(); $this->dataObjectHelper->populateWithArray($taxDetailsDataObject, $taxDetailsData, '\\Magento\\Tax\\Api\\Data\\TaxDetailsInterface'); $taxDetailsDataObject->setItems($processedItems); return $taxDetailsDataObject; }
/** * {@inheritdoc} */ public function calculateTax(QuoteDetails $quoteDetails, $storeId = null) { if (is_null($storeId)) { $storeId = $this->storeManager->getStore()->getStoreId(); } // initial TaxDetails data $taxDetailsData = [TaxDetails::KEY_SUBTOTAL => 0.0, TaxDetails::KEY_TAX_AMOUNT => 0.0, TaxDetails::KEY_DISCOUNT_TAX_COMPENSATION_AMOUNT => 0.0, TaxDetails::KEY_APPLIED_TAXES => [], TaxDetails::KEY_ITEMS => []]; $items = $quoteDetails->getItems(); if (empty($items)) { return $this->taxDetailsBuilder->populateWithArray($taxDetailsData)->create(); } $this->computeRelationships($items); $calculator = $this->calculatorFactory->create($this->config->getAlgorithm($storeId), $storeId, $quoteDetails->getBillingAddress(), $quoteDetails->getShippingAddress(), $this->taxClassService->getTaxClassId($quoteDetails->getCustomerTaxClassKey(), 'customer'), $quoteDetails->getCustomerId()); $processedItems = []; /** @var QuoteDetailsItem $item */ foreach ($this->keyedItems as $item) { if (isset($this->parentToChildren[$item->getCode()])) { $processedChildren = []; foreach ($this->parentToChildren[$item->getCode()] as $child) { $processedItem = $this->processItem($child, $calculator); $taxDetailsData = $this->aggregateItemData($taxDetailsData, $processedItem); $processedItems[$processedItem->getCode()] = $processedItem; $processedChildren[] = $processedItem; } $processedItemBuilder = $this->calculateParent($processedChildren, $item->getQuantity()); $processedItemBuilder->setCode($item->getCode()); $processedItemBuilder->setType($item->getType()); $processedItem = $processedItemBuilder->create(); } else { $processedItem = $this->processItem($item, $calculator); $taxDetailsData = $this->aggregateItemData($taxDetailsData, $processedItem); } $processedItems[$processedItem->getCode()] = $processedItem; } return $this->taxDetailsBuilder->populateWithArray($taxDetailsData)->setItems($processedItems)->create(); }
/** * 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(); }
/** * Get tax calculation algorithm code * * @param null|string|bool|int|Store $store * @return string */ public function getCalculationAgorithm($store = null) { return $this->_config->getAlgorithm($store); }