/** * {@inheritdoc} */ public function calculateTax(QuoteDetails $quoteDetails, $storeId = null) { if (is_null($storeId)) { $storeId = $this->storeManager->getStore()->getStoreId(); } // initial TaxDetails data $taxDetailsData = [TaxDetails::KEY_SUBTOTAL => 0.0, TaxDetails::KEY_TAX_AMOUNT => 0.0, TaxDetails::KEY_DISCOUNT_TAX_COMPENSATION_AMOUNT => 0.0, TaxDetails::KEY_APPLIED_TAXES => [], TaxDetails::KEY_ITEMS => []]; $items = $quoteDetails->getItems(); if (empty($items)) { return $this->taxDetailsBuilder->populateWithArray($taxDetailsData)->create(); } $this->computeRelationships($items); $addressRequest = $this->getAddressTaxRequest($quoteDetails, $storeId, $quoteDetails->getCustomerId()); if ($this->config->priceIncludesTax($storeId)) { $storeRequest = $this->getStoreTaxRequest($storeId); $classIds = []; foreach ($items as $item) { if ($item->getTaxClassId()) { $classIds[] = $item->getTaxClassId(); } } $classIds = array_unique($classIds); $addressRequest->setProductClassId($classIds); $storeRequest->setProductClassId($classIds); if ((bool) $this->config->crossBorderTradeEnabled($storeId)) { $addressRequest->setSameRateAsStore(true); } else { $addressRequest->setSameRateAsStore($this->calculator->compareRequests($storeRequest, $addressRequest)); } } // init rounding deltas for this quote $this->roundingDeltas = []; // init discount tax compensations array $this->discountTaxCompensations = []; $processedItems = []; /** @var QuoteDetailsItem $item */ foreach ($this->keyedItems as $item) { if (isset($this->parentToChildren[$item->getCode()])) { $processedChildren = []; foreach ($this->parentToChildren[$item->getCode()] as $child) { $processedItem = $this->processItem($child, $addressRequest, $storeId); $taxDetailsData = $this->aggregateItemData($taxDetailsData, $processedItem); $processedItems[$processedItem->getCode()] = $processedItem; $processedChildren[] = $processedItem; } $processedItemBuilder = $this->calculateParent($processedChildren, $item->getQuantity()); $processedItemBuilder->setCode($item->getCode()); $processedItemBuilder->setType($item->getType()); $processedItem = $processedItemBuilder->create(); } else { $processedItem = $this->processItem($item, $addressRequest, $storeId); $taxDetailsData = $this->aggregateItemData($taxDetailsData, $processedItem); } $processedItems[$processedItem->getCode()] = $processedItem; } return $this->taxDetailsBuilder->populateWithArray($taxDetailsData)->setItems($processedItems)->create(); }
/** * Call tax calculation service to get tax details on the quote and items * * @param Address $address * @param bool $useBaseCurrency * @return \Magento\Tax\Api\Data\TaxDetailsInterface */ protected function getQuoteTaxDetails($address, $useBaseCurrency) { //Setup taxable items $priceIncludesTax = $this->_config->priceIncludesTax($address->getQuote()->getStore()); $itemDataObjects = $this->mapItems($address, $priceIncludesTax, $useBaseCurrency); //Add shipping $shippingDataObject = $this->getShippingDataObject($address, $useBaseCurrency); if ($shippingDataObject != null) { $itemDataObjects[] = $shippingDataObject; } //process extra taxable items associated only with quote $quoteExtraTaxables = $this->mapQuoteExtraTaxables($this->quoteDetailsItemDataObjectFactory, $address, $useBaseCurrency); if (!empty($quoteExtraTaxables)) { $itemDataObjects = array_merge($itemDataObjects, $quoteExtraTaxables); } //Preparation for calling taxCalculationService $quoteDetails = $this->prepareQuoteDetails($address, $itemDataObjects); $taxDetails = $this->taxCalculationService->calculateTax($quoteDetails, $address->getQuote()->getStore()->getStoreId()); return $taxDetails; }
/** * Check if product prices inputed include tax * * @param null|int|string|Store $store * @return bool */ public function priceIncludesTax($store = null) { return $this->_config->priceIncludesTax($store) || $this->_config->getNeedUseShippingExcludeTax(); }
/** * Get product price with all tax settings processing * * @param \Magento\Catalog\Model\Product $product * @param float $price inputted product price * @param bool $includingTax return price include tax flag * @param null|\Magento\Customer\Model\Address\AbstractAddress $shippingAddress * @param null|\Magento\Customer\Model\Address\AbstractAddress $billingAddress * @param null|int $ctc customer tax class * @param null|string|bool|int|\Magento\Store\Model\Store $store * @param bool $priceIncludesTax flag what price parameter contain tax * @param bool $roundPrice * @return float * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function getTaxPrice($product, $price, $includingTax = null, $shippingAddress = null, $billingAddress = null, $ctc = null, $store = null, $priceIncludesTax = null, $roundPrice = true) { if (!$price) { return $price; } $store = $this->_storeManager->getStore($store); if ($this->_taxConfig->needPriceConversion($store)) { if ($priceIncludesTax === null) { $priceIncludesTax = $this->_taxConfig->priceIncludesTax($store); } $shippingAddressDataObject = null; if ($shippingAddress === null) { $shippingAddressDataObject = $this->convertDefaultTaxAddress($this->_customerSession->getDefaultTaxShippingAddress()); } elseif ($shippingAddress instanceof \Magento\Customer\Model\Address\AbstractAddress) { $shippingAddressDataObject = $shippingAddress->getDataModel(); } $billingAddressDataObject = null; if ($billingAddress === null) { $billingAddressDataObject = $this->convertDefaultTaxAddress($this->_customerSession->getDefaultTaxBillingAddress()); } elseif ($billingAddress instanceof \Magento\Customer\Model\Address\AbstractAddress) { $billingAddressDataObject = $billingAddress->getDataModel(); } $taxClassKey = $this->_taxClassKeyFactory->create(); $taxClassKey->setType(TaxClassKeyInterface::TYPE_ID)->setValue($product->getTaxClassId()); if ($ctc === null && $this->_customerSession->getCustomerGroupId() != null) { $ctc = $this->customerGroupRepository->getById($this->_customerSession->getCustomerGroupId())->getTaxClassId(); } $customerTaxClassKey = $this->_taxClassKeyFactory->create(); $customerTaxClassKey->setType(TaxClassKeyInterface::TYPE_ID)->setValue($ctc); $item = $this->_quoteDetailsItemFactory->create(); $item->setQuantity(1)->setCode($product->getSku())->setShortDescription($product->getShortDescription())->setTaxClassKey($taxClassKey)->setIsTaxIncluded($priceIncludesTax)->setType('product')->setUnitPrice($price); $quoteDetails = $this->_quoteDetailsFactory->create(); $quoteDetails->setShippingAddress($shippingAddressDataObject)->setBillingAddress($billingAddressDataObject)->setCustomerTaxClassKey($customerTaxClassKey)->setItems([$item])->setCustomerId($this->_customerSession->getCustomerId()); $storeId = null; if ($store) { $storeId = $store->getId(); } $taxDetails = $this->_taxCalculationService->calculateTax($quoteDetails, $storeId, $roundPrice); $items = $taxDetails->getItems(); $taxDetailsItem = array_shift($items); if ($includingTax !== null) { if ($includingTax) { $price = $taxDetailsItem->getPriceInclTax(); } else { $price = $taxDetailsItem->getPrice(); } } else { switch ($this->_taxConfig->getPriceDisplayType($store)) { case Config::DISPLAY_TYPE_EXCLUDING_TAX: case Config::DISPLAY_TYPE_BOTH: $price = $taxDetailsItem->getPrice(); break; case Config::DISPLAY_TYPE_INCLUDING_TAX: $price = $taxDetailsItem->getPriceInclTax(); break; default: break; } } } if ($roundPrice) { return $this->priceCurrency->round($price); } else { return $price; } }
/** * @magentoConfigFixture current_store tax/calculation/price_includes_tax 1 */ public function testPriceIncludesTaxNonDefault() { $this->assertTrue($this->_model->priceIncludesTax()); }
/** * Get product price with all tax settings processing * * @param \Magento\Catalog\Model\Product $product * @param float $price inputted product price * @param bool $includingTax return price include tax flag * @param null|Address $shippingAddress * @param null|Address $billingAddress * @param null|int $ctc customer tax class * @param null|string|bool|int|Store $store * @param bool $priceIncludesTax flag what price parameter contain tax * @param bool $roundPrice * @return float */ public function getTaxPrice($product, $price, $includingTax = null, $shippingAddress = null, $billingAddress = null, $ctc = null, $store = null, $priceIncludesTax = null, $roundPrice = true) { if (!$price) { return $price; } $store = $this->_storeManager->getStore($store); if ($this->_taxConfig->needPriceConversion($store)) { if (is_null($priceIncludesTax)) { $priceIncludesTax = $this->_taxConfig->priceIncludesTax($store); } $shippingAddressDataObject = null; if ($shippingAddress instanceof \Magento\Customer\Model\Address\AbstractAddress) { $shippingAddressDataObject = $this->_addressConverter->createAddressFromModel($shippingAddress, null, null); } $billingAddressDataObject = null; if ($billingAddress instanceof \Magento\Customer\Model\Address\AbstractAddress) { $billingAddressDataObject = $this->_addressConverter->createAddressFromModel($billingAddress, null, null); } $item = $this->_quoteDetailsItemBuilder->setQuantity(1)->setCode($product->getSku())->setShortDescription($product->getShortDescription())->setTaxClassKey($this->_taxClassKeyBuilder->setType(TaxClassKey::TYPE_ID)->setValue($product->getTaxClassId())->create())->setTaxIncluded($priceIncludesTax)->setType('product')->setUnitPrice($price)->create(); $quoteDetails = $this->_quoteDetailsBuilder->setShippingAddress($shippingAddressDataObject)->setBillingAddress($billingAddressDataObject)->setCustomerTaxClassKey($this->_taxClassKeyBuilder->setType(TaxClassKey::TYPE_ID)->setValue($ctc)->create())->setItems([$item])->setCustomerId($this->_customerSession->getCustomerId())->create(); $storeId = null; if ($store) { $storeId = $store->getId(); } $taxDetails = $this->_taxCalculationService->calculateTax($quoteDetails, $storeId); $items = $taxDetails->getItems(); $taxDetailsItem = array_shift($items); if (!is_null($includingTax)) { if ($includingTax) { $price = $taxDetailsItem->getPriceInclTax(); } else { $price = $taxDetailsItem->getPrice(); } } else { switch ($this->_taxConfig->getPriceDisplayType($store)) { case Config::DISPLAY_TYPE_EXCLUDING_TAX: case Config::DISPLAY_TYPE_BOTH: $price = $taxDetailsItem->getPrice(); break; case Config::DISPLAY_TYPE_INCLUDING_TAX: $price = $taxDetailsItem->getPriceInclTax(); break; default: break; } } } if ($roundPrice) { return $store->roundPrice($price); } else { return $price; } }
/** * Populate QuoteDetails object from Address object * * @param Address $address * @param bool $useBaseCurrency * @return \Magento\Tax\Service\V1\Data\QuoteDetails */ protected function prepareQuoteDetails(Address $address, $useBaseCurrency) { $items = $this->_getAddressItems($address); if (!count($items)) { return $this->quoteDetailsBuilder->create(); } $addressBuilder = $this->quoteDetailsBuilder->getAddressBuilder(); //Set billing address $this->quoteDetailsBuilder->setBillingAddress($this->mapAddress($addressBuilder, $address->getQuote()->getBillingAddress())); //Set shipping address $this->quoteDetailsBuilder->setShippingAddress($this->mapAddress($addressBuilder, $address)); //Set customer tax class $this->quoteDetailsBuilder->setCustomerTaxClassId($address->getQuote()->getCustomerTaxClassId()); //Populate with items $priceIncludesTax = $this->_config->priceIncludesTax($this->_store); $itemBuilder = $this->quoteDetailsBuilder->getItemBuilder(); $itemDataObjects = []; foreach ($items as $item) { if ($item->getParentItem()) { continue; } if ($item->getHasChildren() && $item->isChildrenCalculated()) { $parentItemDataObject = $this->mapItem($itemBuilder, $item, $priceIncludesTax, $useBaseCurrency); $itemDataObjects[] = $parentItemDataObject; foreach ($item->getChildren() as $child) { $childItemDataObject = $this->mapItem($itemBuilder, $child, $priceIncludesTax, $useBaseCurrency, $parentItemDataObject->getCode()); $itemDataObjects[] = $childItemDataObject; } } else { $itemDataObject = $this->mapItem($itemBuilder, $item, $priceIncludesTax, $useBaseCurrency); $itemDataObjects[] = $itemDataObject; } } if ($this->includeShipping()) { //Add shipping as an item if (!$address->getShippingTaxCalculationAmount() || $address->getShippingTaxCalculationAmount() <= 0) { //Save the original shipping amount because shipping amount will be overridden //with shipping amount excluding tax $address->setShippingTaxCalculationAmount($address->getShippingAmount()); $address->setBaseShippingTaxCalculationAmount($address->getBaseShippingAmount()); } if ($address->getShippingTaxCalculationAmount() > 0) { $itemBuilder->setType(self::SHIPPING_ITEM_TYPE); $itemBuilder->setCode(self::SHIPPING_ITEM_CODE); $itemBuilder->setQuantity(1); if ($useBaseCurrency) { $itemBuilder->setUnitPrice($address->getBaseShippingTaxCalculationAmount()); } else { $itemBuilder->setUnitPrice($address->getShippingTaxCalculationAmount()); } if ($address->getShippingDiscountAmount()) { if ($useBaseCurrency) { $itemBuilder->setDiscountAmount($address->getBaseShippingDiscountAmount()); } else { $itemBuilder->setDiscountAmount($address->getShippingDiscountAmount()); } } $itemBuilder->setTaxClassId($this->_config->getShippingTaxClass($this->_store)); $itemBuilder->setTaxIncluded($this->_config->shippingPriceIncludesTax($this->_store)); $itemDataObjects[] = $itemBuilder->create(); } } $this->quoteDetailsBuilder->setItems($itemDataObjects); $quoteDetails = $this->quoteDetailsBuilder->create(); return $quoteDetails; }