/**
  * 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();
 }
Example #5
0
 /**
  * Get tax calculation algorithm code
  *
  * @param null|string|bool|int|Store $store
  * @return string
  */
 public function getCalculationAgorithm($store = null)
 {
     return $this->_config->getAlgorithm($store);
 }