Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Tax.php
Go to the documentation of this file.
1 <?php
7 
8 use Magento\Customer\Api\Data\AddressInterfaceFactory as CustomerAddressFactory;
9 use Magento\Customer\Api\Data\RegionInterfaceFactory as CustomerAddressRegionFactory;
16 
21 class Tax extends CommonTaxCollector
22 {
28  protected $counter = 0;
29 
35  protected $_taxData;
36 
42  protected $_config;
43 
50 
54  private $serializer;
55 
69  public function __construct(
70  \Magento\Tax\Model\Config $taxConfig,
72  \Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory $quoteDetailsDataObjectFactory,
73  \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory,
74  \Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory,
75  CustomerAddressFactory $customerAddressFactory,
76  CustomerAddressRegionFactory $customerAddressRegionFactory,
77  \Magento\Tax\Helper\Data $taxData,
78  Json $serializer = null
79  ) {
80  $this->setCode('tax');
81  $this->_taxData = $taxData;
82  $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class);
83  parent::__construct(
84  $taxConfig,
91  );
92  }
93 
103  public function collect(
104  \Magento\Quote\Model\Quote $quote,
105  \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment,
106  \Magento\Quote\Model\Quote\Address\Total $total
107  ) {
108  $this->clearValues($total);
109  if (!$shippingAssignment->getItems()) {
110  return $this;
111  }
112 
113  $baseTaxDetails = $this->getQuoteTaxDetails($shippingAssignment, $total, true);
114  $taxDetails = $this->getQuoteTaxDetails($shippingAssignment, $total, false);
115 
116  //Populate address and items with tax calculation results
117  $itemsByType = $this->organizeItemTaxDetailsByType($taxDetails, $baseTaxDetails);
118  if (isset($itemsByType[self::ITEM_TYPE_PRODUCT])) {
119  $this->processProductItems($shippingAssignment, $itemsByType[self::ITEM_TYPE_PRODUCT], $total);
120  }
121 
122  if (isset($itemsByType[self::ITEM_TYPE_SHIPPING])) {
123  $shippingTaxDetails = $itemsByType[self::ITEM_TYPE_SHIPPING][self::ITEM_CODE_SHIPPING][self::KEY_ITEM];
124  $baseShippingTaxDetails =
126  $this->processShippingTaxInfo($shippingAssignment, $total, $shippingTaxDetails, $baseShippingTaxDetails);
127  }
128 
129  //Process taxable items that are not product or shipping
130  $this->processExtraTaxables($total, $itemsByType);
131 
132  //Save applied taxes for each item and the quote in aggregation
133  $this->processAppliedTaxes($total, $shippingAssignment, $itemsByType);
134 
135  if ($this->includeExtraTax()) {
136  $total->addTotalAmount('extra_tax', $total->getExtraTaxAmount());
137  $total->addBaseTotalAmount('extra_tax', $total->getBaseExtraTaxAmount());
138  }
139 
140  return $this;
141  }
142 
149  protected function clearValues(Address\Total $total)
150  {
151  $total->setTotalAmount('subtotal', 0);
152  $total->setBaseTotalAmount('subtotal', 0);
153  $total->setTotalAmount('tax', 0);
154  $total->setBaseTotalAmount('tax', 0);
155  $total->setTotalAmount('shipping', 0);
156  $total->setBaseTotalAmount('shipping', 0);
157  $total->setTotalAmount('discount_tax_compensation', 0);
158  $total->setBaseTotalAmount('discount_tax_compensation', 0);
159  $total->setTotalAmount('shipping_discount_tax_compensation', 0);
160  $total->setBaseTotalAmount('shipping_discount_tax_compensation', 0);
161  $total->setSubtotalInclTax(0);
162  $total->setBaseSubtotalInclTax(0);
163  $total->setShippingInclTax(0);
164  $total->setBaseShippingInclTax(0);
165  $total->setShippingTaxAmount(0);
166  $total->setBaseShippingTaxAmount(0);
167  $total->setShippingAmountForDiscount(0);
168  $total->setBaseShippingAmountForDiscount(0);
169  $total->setBaseShippingAmountForDiscount(0);
170  $total->setTotalAmount('extra_tax', 0);
171  $total->setBaseTotalAmount('extra_tax', 0);
172  }
173 
182  protected function getQuoteTaxDetails($shippingAssignment, $total, $useBaseCurrency)
183  {
184  $address = $shippingAssignment->getShipping()->getAddress();
185  //Setup taxable items
186  $priceIncludesTax = $this->_config->priceIncludesTax($address->getQuote()->getStore());
187  $itemDataObjects = $this->mapItems($shippingAssignment, $priceIncludesTax, $useBaseCurrency);
188 
189  //Add shipping
190  $shippingDataObject = $this->getShippingDataObject($shippingAssignment, $total, $useBaseCurrency);
191  if ($shippingDataObject != null) {
192  $itemDataObjects[] = $shippingDataObject;
193  }
194 
195  //process extra taxable items associated only with quote
196  $quoteExtraTaxables = $this->mapQuoteExtraTaxables(
197  $this->quoteDetailsItemDataObjectFactory,
198  $address,
199  $useBaseCurrency
200  );
201  if (!empty($quoteExtraTaxables)) {
202  $itemDataObjects = array_merge($itemDataObjects, $quoteExtraTaxables);
203  }
204 
205  //Preparation for calling taxCalculationService
206  $quoteDetails = $this->prepareQuoteDetails($shippingAssignment, $itemDataObjects);
207 
208  $taxDetails = $this->taxCalculationService
209  ->calculateTax($quoteDetails, $address->getQuote()->getStore()->getStoreId());
210 
211  return $taxDetails;
212  }
213 
222  public function mapQuoteExtraTaxables(
223  \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $itemDataObjectFactory,
225  $useBaseCurrency
226  ) {
227  $itemDataObjects = [];
228  $extraTaxables = $address->getAssociatedTaxables();
229  if (!$extraTaxables) {
230  return [];
231  }
232 
233  foreach ($extraTaxables as $extraTaxable) {
234  if ($useBaseCurrency) {
235  $unitPrice = $extraTaxable[self::KEY_ASSOCIATED_TAXABLE_BASE_UNIT_PRICE];
236  } else {
237  $unitPrice = $extraTaxable[self::KEY_ASSOCIATED_TAXABLE_UNIT_PRICE];
238  }
239  $itemDataObjects[] = $itemDataObjectFactory->create()
240  ->setCode($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_CODE])
241  ->setType($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_TYPE])
242  ->setQuantity($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_QUANTITY])
243  ->setTaxClassKey(
244  $this->taxClassKeyDataObjectFactory->create()
246  ->setValue($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_TAX_CLASS_ID])
247  )
248  ->setUnitPrice($unitPrice)
249  ->setIsTaxIncluded($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_PRICE_INCLUDES_TAX])
250  ->setAssociatedItemCode($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_ASSOCIATION_ITEM_CODE]);
251  }
252 
253  return $itemDataObjects;
254  }
255 
264  protected function processExtraTaxables(Address\Total $total, array $itemsByType)
265  {
266  $extraTaxableDetails = [];
267  foreach ($itemsByType as $itemType => $itemTaxDetails) {
268  if ($itemType != self::ITEM_TYPE_PRODUCT and $itemType != self::ITEM_TYPE_SHIPPING) {
269  foreach ($itemTaxDetails as $itemCode => $itemTaxDetail) {
271  $taxDetails = $itemTaxDetail[self::KEY_ITEM];
273  $baseTaxDetails = $itemTaxDetail[self::KEY_BASE_ITEM];
274 
275  $appliedTaxes = $taxDetails->getAppliedTaxes();
276  $baseAppliedTaxes = $baseTaxDetails->getAppliedTaxes();
277 
278  $associatedItemCode = $taxDetails->getAssociatedItemCode();
279 
280  $appliedTaxesArray = $this->convertAppliedTaxes($appliedTaxes, $baseAppliedTaxes);
281  $extraTaxableDetails[$itemType][$associatedItemCode][] = [
282  self::KEY_TAX_DETAILS_TYPE => $taxDetails->getType(),
283  self::KEY_TAX_DETAILS_CODE => $taxDetails->getCode(),
284  self::KEY_TAX_DETAILS_PRICE_EXCL_TAX => $taxDetails->getPrice(),
285  self::KEY_TAX_DETAILS_PRICE_INCL_TAX => $taxDetails->getPriceInclTax(),
286  self::KEY_TAX_DETAILS_BASE_PRICE_EXCL_TAX => $baseTaxDetails->getPrice(),
287  self::KEY_TAX_DETAILS_BASE_PRICE_INCL_TAX => $baseTaxDetails->getPriceInclTax(),
288  self::KEY_TAX_DETAILS_ROW_TOTAL => $taxDetails->getRowTotal(),
289  self::KEY_TAX_DETAILS_ROW_TOTAL_INCL_TAX => $taxDetails->getRowTotalInclTax(),
290  self::KEY_TAX_DETAILS_BASE_ROW_TOTAL => $baseTaxDetails->getRowTotal(),
291  self::KEY_TAX_DETAILS_BASE_ROW_TOTAL_INCL_TAX => $baseTaxDetails->getRowTotalInclTax(),
292  self::KEY_TAX_DETAILS_TAX_PERCENT => $taxDetails->getTaxPercent(),
293  self::KEY_TAX_DETAILS_ROW_TAX => $taxDetails->getRowTax(),
294  self::KEY_TAX_DETAILS_BASE_ROW_TAX => $baseTaxDetails->getRowTax(),
295  self::KEY_TAX_DETAILS_APPLIED_TAXES => $appliedTaxesArray,
296  ];
297 
298  $total->addTotalAmount('tax', $taxDetails->getRowTax());
299  $total->addBaseTotalAmount('tax', $baseTaxDetails->getRowTax());
300  //TODO: save applied taxes for the item
301  }
302  }
303  }
304 
305  $total->setExtraTaxableDetails($extraTaxableDetails);
306  return $this;
307  }
308 
318  public function fetch(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Model\Quote\Address\Total $total)
319  {
320  $totals = [];
321  $store = $quote->getStore();
322  $applied = $total->getAppliedTaxes();
323  if (is_string($applied)) {
324  $applied = $this->serializer->unserialize($applied);
325  }
326  $amount = $total->getTaxAmount();
327  if ($amount === null) {
328  $this->enhanceTotalData($quote, $total);
329  $amount = $total->getTaxAmount();
330  }
331  $taxAmount = $amount + $total->getTotalAmount('discount_tax_compensation');
332 
333  $area = null;
334  if ($this->_config->displayCartTaxWithGrandTotal($store) && $total->getGrandTotal()) {
335  $area = 'taxes';
336  }
337 
338  $totals[] = [
339  'code' => $this->getCode(),
340  'title' => __('Tax'),
341  'full_info' => $applied ? $applied : [],
342  'value' => $amount,
343  'area' => $area,
344  ];
345 
349  if ($this->_config->displayCartSubtotalBoth($store) || $this->_config->displayCartSubtotalInclTax($store)) {
350  if ($total->getSubtotalInclTax() > 0) {
351  $subtotalInclTax = $total->getSubtotalInclTax();
352  } else {
353  $subtotalInclTax = $total->getSubtotal() + $taxAmount - $total->getShippingTaxAmount();
354  }
355 
356  $totals[] = [
357  'code' => 'subtotal',
358  'title' => __('Subtotal'),
359  'value' => $subtotalInclTax,
360  'value_incl_tax' => $subtotalInclTax,
361  'value_excl_tax' => $total->getSubtotal(),
362  ];
363  }
364 
365  if (empty($totals)) {
366  return null;
367  }
368  return $totals;
369  }
370 
378  protected function enhanceTotalData(
379  \Magento\Quote\Model\Quote $quote,
381  ) {
382  $taxAmount = 0;
383  $shippingTaxAmount = 0;
384  $discountTaxCompensation = 0;
385 
386  $subtotalInclTax = $total->getSubtotalInclTax();
387  $computeSubtotalInclTax = true;
388  if ($total->getSubtotalInclTax() > 0) {
389  $computeSubtotalInclTax = false;
390  }
391 
393  foreach ($quote->getAllAddresses() as $address) {
394  $taxAmount += $address->getTaxAmount();
395  $shippingTaxAmount += $address->getShippingTaxAmount();
396  $discountTaxCompensation += $address->getDiscountTaxCompensationAmount();
397  if ($computeSubtotalInclTax) {
398  $subtotalInclTax += $address->getSubtotalInclTax();
399  }
400  }
401 
402  $total->setTaxAmount($taxAmount);
403  $total->setShippingTaxAmount($shippingTaxAmount);
404  $total->setDiscountTaxCompensationAmount($discountTaxCompensation); // accessed via 'discount_tax_compensation'
405  $total->setSubtotalInclTax($subtotalInclTax);
406  return;
407  }
408 
418  {
419  $calculationSequence = $this->_taxData->getCalculationSequence($store);
420  switch ($calculationSequence) {
422  $config['before'][] = 'discount';
423  break;
424  default:
425  $config['after'][] = 'discount';
426  break;
427  }
428  return $config;
429  }
430 
436  public function getLabel()
437  {
438  return __('Tax');
439  }
440 }
clearValues(Address\Total $total)
Definition: Tax.php:149
fetch(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Model\Quote\Address\Total $total)
Definition: Tax.php:318
$config
Definition: fraud_order.php:17
$quote
__()
Definition: __.php:13
getQuoteTaxDetails($shippingAssignment, $total, $useBaseCurrency)
Definition: Tax.php:182
$address
Definition: customer.php:38
$amount
Definition: order.php:14
convertAppliedTaxes($appliedTaxes, $baseAppliedTaxes, $extraInfo=[])
$totals
Definition: totalbar.phtml:10
mapItems(ShippingAssignmentInterface $shippingAssignment, $priceIncludesTax, $useBaseCurrency)
__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, \Magento\Tax\Helper\Data $taxData, Json $serializer=null)
Definition: Tax.php:69
mapQuoteExtraTaxables(\Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $itemDataObjectFactory, Address $address, $useBaseCurrency)
Definition: Tax.php:222
prepareQuoteDetails(ShippingAssignmentInterface $shippingAssignment, $itemDataObjects)
processShippingTaxInfo(ShippingAssignmentInterface $shippingAssignment, QuoteAddress\Total $total, $shippingTaxDetails, $baseShippingTaxDetails)
processConfigArray($config, $store)
Definition: Tax.php:417
collect(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment, \Magento\Quote\Model\Quote\Address\Total $total)
Definition: Tax.php:103