| Current Path : /home/rtorresani/www/vendor/magento/module-tax/Model/Sales/Total/Quote/ |
| Current File : //home/rtorresani/www/vendor/magento/module-tax/Model/Sales/Total/Quote/CommonTaxCollector.php |
<?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();
}
}
}