<?php
/**
* Taxcloud_Magento2
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @package Taxcloud_Magento2
* @author TaxCloud <[email protected]>
* @copyright 2021 The Federal Tax Authority, LLC d/b/a TaxCloud
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
namespace TaxcloudMagento2Model;
use MagentoCustomerApiDataAddressInterfaceFactory;
use MagentoCustomerApiDataRegionInterfaceFactory;
use MagentoFrameworkAppConfigScopeConfigInterface;
use MagentoFrameworkSerializeSerializerJson;
use MagentoQuoteApiDataShippingAssignmentInterface;
use MagentoQuoteModelQuote;
use MagentoQuoteModelQuoteAddressTotal;
use MagentoStoreModelScopeInterface;
use MagentoTaxApiDataQuoteDetailsInterfaceFactory;
use MagentoTaxApiDataQuoteDetailsItemInterfaceFactory;
use MagentoTaxApiDataTaxClassKeyInterfaceFactory;
use MagentoTaxApiTaxCalculationInterface;
use MagentoTaxHelperData;
use MagentoTaxModelConfig;
use TaxcloudMagento2LoggerLogger;
/**
* Tax totals calculation model
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Tax extends MagentoTaxModelSalesTotalQuoteTax
{
/**
* Magento Config Object
*
* @var ScopeConfigInterface|null
*/
protected ?ScopeConfigInterface $_scopeConfig = null;
/**
* TaxCloud Api Object
*
* @var Api
*/
protected $_tcapi;
/**
* TaxCloud Logger
*
* @var Logger
*/
protected $_tclogger;
/**
* Class constructor
*
* @param Config $taxConfig
* @param TaxCalculationInterface $taxCalculationService
* @param QuoteDetailsInterfaceFactory $quoteDetailsDataObjectFactory
* @param QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory
* @param TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory
* @param AddressInterfaceFactory $customerAddressFactory
* @param RegionInterfaceFactory $customerAddressRegionFactory
* @param Data $taxData
* @param Json|null $serializer
* @param ScopeConfigInterface $scopeConfig
* @param Api $tcapi
* @param Logger $tclogger
*/
public function __construct(
Config $taxConfig,
TaxCalculationInterface $taxCalculationService,
QuoteDetailsInterfaceFactory $quoteDetailsDataObjectFactory,
QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory,
TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory,
AddressInterfaceFactory $customerAddressFactory,
RegionInterfaceFactory $customerAddressRegionFactory,
Data $taxData,
Json $serializer = null,
ScopeConfigInterface $scopeConfig,
Api $tcapi,
Logger $tclogger
) {
$this->_scopeConfig = $scopeConfig;
$this->_tcapi = $tcapi;
if ($scopeConfig->getValue('tax/taxcloud_settings/logging', ScopeInterface::SCOPE_STORE)) {
$this->_tclogger = $tclogger;
} else {
$this->_tclogger = new class() {
public function info()
{
}
};
}
parent::__construct(
$taxConfig,
$taxCalculationService,
$quoteDetailsDataObjectFactory,
$quoteDetailsItemDataObjectFactory,
$taxClassKeyDataObjectFactory,
$customerAddressFactory,
$customerAddressRegionFactory,
$taxData,
$serializer
);
}
/**
* Collect tax totals for quote address
*
* @param Quote $quote
* @param ShippingAssignmentInterface $shippingAssignment
* @param Total $total
* @return $this
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function collect(
Quote $quote,
ShippingAssignmentInterface $shippingAssignment,
Total $total
): static {
if (!$this->_scopeConfig->getValue('tax/taxcloud_settings/enabled', ScopeInterface::SCOPE_STORE)) {
return parent::collect($quote, $shippingAssignment, $total);
}
$this->clearValues($total);
if (!$shippingAssignment->getItems()) {
return $this;
}
$baseTaxDetails = $this->getQuoteTaxDetails($shippingAssignment, $total, true);
$taxDetails = $this->getQuoteTaxDetails($shippingAssignment, $total, false);
//Populate address and items with tax calculation results
$itemsByType = $this->organizeItemTaxDetailsByType($taxDetails, $baseTaxDetails);
// Fetch tax amount from TaxCloud
$taxAmounts = $this->_tcapi->lookupTaxes($itemsByType, $shippingAssignment, $quote);
// $this->_tclogger->info(json_encode($taxAmounts, JSON_PRETTY_PRINT));
$keyedAddressItems = [];
foreach ($shippingAssignment->getItems() as $item) {
$keyedAddressItems[$item->getTaxCalculationItemId()] = $item;
}
if (isset($itemsByType[self::ITEM_TYPE_PRODUCT])) {
foreach ($itemsByType[self::ITEM_TYPE_PRODUCT] as $code => $itemTaxDetail) {
$taxDetail = $itemTaxDetail[self::KEY_ITEM];
$baseTaxDetail = $itemTaxDetail[self::KEY_BASE_ITEM];
$quoteItem = $keyedAddressItems[$code];
if ($quoteItem->getProduct()->getTaxClassId() === '0' || $quoteItem->getQty() === 0) {
$taxAmount = 0;
$taxAmountPer = 0;
} else {
$taxAmount = $taxAmounts[self::ITEM_TYPE_PRODUCT][$code] ?? 0;
$taxAmountPer = $taxAmount / $quoteItem->getQty();
}
$taxDetail->setRowTax($taxAmount);
$taxDetail->setPriceInclTax($taxDetail->getPrice() + $taxAmountPer);
$taxDetail->setRowTotalInclTax($taxDetail->getRowTotal() + $taxAmount);
$taxDetail->setAppliedTaxes([]);
if ($taxDetail->getRowTotal() > 0) {
$taxDetail->setTaxPercent(round(100 * $taxDetail->getRowTax() / $taxDetail->getRowTotal(), 2));
} else {
$taxDetail->setTaxPercent(0);
}
$baseTaxDetail->setRowTax($taxAmount);
$baseTaxDetail->setPriceInclTax($baseTaxDetail->getPrice() + $taxAmountPer);
$baseTaxDetail->setRowTotalInclTax($baseTaxDetail->getRowTotal() + $taxAmount);
$baseTaxDetail->setAppliedTaxes([]);
if ($baseTaxDetail->getRowTotal() > 0) {
$baseTaxDetail->setTaxPercent(
round(100 * $baseTaxDetail->getRowTax() / $baseTaxDetail->getRowTotal(), 2)
);
} else {
$baseTaxDetail->setTaxPercent(0);
}
}
$this->processProductItems($shippingAssignment, $itemsByType[self::ITEM_TYPE_PRODUCT], $total);
}
if (isset($itemsByType[self::ITEM_TYPE_SHIPPING])) {
$shippingTaxDetails = $itemsByType[self::ITEM_TYPE_SHIPPING][self::ITEM_CODE_SHIPPING][self::KEY_ITEM];
$baseShippingTaxDetails = $itemsByType[self::ITEM_TYPE_SHIPPING][self::ITEM_CODE_SHIPPING][self::KEY_BASE_ITEM];
$taxAmount = $taxAmounts[self::ITEM_TYPE_SHIPPING];
$taxAmountPer = $taxAmount / 1;
$shippingTaxDetails->setRowTax($taxAmount);
$shippingTaxDetails->setPriceInclTax($shippingTaxDetails->getPrice() + $taxAmountPer);
$shippingTaxDetails->setRowTotalInclTax($shippingTaxDetails->getRowTotal() + $taxAmount);
$shippingTaxDetails->setAppliedTaxes([]);
if ($shippingTaxDetails->getRowTotal() > 0) {
$shippingTaxDetails->setTaxPercent(
round(
100 * $shippingTaxDetails->getRowTax() / $shippingTaxDetails->getRowTotal(),
2
)
);
} else {
$shippingTaxDetails->setTaxPercent(0);
}
$baseShippingTaxDetails->setRowTax($taxAmount);
$baseShippingTaxDetails->setPriceInclTax($baseShippingTaxDetails->getPrice() + $taxAmountPer);
$baseShippingTaxDetails->setRowTotalInclTax($baseShippingTaxDetails->getRowTotal() + $taxAmount);
$baseShippingTaxDetails->setAppliedTaxes([]);
if ($baseShippingTaxDetails->getRowTotal() > 0) {
$baseShippingTaxDetails->setTaxPercent(
round(100 * $baseShippingTaxDetails->getRowTax() / $baseShippingTaxDetails->getRowTotal(), 2)
);
} else {
$baseShippingTaxDetails->setTaxPercent(0);
}
$this->processShippingTaxInfo($shippingAssignment, $total, $shippingTaxDetails, $baseShippingTaxDetails);
}
//Process taxable items that are not product or shipping
$this->processExtraTaxables($total, $itemsByType);
//Save applied taxes for each item and the quote in aggregation
$this->processAppliedTaxes($total, $shippingAssignment, $itemsByType);
if ($this->includeExtraTax()) {
$total->addTotalAmount('extra_tax', $total->getExtraTaxAmount());
$total->addBaseTotalAmount('extra_tax', $total->getBaseExtraTaxAmount());
}
return $this;
}
}