123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Tax\Model\Sales\Total\Quote;
- 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
- */
- const ITEM_TYPE_SHIPPING = 'shipping';
- const ITEM_TYPE_PRODUCT = 'product';
- /**#@-*/
- /**
- * Constant for shipping item code
- */
- const ITEM_CODE_SHIPPING = 'shipping';
- /**#@+
- * Constants for array keys
- */
- const KEY_ITEM = 'item';
- const KEY_BASE_ITEM = 'base_item';
- /**#@-*/
- /**#@+
- * Constants for fields in associated taxables array
- */
- const KEY_ASSOCIATED_TAXABLE_TYPE = 'type';
- const KEY_ASSOCIATED_TAXABLE_CODE = 'code';
- const KEY_ASSOCIATED_TAXABLE_UNIT_PRICE = 'unit_price';
- const KEY_ASSOCIATED_TAXABLE_BASE_UNIT_PRICE = 'base_unit_price';
- const KEY_ASSOCIATED_TAXABLE_QUANTITY = 'quantity';
- const KEY_ASSOCIATED_TAXABLE_TAX_CLASS_ID = 'tax_class_id';
- const KEY_ASSOCIATED_TAXABLE_PRICE_INCLUDES_TAX = 'price_includes_tax';
- 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
- */
- const ASSOCIATION_ITEM_CODE_FOR_QUOTE = 'quote';
- /**#@+
- * Constants for fields in tax details for associated taxable items
- */
- const KEY_TAX_DETAILS_TYPE = 'type';
- const KEY_TAX_DETAILS_CODE = 'code';
- const KEY_TAX_DETAILS_PRICE_EXCL_TAX = 'price_excl_tax';
- const KEY_TAX_DETAILS_BASE_PRICE_EXCL_TAX = 'base_price_excl_tax';
- const KEY_TAX_DETAILS_PRICE_INCL_TAX = 'price_incl_tax';
- const KEY_TAX_DETAILS_BASE_PRICE_INCL_TAX = 'base_price_incl_tax';
- const KEY_TAX_DETAILS_ROW_TOTAL = 'row_total_excl_tax';
- const KEY_TAX_DETAILS_BASE_ROW_TOTAL = 'base_row_total_excl_tax';
- const KEY_TAX_DETAILS_ROW_TOTAL_INCL_TAX = 'row_total_incl_tax';
- const KEY_TAX_DETAILS_BASE_ROW_TOTAL_INCL_TAX = 'base_row_total_incl_tax';
- const KEY_TAX_DETAILS_TAX_PERCENT = 'tax_percent';
- const KEY_TAX_DETAILS_ROW_TAX = 'row_tax';
- const KEY_TAX_DETAILS_BASE_ROW_TAX = 'base_row_tax';
- const KEY_TAX_DETAILS_APPLIED_TAXES = 'applied_taxes';
- /**#@-*/
- /**#@-*/
- 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;
- /**
- * 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
- */
- 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
- ) {
- $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);
- }
- /**
- * 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 (!count($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 = array_merge($itemDataObjects, $extraTaxableItems);
- }
- } else {
- $itemDataObject = $this->mapItem($itemDataObjectFactory, $item, $priceIncludesTax, $useBaseCurrency);
- $itemDataObjects[] = $itemDataObject;
- $extraTaxableItems = $this->mapItemExtraTaxables(
- $itemDataObjectFactory,
- $item,
- $priceIncludesTax,
- $useBaseCurrency
- );
- $itemDataObjects = array_merge($itemDataObjects, $extraTaxableItems);
- }
- }
- return $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()));
- $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 (!count($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];
- $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);
- $shippingAssignment->getShipping()->getAddress()->setBaseSubtotalTotalInclTax($baseSubtotalInclTax);
- 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 = $keyedAddressItems[$itemTaxCalculationId]->getId();
- } 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());
- $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)) {
- $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;
- }
- }
|