![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/magento/module-tax/Model/Sales/Total/Quote/ |
<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Tax\Model\Sales\Total\Quote; use Magento\Customer\Api\AccountManagementInterface as CustomerAccountManagement; use Magento\Customer\Api\Data\AddressInterfaceFactory as CustomerAddressFactory; use Magento\Customer\Api\Data\AddressInterface as CustomerAddress; use Magento\Customer\Api\Data\RegionInterfaceFactory as CustomerAddressRegionFactory; use Magento\Framework\DataObject; use Magento\Quote\Model\Quote\Address as QuoteAddress; use Magento\Quote\Model\Quote\Address\Total\AbstractTotal; use Magento\Quote\Model\Quote\Item\AbstractItem; use Magento\Store\Model\Store; use Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory; use Magento\Tax\Api\Data\QuoteDetailsItemInterface; use Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory; use Magento\Tax\Api\Data\TaxClassKeyInterface; use Magento\Tax\Api\Data\TaxDetailsInterface; use Magento\Tax\Api\Data\TaxDetailsItemInterface; use Magento\Tax\Api\Data\QuoteDetailsInterface; use Magento\Quote\Api\Data\ShippingAssignmentInterface; use Magento\Tax\Helper\Data as TaxHelper; use Magento\Framework\App\ObjectManager; use Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterface; use Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterfaceFactory; /** * Tax totals calculation model * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CommonTaxCollector extends AbstractTotal { /**#@+ * Constants defined for type of items */ public const ITEM_TYPE_SHIPPING = 'shipping'; public const ITEM_TYPE_PRODUCT = 'product'; /**#@-*/ /** * Constant for shipping item code */ public const ITEM_CODE_SHIPPING = 'shipping'; /**#@+ * Constants for array keys */ public const KEY_ITEM = 'item'; public const KEY_BASE_ITEM = 'base_item'; /**#@-*/ /**#@+ * Constants for fields in associated taxables array */ public const KEY_ASSOCIATED_TAXABLE_TYPE = 'type'; public const KEY_ASSOCIATED_TAXABLE_CODE = 'code'; public const KEY_ASSOCIATED_TAXABLE_UNIT_PRICE = 'unit_price'; public const KEY_ASSOCIATED_TAXABLE_BASE_UNIT_PRICE = 'base_unit_price'; public const KEY_ASSOCIATED_TAXABLE_QUANTITY = 'quantity'; public const KEY_ASSOCIATED_TAXABLE_TAX_CLASS_ID = 'tax_class_id'; public const KEY_ASSOCIATED_TAXABLE_PRICE_INCLUDES_TAX = 'price_includes_tax'; public const KEY_ASSOCIATED_TAXABLE_ASSOCIATION_ITEM_CODE = 'associated_item_code'; /**#@-*/ /** * When an extra taxable item is associated with quote and not with an item, this value * is used as associated item code */ public const ASSOCIATION_ITEM_CODE_FOR_QUOTE = 'quote'; /**#@+ * Constants for fields in tax details for associated taxable items */ public const KEY_TAX_DETAILS_TYPE = 'type'; public const KEY_TAX_DETAILS_CODE = 'code'; public const KEY_TAX_DETAILS_PRICE_EXCL_TAX = 'price_excl_tax'; public const KEY_TAX_DETAILS_BASE_PRICE_EXCL_TAX = 'base_price_excl_tax'; public const KEY_TAX_DETAILS_PRICE_INCL_TAX = 'price_incl_tax'; public const KEY_TAX_DETAILS_BASE_PRICE_INCL_TAX = 'base_price_incl_tax'; public const KEY_TAX_DETAILS_ROW_TOTAL = 'row_total_excl_tax'; public const KEY_TAX_DETAILS_BASE_ROW_TOTAL = 'base_row_total_excl_tax'; public const KEY_TAX_DETAILS_ROW_TOTAL_INCL_TAX = 'row_total_incl_tax'; public const KEY_TAX_DETAILS_BASE_ROW_TOTAL_INCL_TAX = 'base_row_total_incl_tax'; public const KEY_TAX_DETAILS_TAX_PERCENT = 'tax_percent'; public const KEY_TAX_DETAILS_ROW_TAX = 'row_tax'; public const KEY_TAX_DETAILS_BASE_ROW_TAX = 'base_row_tax'; public const KEY_TAX_DETAILS_APPLIED_TAXES = 'applied_taxes'; /**#@-*/ /** * @var \Magento\Tax\Model\Config */ protected $_config; /** * Counter that is used to construct temporary ids for taxable items * * @var int */ protected $counter = 0; /** * Tax calculation service, the collector will call the service which performs the actual calculation * * @var \Magento\Tax\Api\TaxCalculationInterface */ protected $taxCalculationService; /** * Factory to create QuoteDetails as input to tax calculation service * * @var \Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory */ protected $quoteDetailsDataObjectFactory; /** * @var CustomerAddressFactory */ protected $customerAddressFactory; /** * @var CustomerAddressRegionFactory */ protected $customerAddressRegionFactory; /** * @var \Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory */ protected $taxClassKeyDataObjectFactory; /** * @var \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory */ protected $quoteDetailsItemDataObjectFactory; /** * @var TaxHelper */ private $taxHelper; /** * @var QuoteDetailsItemExtensionInterfaceFactory */ private $quoteDetailsItemExtensionFactory; /** * @var CustomerAccountManagement */ private $customerAccountManagement; /** * Class constructor * * @param \Magento\Tax\Model\Config $taxConfig * @param \Magento\Tax\Api\TaxCalculationInterface $taxCalculationService * @param QuoteDetailsInterfaceFactory $quoteDetailsDataObjectFactory * @param \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory * @param \Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory * @param CustomerAddressFactory $customerAddressFactory * @param CustomerAddressRegionFactory $customerAddressRegionFactory * @param TaxHelper|null $taxHelper * @param QuoteDetailsItemExtensionInterfaceFactory|null $quoteDetailsItemExtensionInterfaceFactory * @param CustomerAccountManagement|null $customerAccountManagement * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Tax\Model\Config $taxConfig, \Magento\Tax\Api\TaxCalculationInterface $taxCalculationService, \Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory $quoteDetailsDataObjectFactory, \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory, \Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory, CustomerAddressFactory $customerAddressFactory, CustomerAddressRegionFactory $customerAddressRegionFactory, TaxHelper $taxHelper = null, QuoteDetailsItemExtensionInterfaceFactory $quoteDetailsItemExtensionInterfaceFactory = null, ?CustomerAccountManagement $customerAccountManagement = null ) { $this->taxCalculationService = $taxCalculationService; $this->quoteDetailsDataObjectFactory = $quoteDetailsDataObjectFactory; $this->_config = $taxConfig; $this->taxClassKeyDataObjectFactory = $taxClassKeyDataObjectFactory; $this->quoteDetailsItemDataObjectFactory = $quoteDetailsItemDataObjectFactory; $this->customerAddressFactory = $customerAddressFactory; $this->customerAddressRegionFactory = $customerAddressRegionFactory; $this->taxHelper = $taxHelper ?: ObjectManager::getInstance()->get(TaxHelper::class); $this->quoteDetailsItemExtensionFactory = $quoteDetailsItemExtensionInterfaceFactory ?: ObjectManager::getInstance()->get(QuoteDetailsItemExtensionInterfaceFactory::class); $this->customerAccountManagement = $customerAccountManagement ?? ObjectManager::getInstance()->get(CustomerAccountManagement::class); } /** * Map quote address to customer address * * @param QuoteAddress $address * @return CustomerAddress */ public function mapAddress(QuoteAddress $address) { $customerAddress = $this->customerAddressFactory->create(); $customerAddress->setCountryId($address->getCountryId()); $customerAddress->setRegion( $this->customerAddressRegionFactory->create()->setRegionId($address->getRegionId()) ); $customerAddress->setPostcode($address->getPostcode()); $customerAddress->setCity($address->getCity()); $customerAddress->setStreet($address->getStreet()); return $customerAddress; } /** * Map an item to item data object * * @param \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $itemDataObjectFactory * @param AbstractItem $item * @param bool $priceIncludesTax * @param bool $useBaseCurrency * @param string $parentCode * @return QuoteDetailsItemInterface */ public function mapItem( \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $itemDataObjectFactory, AbstractItem $item, $priceIncludesTax, $useBaseCurrency, $parentCode = null ) { if (!$item->getTaxCalculationItemId()) { $sequence = 'sequence-' . $this->getNextIncrement(); $item->setTaxCalculationItemId($sequence); } /** @var QuoteDetailsItemInterface $itemDataObject */ $itemDataObject = $itemDataObjectFactory->create(); $itemDataObject->setCode($item->getTaxCalculationItemId()) ->setQuantity($item->getQty()) ->setTaxClassKey( $this->taxClassKeyDataObjectFactory->create() ->setType(TaxClassKeyInterface::TYPE_ID) ->setValue($item->getProduct()->getTaxClassId()) ) ->setIsTaxIncluded($priceIncludesTax) ->setType(self::ITEM_TYPE_PRODUCT); if ($useBaseCurrency) { if (!$item->getBaseTaxCalculationPrice()) { $item->setBaseTaxCalculationPrice($item->getBaseCalculationPriceOriginal()); } if ($this->taxHelper->applyTaxOnOriginalPrice()) { $baseTaxCalculationPrice = $item->getBaseOriginalPrice(); } else { $baseTaxCalculationPrice = $item->getBaseCalculationPriceOriginal(); } $this->setPriceForTaxCalculation($itemDataObject, (float)$baseTaxCalculationPrice); $itemDataObject->setUnitPrice($item->getBaseTaxCalculationPrice()) ->setDiscountAmount($item->getBaseDiscountAmount()); } else { if (!$item->getTaxCalculationPrice()) { $item->setTaxCalculationPrice($item->getCalculationPriceOriginal()); } if ($this->taxHelper->applyTaxOnOriginalPrice()) { $taxCalculationPrice = $item->getOriginalPrice(); } else { $taxCalculationPrice = $item->getCalculationPriceOriginal(); } $this->setPriceForTaxCalculation($itemDataObject, (float)$taxCalculationPrice); $itemDataObject->setUnitPrice($item->getTaxCalculationPrice()) ->setDiscountAmount($item->getDiscountAmount()); } $itemDataObject->setParentCode($parentCode); return $itemDataObject; } /** * Set price for tax calculation. * * @param QuoteDetailsItemInterface $quoteDetailsItem * @param float $taxCalculationPrice * @return void */ private function setPriceForTaxCalculation(QuoteDetailsItemInterface $quoteDetailsItem, float $taxCalculationPrice) { $extensionAttributes = $quoteDetailsItem->getExtensionAttributes(); if (!$extensionAttributes) { $extensionAttributes = $this->quoteDetailsItemExtensionFactory->create(); } $extensionAttributes->setPriceForTaxCalculation($taxCalculationPrice); $quoteDetailsItem->setExtensionAttributes($extensionAttributes); } /** * Map item extra taxables * * @param \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $itemDataObjectFactory * @param AbstractItem $item * @param bool $priceIncludesTax * @param bool $useBaseCurrency * @return QuoteDetailsItemInterface[] */ public function mapItemExtraTaxables( \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $itemDataObjectFactory, AbstractItem $item, $priceIncludesTax, $useBaseCurrency ) { $itemDataObjects = []; $extraTaxables = $item->getAssociatedTaxables(); if (!$extraTaxables) { return []; } foreach ($extraTaxables as $extraTaxable) { $extraTaxableIncludesTax = isset($extraTaxable['price_includes_tax']) ? $extraTaxable['price_includes_tax'] : $priceIncludesTax; if ($useBaseCurrency) { $unitPrice = $extraTaxable[self::KEY_ASSOCIATED_TAXABLE_BASE_UNIT_PRICE]; } else { $unitPrice = $extraTaxable[self::KEY_ASSOCIATED_TAXABLE_UNIT_PRICE]; } /** @var QuoteDetailsItemInterface $itemDataObject */ $itemDataObject = $itemDataObjectFactory->create(); $itemDataObject->setCode($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_CODE]) ->setType($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_TYPE]) ->setQuantity($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_QUANTITY]) ->setTaxClassKey( $this->taxClassKeyDataObjectFactory->create() ->setType(TaxClassKeyInterface::TYPE_ID) ->setValue($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_TAX_CLASS_ID]) ) ->setUnitPrice($unitPrice) ->setIsTaxIncluded($extraTaxableIncludesTax) ->setAssociatedItemCode($item->getTaxCalculationItemId()); $itemDataObjects[] = $itemDataObject; } return $itemDataObjects; } /** * Add quote items * * @param ShippingAssignmentInterface $shippingAssignment * @param bool $priceIncludesTax * @param bool $useBaseCurrency * @return QuoteDetailsItemInterface[] */ public function mapItems( ShippingAssignmentInterface $shippingAssignment, $priceIncludesTax, $useBaseCurrency ) { $items = $shippingAssignment->getItems(); if (empty($items)) { return []; } //Populate with items $itemDataObjectFactory = $this->quoteDetailsItemDataObjectFactory; $itemDataObjects = []; foreach ($items as $item) { if ($item->getParentItem()) { continue; } if ($item->getHasChildren() && $item->isChildrenCalculated()) { $parentItemDataObject = $this->mapItem( $itemDataObjectFactory, $item, $priceIncludesTax, $useBaseCurrency ); $itemDataObjects[] = [$parentItemDataObject]; foreach ($item->getChildren() as $child) { $childItemDataObject = $this->mapItem( $itemDataObjectFactory, $child, $priceIncludesTax, $useBaseCurrency, $parentItemDataObject->getCode() ); $itemDataObjects[] = [$childItemDataObject]; $extraTaxableItems = $this->mapItemExtraTaxables( $itemDataObjectFactory, $item, $priceIncludesTax, $useBaseCurrency ); $itemDataObjects[] = $extraTaxableItems; } } else { $itemDataObject = $this->mapItem($itemDataObjectFactory, $item, $priceIncludesTax, $useBaseCurrency); $itemDataObjects[] = [$itemDataObject]; $extraTaxableItems = $this->mapItemExtraTaxables( $itemDataObjectFactory, $item, $priceIncludesTax, $useBaseCurrency ); $itemDataObjects[] = $extraTaxableItems; } } return array_merge([], ...$itemDataObjects); } /** * Populate the quote details with address information * * @param QuoteDetailsInterface $quoteDetails * @param QuoteAddress $address * @return QuoteDetailsInterface */ public function populateAddressData(QuoteDetailsInterface $quoteDetails, QuoteAddress $address) { $quoteDetails->setBillingAddress($this->mapAddress($address->getQuote()->getBillingAddress())); if ($address->getAddressType() === QuoteAddress::ADDRESS_TYPE_BILLING && !$address->getCountryId() && $address->getQuote()->isVirtual() && $address->getQuote()->getCustomerId() ) { $defaultBillingAddress = $this->customerAccountManagement->getDefaultBillingAddress( $address->getQuote()->getCustomerId() ); $addressCopy = $address; if ($defaultBillingAddress) { $addressCopy = clone $address; $addressCopy->importCustomerAddressData($defaultBillingAddress); } $quoteDetails->setShippingAddress($this->mapAddress($addressCopy)); } else { $quoteDetails->setShippingAddress($this->mapAddress($address)); } return $quoteDetails; } /** * Get shipping data object. * * @param ShippingAssignmentInterface $shippingAssignment * @param QuoteAddress\Total $total * @param bool $useBaseCurrency * @return QuoteDetailsItemInterface */ public function getShippingDataObject( ShippingAssignmentInterface $shippingAssignment, QuoteAddress\Total $total, $useBaseCurrency ) { $store = $shippingAssignment->getShipping()->getAddress()->getQuote()->getStore(); if ($total->getShippingTaxCalculationAmount() === null) { //Save the original shipping amount because shipping amount will be overridden //with shipping amount excluding tax $total->setShippingTaxCalculationAmount($total->getShippingAmount()); $total->setBaseShippingTaxCalculationAmount($total->getBaseShippingAmount()); } if ($total->getShippingTaxCalculationAmount() !== null) { /** @var QuoteDetailsItemInterface $itemDataObject */ $itemDataObject = $this->quoteDetailsItemDataObjectFactory->create() ->setType(self::ITEM_TYPE_SHIPPING) ->setCode(self::ITEM_CODE_SHIPPING) ->setQuantity(1); if ($useBaseCurrency) { $itemDataObject->setUnitPrice($total->getBaseShippingTaxCalculationAmount()); } else { $itemDataObject->setUnitPrice($total->getShippingTaxCalculationAmount()); } if ($total->getShippingDiscountAmount()) { if ($useBaseCurrency) { $itemDataObject->setDiscountAmount($total->getBaseShippingDiscountAmount()); } else { $itemDataObject->setDiscountAmount($total->getShippingDiscountAmount()); } } $itemDataObject->setTaxClassKey( $this->taxClassKeyDataObjectFactory->create() ->setType(TaxClassKeyInterface::TYPE_ID) ->setValue($this->_config->getShippingTaxClass($store)) ); $itemDataObject->setIsTaxIncluded( $this->_config->shippingPriceIncludesTax($store) ); return $itemDataObject; } return null; } /** * Populate QuoteDetails object from quote address object * * @param ShippingAssignmentInterface $shippingAssignment * @param QuoteDetailsItemInterface[] $itemDataObjects * @return \Magento\Tax\Api\Data\QuoteDetailsInterface */ protected function prepareQuoteDetails(ShippingAssignmentInterface $shippingAssignment, $itemDataObjects) { $items = $shippingAssignment->getItems(); $address = $shippingAssignment->getShipping()->getAddress(); if (empty($items)) { return $this->quoteDetailsDataObjectFactory->create(); } $quoteDetails = $this->quoteDetailsDataObjectFactory->create(); $this->populateAddressData($quoteDetails, $address); //Set customer tax class $quoteDetails->setCustomerTaxClassKey( $this->taxClassKeyDataObjectFactory->create() ->setType(TaxClassKeyInterface::TYPE_ID) ->setValue($address->getQuote()->getCustomerTaxClassId()) ); $quoteDetails->setItems($itemDataObjects); $quoteDetails->setCustomerId($address->getQuote()->getCustomerId()); return $quoteDetails; } /** * Organize tax details by type and by item code * * @param TaxDetailsInterface $taxDetails * @param TaxDetailsInterface $baseTaxDetails * @return array */ protected function organizeItemTaxDetailsByType( TaxDetailsInterface $taxDetails, TaxDetailsInterface $baseTaxDetails ) { /** @var \Magento\Tax\Api\Data\TaxDetailsItemInterface[] $keyedItems */ $keyedItems = []; foreach ($taxDetails->getItems() as $item) { $keyedItems[$item->getCode()] = $item; } /** @var \Magento\Tax\Api\Data\TaxDetailsItemInterface[] $baseKeyedItems */ $baseKeyedItems = []; foreach ($baseTaxDetails->getItems() as $item) { $baseKeyedItems[$item->getCode()] = $item; } $itemsByType = []; foreach ($keyedItems as $code => $item) { $baseItem = $baseKeyedItems[$code]; $itemType = $item->getType(); $itemsByType[$itemType][$code] = [self::KEY_ITEM => $item, self::KEY_BASE_ITEM => $baseItem]; } return $itemsByType; } /** * Process product items in the quote. * Set the following aggregated values in the quote object: * subtotal, subtotalInclTax, tax, discount_tax_compensation, * * @param ShippingAssignmentInterface $shippingAssignment * @param array $itemTaxDetails * @param QuoteAddress\Total $total * @return $this */ protected function processProductItems( ShippingAssignmentInterface $shippingAssignment, array $itemTaxDetails, QuoteAddress\Total $total ) { $store = $shippingAssignment->getShipping()->getAddress()->getQuote()->getStore(); /** @var AbstractItem[] $keyedAddressItems */ $keyedAddressItems = []; foreach ($shippingAssignment->getItems() as $addressItem) { $keyedAddressItems[$addressItem->getTaxCalculationItemId()] = $addressItem; } $subtotal = $baseSubtotal = 0; $discountTaxCompensation = $baseDiscountTaxCompensation = 0; $tax = $baseTax = 0; $subtotalInclTax = $baseSubtotalInclTax = 0; foreach ($itemTaxDetails as $code => $itemTaxDetail) { /** @var TaxDetailsItemInterface $taxDetail */ $taxDetail = $itemTaxDetail[self::KEY_ITEM]; /** @var TaxDetailsItemInterface $baseTaxDetail */ $baseTaxDetail = $itemTaxDetail[self::KEY_BASE_ITEM]; $quoteItem = $keyedAddressItems[$code]; if (!$quoteItem->isDeleted()) { $this->updateItemTaxInfo($quoteItem, $taxDetail, $baseTaxDetail, $store); //Update aggregated values if ($quoteItem->getHasChildren() && $quoteItem->isChildrenCalculated()) { //avoid double counting continue; } $subtotal += $taxDetail->getRowTotal(); $baseSubtotal += $baseTaxDetail->getRowTotal(); $discountTaxCompensation += $taxDetail->getDiscountTaxCompensationAmount(); $baseDiscountTaxCompensation += $baseTaxDetail->getDiscountTaxCompensationAmount(); $tax += $taxDetail->getRowTax(); $baseTax += $baseTaxDetail->getRowTax(); $subtotalInclTax += $taxDetail->getRowTotalInclTax(); $baseSubtotalInclTax += $baseTaxDetail->getRowTotalInclTax(); } } //Set aggregated values $total->setTotalAmount('subtotal', $subtotal); $total->setBaseTotalAmount('subtotal', $baseSubtotal); $total->setTotalAmount('tax', $tax); $total->setBaseTotalAmount('tax', $baseTax); $total->setTotalAmount('discount_tax_compensation', $discountTaxCompensation); $total->setBaseTotalAmount('discount_tax_compensation', $baseDiscountTaxCompensation); $total->setSubtotalInclTax($subtotalInclTax); $total->setBaseSubtotalTotalInclTax($baseSubtotalInclTax); $total->setBaseSubtotalInclTax($baseSubtotalInclTax); $address = $shippingAssignment->getShipping()->getAddress(); $address->setBaseTaxAmount($baseTax); $address->setBaseSubtotalTotalInclTax($baseSubtotalInclTax); $address->setSubtotalInclTax($subtotalInclTax); $address->setSubtotal($total->getSubtotal()); $address->setBaseSubtotal($total->getBaseSubtotal()); return $this; } /** * Process applied taxes for items and quote * * @param QuoteAddress\Total $total * @param ShippingAssignmentInterface $shippingAssignment * @param array $itemsByType * @return $this */ protected function processAppliedTaxes( QuoteAddress\Total $total, ShippingAssignmentInterface $shippingAssignment, array $itemsByType ) { $total->setAppliedTaxes([]); $allAppliedTaxesArray = []; /** @var AbstractItem[] $keyedAddressItems */ $keyedAddressItems = []; foreach ($shippingAssignment->getItems() as $addressItem) { $keyedAddressItems[$addressItem->getTaxCalculationItemId()] = $addressItem; } foreach ($itemsByType as $itemType => $items) { foreach ($items as $itemTaxCalculationId => $itemTaxDetails) { /** @var TaxDetailsItemInterface $taxDetails */ $taxDetails = $itemTaxDetails[self::KEY_ITEM]; $baseTaxDetails = $itemTaxDetails[self::KEY_BASE_ITEM]; $appliedTaxes = $taxDetails->getAppliedTaxes(); $baseAppliedTaxes = $baseTaxDetails->getAppliedTaxes(); $itemType = $taxDetails->getType(); $itemId = null; $associatedItemId = null; if ($itemType == self::ITEM_TYPE_PRODUCT) { //Use item id instead of tax calculation id $itemId = $this->getQuoteItemId($keyedAddressItems, $itemTaxCalculationId); } else { if ($taxDetails->getAssociatedItemCode() && $taxDetails->getAssociatedItemCode() != self::ASSOCIATION_ITEM_CODE_FOR_QUOTE) { //This item is associated with a product item $associatedItemId = $keyedAddressItems[$taxDetails->getAssociatedItemCode()]->getId(); } else { //This item is associated with an order, e.g., shipping, etc. $itemId = null; } } $extraInfo = [ 'item_id' => $itemId, 'item_type' => $itemType, 'associated_item_id' => $associatedItemId, ]; $appliedTaxesArray = $this->convertAppliedTaxes($appliedTaxes, $baseAppliedTaxes, $extraInfo); if ($itemType == self::ITEM_TYPE_PRODUCT) { $quoteItem = $keyedAddressItems[$itemTaxCalculationId]; $quoteItem->setAppliedTaxes($appliedTaxesArray); } $allAppliedTaxesArray[$itemTaxCalculationId] = $appliedTaxesArray; foreach ($appliedTaxesArray as $appliedTaxArray) { $this->_saveAppliedTaxes( $total, [$appliedTaxArray], $appliedTaxArray['amount'], $appliedTaxArray['base_amount'], $appliedTaxArray['percent'] ); } } } $total->setItemsAppliedTaxes($allAppliedTaxesArray); return $this; } /** * Update tax related fields for quote item * * @param AbstractItem $quoteItem * @param TaxDetailsItemInterface $itemTaxDetails * @param TaxDetailsItemInterface $baseItemTaxDetails * @param Store $store * @return $this */ public function updateItemTaxInfo($quoteItem, $itemTaxDetails, $baseItemTaxDetails, $store) { //The price should be base price $quoteItem->setPrice($baseItemTaxDetails->getPrice()); if ($quoteItem->getCustomPrice() && $this->taxHelper->applyTaxOnCustomPrice()) { $quoteItem->setCustomPrice($itemTaxDetails->getPrice()); } $quoteItem->setConvertedPrice($itemTaxDetails->getPrice()); $quoteItem->setPriceInclTax($itemTaxDetails->getPriceInclTax()); $quoteItem->setRowTotal($itemTaxDetails->getRowTotal()); $quoteItem->setRowTotalInclTax($itemTaxDetails->getRowTotalInclTax()); $quoteItem->setTaxAmount($itemTaxDetails->getRowTax()); $quoteItem->setTaxPercent($itemTaxDetails->getTaxPercent()); $quoteItem->setDiscountTaxCompensationAmount($itemTaxDetails->getDiscountTaxCompensationAmount()); $quoteItem->setBasePrice($baseItemTaxDetails->getPrice()); $quoteItem->setBasePriceInclTax($baseItemTaxDetails->getPriceInclTax()); $quoteItem->setBaseRowTotal($baseItemTaxDetails->getRowTotal()); $quoteItem->setBaseRowTotalInclTax($baseItemTaxDetails->getRowTotalInclTax()); $quoteItem->setBaseTaxAmount($baseItemTaxDetails->getRowTax()); $quoteItem->setTaxPercent($baseItemTaxDetails->getTaxPercent()); $quoteItem->setBaseDiscountTaxCompensationAmount($baseItemTaxDetails->getDiscountTaxCompensationAmount()); //Set discount calculation price, this may be needed by discount collector if ($this->_config->discountTax($store)) { $quoteItem->setDiscountCalculationPrice($itemTaxDetails->getPriceInclTax()); $quoteItem->setBaseDiscountCalculationPrice($baseItemTaxDetails->getPriceInclTax()); } else { $quoteItem->setDiscountCalculationPrice($itemTaxDetails->getPrice()); $quoteItem->setBaseDiscountCalculationPrice($baseItemTaxDetails->getPrice()); } return $this; } /** * Update tax related fields for shipping * * @param ShippingAssignmentInterface $shippingAssignment * @param QuoteAddress\Total $total * @param TaxDetailsItemInterface $shippingTaxDetails * @param TaxDetailsItemInterface $baseShippingTaxDetails * @return $this */ protected function processShippingTaxInfo( ShippingAssignmentInterface $shippingAssignment, QuoteAddress\Total $total, $shippingTaxDetails, $baseShippingTaxDetails ) { $total->setTotalAmount('shipping', $shippingTaxDetails->getRowTotal()); $total->setBaseTotalAmount('shipping', $baseShippingTaxDetails->getRowTotal()); $total->setTotalAmount( 'shipping_discount_tax_compensation', $shippingTaxDetails->getDiscountTaxCompensationAmount() ); $total->setBaseTotalAmount( 'shipping_discount_tax_compensation', $baseShippingTaxDetails->getDiscountTaxCompensationAmount() ); $total->setShippingInclTax($shippingTaxDetails->getRowTotalInclTax()); $total->setBaseShippingInclTax($baseShippingTaxDetails->getRowTotalInclTax()); $total->setShippingTaxAmount($shippingTaxDetails->getRowTax()); $total->setBaseShippingTaxAmount($baseShippingTaxDetails->getRowTax()); //Add the shipping tax to total tax amount $total->addTotalAmount('tax', $shippingTaxDetails->getRowTax()); $total->addBaseTotalAmount('tax', $baseShippingTaxDetails->getRowTax()); if ($this->_config->discountTax($shippingAssignment->getShipping()->getAddress()->getQuote()->getStore())) { $total->setShippingAmountForDiscount($shippingTaxDetails->getRowTotalInclTax()); $total->setBaseShippingAmountForDiscount($baseShippingTaxDetails->getRowTotalInclTax()); } return $this; } /** * Convert appliedTax data object from tax calculation service to internal array format * * @param \Magento\Tax\Api\Data\AppliedTaxInterface[] $appliedTaxes * @param \Magento\Tax\Api\Data\AppliedTaxInterface[] $baseAppliedTaxes * @param array $extraInfo * @return array */ public function convertAppliedTaxes($appliedTaxes, $baseAppliedTaxes, $extraInfo = []) { $appliedTaxesArray = []; if (!$appliedTaxes || !$baseAppliedTaxes) { return $appliedTaxesArray; } foreach ($appliedTaxes as $taxId => $appliedTax) { $baseAppliedTax = $baseAppliedTaxes[$taxId]; $rateDataObjects = $appliedTax->getRates(); $rates = []; foreach ($rateDataObjects as $rateDataObject) { $rates[] = [ 'percent' => $rateDataObject->getPercent(), 'code' => $rateDataObject->getCode(), 'title' => $rateDataObject->getTitle(), ]; } $appliedTaxArray = [ 'amount' => $appliedTax->getAmount(), 'base_amount' => $baseAppliedTax->getAmount(), 'percent' => $appliedTax->getPercent(), 'id' => $appliedTax->getTaxRateKey(), 'rates' => $rates, ]; if (!empty($extraInfo)) { //phpcs:ignore Magento2.Performance.ForeachArrayMerge $appliedTaxArray = array_merge($appliedTaxArray, $extraInfo); } $appliedTaxesArray[] = $appliedTaxArray; } return $appliedTaxesArray; } /** * Collect applied tax rates information on address level * * @param QuoteAddress\Total $total * @param array $applied * @param float $amount * @param float $baseAmount * @param float $rate * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _saveAppliedTaxes( QuoteAddress\Total $total, $applied, $amount, $baseAmount, $rate ) { $previouslyAppliedTaxes = $total->getAppliedTaxes(); $process = count($previouslyAppliedTaxes); foreach ($applied as $row) { if ($row['percent'] == 0) { continue; } if (!isset($previouslyAppliedTaxes[$row['id']])) { $row['process'] = $process; $row['amount'] = 0; $row['base_amount'] = 0; $previouslyAppliedTaxes[$row['id']] = $row; } if ($row['percent'] !== null) { $row['percent'] = $row['percent'] ? $row['percent'] : 1; $rate = $rate ? $rate : 1; $appliedAmount = $amount / $rate * $row['percent']; $baseAppliedAmount = $baseAmount / $rate * $row['percent']; } else { $appliedAmount = 0; $baseAppliedAmount = 0; foreach ($row['rates'] as $rate) { $appliedAmount += $rate['amount']; $baseAppliedAmount += $rate['base_amount']; } } if ($appliedAmount || $previouslyAppliedTaxes[$row['id']]['amount']) { $previouslyAppliedTaxes[$row['id']]['amount'] += $appliedAmount; $previouslyAppliedTaxes[$row['id']]['base_amount'] += $baseAppliedAmount; } else { unset($previouslyAppliedTaxes[$row['id']]); } } $total->setAppliedTaxes($previouslyAppliedTaxes); } /** * Determine whether to include shipping in tax calculation * * @return bool */ protected function includeShipping() { return false; } /** * Determine whether to include item in tax calculation * * @return bool */ protected function includeItems() { return false; } /** * Determine whether to include item in tax calculation * * @return bool */ protected function includeExtraTax() { return false; } /** * Determine whether to save applied tax in address * * @return bool */ protected function saveAppliedTaxes() { return false; } /** * Increment and return counter. * * This function is intended to be used to generate temporary id for an item. * * @return int */ protected function getNextIncrement() { return ++$this->counter; } /** * Returns quote_item_id, as structure differs for usual shipping and multishipping approaches * * @param AbstractItem[] $keyedAddressItems * @param string $itemTaxCalculationId * * @return mixed */ private function getQuoteItemId(array $keyedAddressItems, string $itemTaxCalculationId) { if (isset($keyedAddressItems[$itemTaxCalculationId]["quote_item"])) { return $keyedAddressItems[$itemTaxCalculationId]["quote_item"]->getId(); } else { return $keyedAddressItems[$itemTaxCalculationId]->getId(); } } }