/** * @param array $addressData * @param bool $useBaseCurrency * @param string $shippingTaxClass * @param bool $shippingPriceInclTax * @dataProvider getShippingDataObjectDataProvider */ public function testGetShippingDataObject(array $addressData, $useBaseCurrency, $shippingTaxClass, $shippingPriceInclTax) { $shippingAssignmentMock = $this->getMock('Magento\\Quote\\Api\\Data\\ShippingAssignmentInterface'); $methods = ['getShippingDiscountAmount', 'getShippingTaxCalculationAmount', 'setShippingTaxCalculationAmount', 'getShippingAmount', 'setBaseShippingTaxCalculationAmount', 'getBaseShippingAmount', 'getBaseShippingDiscountAmount']; $totalsMock = $this->getMock('Magento\\Quote\\Model\\Quote\\Address\\Total', $methods, [], '', false); $shippingMock = $this->getMock('Magento\\Quote\\Api\\Data\\ShippingInterface'); $shippingAssignmentMock->expects($this->once())->method('getShipping')->willReturn($shippingMock); $shippingMock->expects($this->once())->method('getAddress')->willReturn($this->address); $baseShippingAmount = $addressData['base_shipping_amount']; $shippingAmount = $addressData['shipping_amount']; $totalsMock->expects($this->any())->method('getShippingTaxCalculationAmount')->willReturn($shippingAmount); $this->taxConfig->expects($this->any())->method('getShippingTaxClass')->with($this->store)->will($this->returnValue($shippingTaxClass)); $this->taxConfig->expects($this->any())->method('shippingPriceIncludesTax')->with($this->store)->will($this->returnValue($shippingPriceInclTax)); $totalsMock->expects($this->atLeastOnce())->method('getShippingDiscountAmount')->willReturn($shippingAmount); if ($shippingAmount) { if ($useBaseCurrency && $shippingAmount != 0) { $totalsMock->expects($this->once())->method('getBaseShippingDiscountAmount')->willReturn($baseShippingAmount); $expectedDiscountAmount = $baseShippingAmount; } else { $totalsMock->expects($this->never())->method('getBaseShippingDiscountAmount'); $expectedDiscountAmount = $shippingAmount; } } foreach ($addressData as $key => $value) { $totalsMock->setData($key, $value); } $this->assertEquals($this->quoteDetailsItemDataObject, $this->commonTaxCollector->getShippingDataObject($shippingAssignmentMock, $totalsMock, $useBaseCurrency)); if ($shippingAmount) { $this->assertEquals($expectedDiscountAmount, $this->quoteDetailsItemDataObject->getDiscountAmount()); } }
/** * {@inheritdoc} */ protected function calculateWithTaxNotInPrice(QuoteDetailsItemInterface $item, $quantity, $round = true) { $taxRateRequest = $this->getAddressRateRequest()->setProductClassId($this->taxClassManagement->getTaxClassId($item->getTaxClassKey())); $rate = $this->calculationTool->getRate($taxRateRequest); $appliedRates = $this->calculationTool->getAppliedRates($taxRateRequest); $applyTaxAfterDiscount = $this->config->applyTaxAfterDiscount($this->storeId); $discountAmount = $item->getDiscountAmount(); $discountTaxCompensationAmount = 0; // Calculate $rowTotal $price = $this->calculationTool->round($item->getUnitPrice()); $rowTotal = $price * $quantity; $rowTaxes = []; $rowTaxesBeforeDiscount = []; $appliedTaxes = []; //Apply each tax rate separately foreach ($appliedRates as $appliedRate) { $taxId = $appliedRate['id']; $taxRate = $appliedRate['percent']; $rowTaxPerRate = $this->calculationTool->calcTaxAmount($rowTotal, $taxRate, false, false); $deltaRoundingType = self::KEY_REGULAR_DELTA_ROUNDING; if ($applyTaxAfterDiscount) { $deltaRoundingType = self::KEY_TAX_BEFORE_DISCOUNT_DELTA_ROUNDING; } $rowTaxPerRate = $this->roundAmount($rowTaxPerRate, $taxId, false, $deltaRoundingType, $round); $rowTaxAfterDiscount = $rowTaxPerRate; //Handle discount if ($applyTaxAfterDiscount) { //TODO: handle originalDiscountAmount $taxableAmount = max($rowTotal - $discountAmount, 0); $rowTaxAfterDiscount = $this->calculationTool->calcTaxAmount($taxableAmount, $taxRate, false, false); $rowTaxAfterDiscount = $this->roundAmount($rowTaxAfterDiscount, $taxId, false, self::KEY_REGULAR_DELTA_ROUNDING, $round); } $appliedTaxes[$taxId] = $this->getAppliedTax($rowTaxAfterDiscount, $appliedRate); $rowTaxes[] = $rowTaxAfterDiscount; $rowTaxesBeforeDiscount[] = $rowTaxPerRate; } $rowTax = array_sum($rowTaxes); $rowTaxBeforeDiscount = array_sum($rowTaxesBeforeDiscount); $rowTotalInclTax = $rowTotal + $rowTaxBeforeDiscount; $priceInclTax = $rowTotalInclTax / $quantity; if ($round) { $priceInclTax = $this->calculationTool->round($priceInclTax); } return $this->taxDetailsItemDataObjectFactory->create()->setCode($item->getCode())->setType($item->getType())->setRowTax($rowTax)->setPrice($price)->setPriceInclTax($priceInclTax)->setRowTotal($rowTotal)->setRowTotalInclTax($rowTotalInclTax)->setDiscountTaxCompensationAmount($discountTaxCompensationAmount)->setAssociatedItemCode($item->getAssociatedItemCode())->setTaxPercent($rate)->setAppliedTaxes($appliedTaxes); }
/** * @param array $addressData * @param bool $useBaseCurrency * @param string $shippingTaxClass * @param bool $shippingPriceInclTax * @dataProvider getShippingDataObjectDataProvider */ public function testGetShippingDataObject(array $addressData, $useBaseCurrency, $shippingTaxClass, $shippingPriceInclTax) { $baseShippingAmount = $addressData['base_shipping_amount']; $shippingAmount = $addressData['shipping_amount']; $this->taxConfig->expects($this->any())->method('getShippingTaxClass')->with($this->store)->will($this->returnValue($shippingTaxClass)); $this->taxConfig->expects($this->any())->method('shippingPriceIncludesTax')->with($this->store)->will($this->returnValue($shippingPriceInclTax)); $this->address->expects($this->atLeastOnce())->method('getShippingDiscountAmount')->willReturn($shippingAmount); if ($shippingAmount) { if ($useBaseCurrency && $shippingAmount != 0) { $this->address->expects($this->once())->method('getBaseShippingDiscountAmount')->willReturn($baseShippingAmount); $expectedDiscountAmount = $baseShippingAmount; } else { $this->address->expects($this->never())->method('getBaseShippingDiscountAmount'); $expectedDiscountAmount = $shippingAmount; } } foreach ($addressData as $key => $value) { $this->address->setData($key, $value); } $this->assertEquals($this->quoteDetailsItemDataObject, $this->commonTaxCollector->getShippingDataObject($this->address, $useBaseCurrency)); if ($shippingAmount) { $this->assertEquals($expectedDiscountAmount, $this->quoteDetailsItemDataObject->getDiscountAmount()); } }
/** * {@inheritdoc} */ protected function calculateWithTaxNotInPrice(QuoteDetailsItemInterface $item, $quantity, $round = true) { $taxRateRequest = $this->getAddressRateRequest()->setProductClassId($this->taxClassManagement->getTaxClassId($item->getTaxClassKey())); $rate = $this->calculationTool->getRate($taxRateRequest); $appliedRates = $this->calculationTool->getAppliedRates($taxRateRequest); $applyTaxAfterDiscount = $this->config->applyTaxAfterDiscount($this->storeId); $discountAmount = $item->getDiscountAmount(); $discountTaxCompensationAmount = 0; // Calculate $price $price = $this->calculationTool->round($item->getUnitPrice()); $unitTaxes = []; $unitTaxesBeforeDiscount = []; $appliedTaxes = []; //Apply each tax rate separately foreach ($appliedRates as $appliedRate) { $taxId = $appliedRate['id']; $taxRate = $appliedRate['percent']; $unitTaxPerRate = $this->calculationTool->calcTaxAmount($price, $taxRate, false); $unitTaxAfterDiscount = $unitTaxPerRate; //Handle discount if ($discountAmount && $applyTaxAfterDiscount) { //TODO: handle originalDiscountAmount $unitDiscountAmount = $discountAmount / $quantity; $taxableAmount = max($price - $unitDiscountAmount, 0); $unitTaxAfterDiscount = $this->calculationTool->calcTaxAmount($taxableAmount, $taxRate, false, true); } $appliedTaxes[$taxId] = $this->getAppliedTax($unitTaxAfterDiscount * $quantity, $appliedRate); $unitTaxes[] = $unitTaxAfterDiscount; $unitTaxesBeforeDiscount[] = $unitTaxPerRate; } $unitTax = array_sum($unitTaxes); $unitTaxBeforeDiscount = array_sum($unitTaxesBeforeDiscount); $rowTax = $unitTax * $quantity; $priceInclTax = $price + $unitTaxBeforeDiscount; return $this->taxDetailsItemDataObjectFactory->create()->setCode($item->getCode())->setType($item->getType())->setRowTax($rowTax)->setPrice($price)->setPriceInclTax($priceInclTax)->setRowTotal($price * $quantity)->setRowTotalInclTax($priceInclTax * $quantity)->setDiscountTaxCompensationAmount($discountTaxCompensationAmount)->setAssociatedItemCode($item->getAssociatedItemCode())->setTaxPercent($rate)->setAppliedTaxes($appliedTaxes); }
/** * Convert \Magento\Tax\Model\Sales\Quote\ItemDetails to an array to be used for building an \AvaTax\Line object * * @param \Magento\Tax\Api\Data\QuoteDetailsItemInterface $item * @return array */ protected function convertTaxQuoteDetailsItemToData(\Magento\Tax\Api\Data\QuoteDetailsItemInterface $item) { $extensionAttributes = $item->getExtensionAttributes(); if ($extensionAttributes) { $quantity = $extensionAttributes->getTotalQuantity() !== null ? $extensionAttributes->getTotalQuantity() : $item->getQuantity(); } else { $quantity = $item->getQuantity(); } $itemCode = $extensionAttributes ? $extensionAttributes->getAvataxItemCode() : ''; $description = $extensionAttributes ? $extensionAttributes->getAvataxDescription() : ''; $taxCode = $extensionAttributes ? $extensionAttributes->getAvataxTaxCode() : null; // The AvaTax 15 API doesn't support the concept of line-based discounts, so subtract discount amount // from taxable amount $amount = $item->getUnitPrice() * $quantity - $item->getDiscountAmount(); $ref1 = $extensionAttributes ? $extensionAttributes->getAvataxRef1() : null; $ref2 = $extensionAttributes ? $extensionAttributes->getAvataxRef2() : null; return ['No' => $item->getCode(), 'ItemCode' => $itemCode, 'TaxCode' => $taxCode, 'Description' => $description, 'Qty' => $item->getQuantity(), 'Amount' => $amount, 'Discounted' => (bool) ($item->getDiscountAmount() > 0), 'TaxIncluded' => false, 'Ref1' => $ref1, 'Ref2' => $ref2]; }
/** * Calculates the total quantity for this item. * * What this really means is that if this is a child item, it return the parent quantity times * the child quantity and return that as the child's quantity. * * @param QuoteDetailsItemInterface $item * @return float */ protected function getTotalQuantity(QuoteDetailsItemInterface $item) { if ($item->getParentCode()) { $parentQuantity = $this->keyedItems[$item->getParentCode()]->getQuantity(); return $parentQuantity * $item->getQuantity(); } return $item->getQuantity(); }
/** * Calculate tax details for quote item with given quantity * * @param QuoteDetailsItemInterface $item * @param int $quantity * @param bool $round * @return TaxDetailsItemInterface */ public function calculate(QuoteDetailsItemInterface $item, $quantity, $round = true) { if ($item->getIsTaxIncluded()) { return $this->calculateWithTaxInPrice($item, $quantity, $round); } else { return $this->calculateWithTaxNotInPrice($item, $quantity, $round); } }
/** * Add AvaTax specific extension attribute fields to a \Magento\Tax\Model\Sales\Quote\ItemDetails object * * @param \Magento\Tax\Api\Data\QuoteDetailsItemInterface $quoteDetailsItem * @param $avaTaxItemCode * @param $avaTaxTaxCode * @param $avaTaxDescription * @return $this */ protected function addExtensionAttributesToTaxQuoteDetailsItem(\Magento\Tax\Api\Data\QuoteDetailsItemInterface $quoteDetailsItem, $avaTaxItemCode, $avaTaxTaxCode, $avaTaxDescription) { /** @var \Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterface $extensionAttribute */ $extensionAttribute = $quoteDetailsItem->getExtensionAttributes() ? $quoteDetailsItem->getExtensionAttributes() : $this->extensionFactory->create(); $extensionAttribute->setAvataxItemCode($avaTaxItemCode); $extensionAttribute->setAvataxTaxCode($avaTaxTaxCode); $extensionAttribute->setAvataxDescription($avaTaxDescription); $quoteDetailsItem->setExtensionAttributes($extensionAttribute); return $this; }
/** * Calculates the total quantity for this item. * * What this really means is that if this is a child item, it return the parent quantity times * the child quantity and return that as the child's quantity. This code is a duplicate of the * @see \Magento\Tax\Model\TaxCalculation::getTotalQuantity() * method, but is refactored to accept the $keyedItems array. * * @param QuoteDetailsItemInterface $item * @param array $keyedItems * @return float */ public function calculateTotalQuantity(QuoteDetailsItemInterface $item, array $keyedItems) { if ($item->getParentCode()) { $parentQuantity = $keyedItems[$item->getParentCode()]->getQuantity(); return $parentQuantity * $item->getQuantity(); } return $item->getQuantity(); }