123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Tax\Model;
- use Magento\Customer\Api\AccountManagementInterface as CustomerAccountManagement;
- use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository;
- use Magento\Customer\Api\Data\AddressInterface as CustomerAddress;
- use Magento\Customer\Api\Data\CustomerInterface as CustomerDataObject;
- use Magento\Customer\Api\Data\RegionInterface as AddressRegion;
- use Magento\Customer\Api\GroupManagementInterface as CustomerGroupManagement;
- use Magento\Customer\Api\GroupRepositoryInterface as CustomerGroupRepository;
- use Magento\Framework\Api\FilterBuilder;
- use Magento\Framework\Api\SearchCriteriaBuilder;
- use Magento\Framework\Exception\NoSuchEntityException;
- use Magento\Framework\Pricing\PriceCurrencyInterface;
- use Magento\Store\Model\Store;
- use Magento\Tax\Api\TaxClassRepositoryInterface;
- /**
- * Tax Calculation Model
- * @SuppressWarnings(PHPMD.TooManyFields)
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- */
- class Calculation extends \Magento\Framework\Model\AbstractModel
- {
- /**
- * Identifier constant for Tax calculation before discount excluding TAX
- */
- const CALC_TAX_BEFORE_DISCOUNT_ON_EXCL = '0_0';
- /**
- * Identifier constant for Tax calculation before discount including TAX
- */
- const CALC_TAX_BEFORE_DISCOUNT_ON_INCL = '0_1';
- /**
- * Identifier constant for Tax calculation after discount excluding TAX
- */
- const CALC_TAX_AFTER_DISCOUNT_ON_EXCL = '1_0';
- /**
- * Identifier constant for Tax calculation after discount including TAX
- */
- const CALC_TAX_AFTER_DISCOUNT_ON_INCL = '1_1';
- /**
- * Identifier constant for unit based calculation
- */
- const CALC_UNIT_BASE = 'UNIT_BASE_CALCULATION';
- /**
- * Identifier constant for row based calculation
- */
- const CALC_ROW_BASE = 'ROW_BASE_CALCULATION';
- /**
- * Identifier constant for total based calculation
- */
- const CALC_TOTAL_BASE = 'TOTAL_BASE_CALCULATION';
- /**
- * Identifier constant for unit based calculation
- *
- * @var array
- */
- protected $_rates = [];
- /**
- * Identifier constant for row based calculation
- *
- * @var array
- */
- protected $_ctc = [];
- /**
- * Identifier constant for total based calculation
- *
- * @var array
- */
- protected $_ptc = [];
- /**
- * Cache to hold the rates
- *
- * @var array
- */
- protected $_rateCache = [];
- /**
- * Store the rate calculation process
- *
- * @var array
- */
- protected $_rateCalculationProcess = [];
- /**
- * Hold the customer
- *
- * @var CustomerDataObject|bool
- */
- protected $_customer;
- /**
- * @var int
- */
- protected $_defaultCustomerTaxClass;
- /**
- * Core store config
- *
- * @var \Magento\Framework\App\Config\ScopeConfigInterface
- */
- protected $_scopeConfig;
- /**
- * @var \Magento\Store\Model\StoreManagerInterface
- */
- protected $_storeManager;
- /**
- * @var \Magento\Customer\Model\Session
- */
- protected $_customerSession;
- /**
- * @var \Magento\Customer\Model\CustomerFactory
- */
- protected $_customerFactory;
- /**
- * @var \Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory
- */
- protected $_classesFactory;
- /**
- * Tax configuration object
- *
- * @var Config
- */
- protected $_config;
- /**
- * @var CustomerAccountManagement
- */
- protected $customerAccountManagement;
- /**
- * @var CustomerGroupManagement
- */
- protected $customerGroupManagement;
- /**
- * @var CustomerGroupRepository
- */
- protected $customerGroupRepository;
- /**
- * @var CustomerRepository
- */
- protected $customerRepository;
- /**
- * @var PriceCurrencyInterface
- */
- protected $priceCurrency;
- /**
- * Filter Builder
- *
- * @var FilterBuilder
- */
- protected $filterBuilder;
- /**
- * Search Criteria Builder
- *
- * @var SearchCriteriaBuilder
- */
- protected $searchCriteriaBuilder;
- /**
- * Tax Class Repository
- *
- * @var TaxClassRepositoryInterface
- */
- protected $taxClassRepository;
- /**
- * @param \Magento\Framework\Model\Context $context
- * @param \Magento\Framework\Registry $registry
- * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
- * @param Config $taxConfig
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Customer\Model\Session $customerSession
- * @param \Magento\Customer\Model\CustomerFactory $customerFactory
- * @param \Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory $classesFactory
- * @param \Magento\Tax\Model\ResourceModel\Calculation $resource
- * @param CustomerAccountManagement $customerAccountManagement
- * @param CustomerGroupManagement $customerGroupManagement
- * @param CustomerGroupRepository $customerGroupRepository
- * @param CustomerRepository $customerRepository
- * @param PriceCurrencyInterface $priceCurrency
- * @param SearchCriteriaBuilder $searchCriteriaBuilder
- * @param FilterBuilder $filterBuilder
- * @param TaxClassRepositoryInterface $taxClassRepository
- * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
- * @param array $data
- * @SuppressWarnings(PHPMD.ExcessiveParameterList)
- */
- public function __construct(
- \Magento\Framework\Model\Context $context,
- \Magento\Framework\Registry $registry,
- \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
- Config $taxConfig,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Customer\Model\Session $customerSession,
- \Magento\Customer\Model\CustomerFactory $customerFactory,
- \Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory $classesFactory,
- \Magento\Tax\Model\ResourceModel\Calculation $resource,
- CustomerAccountManagement $customerAccountManagement,
- CustomerGroupManagement $customerGroupManagement,
- CustomerGroupRepository $customerGroupRepository,
- CustomerRepository $customerRepository,
- PriceCurrencyInterface $priceCurrency,
- SearchCriteriaBuilder $searchCriteriaBuilder,
- FilterBuilder $filterBuilder,
- TaxClassRepositoryInterface $taxClassRepository,
- \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
- ) {
- $this->_scopeConfig = $scopeConfig;
- $this->_config = $taxConfig;
- $this->_storeManager = $storeManager;
- $this->_customerSession = $customerSession;
- $this->_customerFactory = $customerFactory;
- $this->_classesFactory = $classesFactory;
- $this->customerAccountManagement = $customerAccountManagement;
- $this->customerGroupManagement = $customerGroupManagement;
- $this->customerGroupRepository = $customerGroupRepository;
- $this->customerRepository = $customerRepository;
- $this->priceCurrency = $priceCurrency;
- $this->searchCriteriaBuilder = $searchCriteriaBuilder;
- $this->filterBuilder = $filterBuilder;
- $this->taxClassRepository = $taxClassRepository;
- parent::__construct($context, $registry, $resource, $resourceCollection, $data);
- }
- /**
- * @return void
- */
- protected function _construct()
- {
- $this->_init(\Magento\Tax\Model\ResourceModel\Calculation::class);
- }
- /**
- * Fetch default customer tax class
- *
- * @param null|Store|string|int $store
- * @return int
- */
- public function getDefaultCustomerTaxClass($store = null)
- {
- if ($this->_defaultCustomerTaxClass === null) {
- //Not catching the exception here since default group is expected
- $defaultCustomerGroup = $this->customerGroupManagement->getDefaultGroup($store);
- $this->_defaultCustomerTaxClass = $defaultCustomerGroup->getTaxClassId();
- }
- return $this->_defaultCustomerTaxClass;
- }
- /**
- * Delete calculation settings by rule id
- *
- * @param int $ruleId
- * @return $this
- */
- public function deleteByRuleId($ruleId)
- {
- $this->_getResource()->deleteByRuleId($ruleId);
- return $this;
- }
- /**
- * Get calculation rates by rule id
- *
- * @param int $ruleId
- * @return array
- */
- public function getRates($ruleId)
- {
- if (!isset($this->_rates[$ruleId])) {
- $this->_rates[$ruleId] = $this->_getResource()->getCalculationsById('tax_calculation_rate_id', $ruleId);
- }
- return $this->_rates[$ruleId];
- }
- /**
- * Get allowed customer tax classes by rule id
- *
- * @param int $ruleId
- * @return array
- */
- public function getCustomerTaxClasses($ruleId)
- {
- if (!isset($this->_ctc[$ruleId])) {
- $this->_ctc[$ruleId] = $this->_getResource()->getCalculationsById('customer_tax_class_id', $ruleId);
- }
- return $this->_ctc[$ruleId];
- }
- /**
- * Get allowed product tax classes by rule id
- *
- * @param int $ruleId
- * @return array
- */
- public function getProductTaxClasses($ruleId)
- {
- if (!isset($this->_ptc[$ruleId])) {
- $this->_ptc[$ruleId] = $this->getResource()->getCalculationsById('product_tax_class_id', $ruleId);
- }
- return $this->_ptc[$ruleId];
- }
- /**
- * Aggregate tax calculation data to array
- *
- * @return array
- */
- protected function _formCalculationProcess()
- {
- $title = $this->getRateTitle();
- $value = $this->getRateValue();
- $id = $this->getRateId();
- $rate = ['code' => $title, 'title' => $title, 'percent' => $value, 'position' => 1, 'priority' => 1];
- $process = [];
- $process['percent'] = $value;
- $process['id'] = "{$id}-{$value}";
- $process['rates'][] = $rate;
- return [$process];
- }
- /**
- * Get calculation tax rate by specific request
- *
- * @param \Magento\Framework\DataObject $request
- * @return float
- */
- public function getRate($request)
- {
- if (!$request->getCountryId() || !$request->getCustomerClassId() || !$request->getProductClassId()) {
- return 0;
- }
- $cacheKey = $this->_getRequestCacheKey($request);
- if (!isset($this->_rateCache[$cacheKey])) {
- $this->unsRateValue();
- $this->unsCalculationProcess();
- $this->unsEventModuleId();
- $this->_eventManager->dispatch('tax_rate_data_fetch', ['request' => $request, 'sender' => $this]);
- if (!$this->hasRateValue()) {
- $rateInfo = $this->_getResource()->getRateInfo($request);
- $this->setCalculationProcess($rateInfo['process']);
- $this->setRateValue($rateInfo['value']);
- } else {
- $this->setCalculationProcess($this->_formCalculationProcess());
- }
- $this->_rateCache[$cacheKey] = $this->getRateValue();
- $this->_rateCalculationProcess[$cacheKey] = $this->getCalculationProcess();
- }
- return $this->_rateCache[$cacheKey];
- }
- /**
- * Get cache key value for specific tax rate request
- *
- * @param \Magento\Framework\DataObject $request
- * @return string
- */
- protected function _getRequestCacheKey($request)
- {
- $store = $request->getStore();
- $key = '';
- if ($store instanceof \Magento\Store\Model\Store) {
- $key = $store->getId() . '|';
- } elseif (is_numeric($store)) {
- $key = $store . '|';
- }
- $key .= $request->getProductClassId() . '|'
- . $request->getCustomerClassId() . '|'
- . $request->getCountryId() . '|'
- . $request->getRegionId() . '|'
- . $request->getPostcode();
- return $key;
- }
- /**
- * Get tax rate based on store shipping origin address settings
- * This rate can be used for conversion store price including tax to
- * store price excluding tax
- *
- * @param \Magento\Framework\DataObject $request
- * @param null|string|bool|int|Store $store
- * @return float
- */
- public function getStoreRate($request, $store = null)
- {
- $storeRequest = $this->getRateOriginRequest($store)->setProductClassId($request->getProductClassId());
- return $this->getRate($storeRequest);
- }
- /**
- * Get request object for getting tax rate based on store shipping original address
- *
- * @param null|string|bool|int|Store $store
- * @return \Magento\Framework\DataObject
- */
- protected function getRateOriginRequest($store = null)
- {
- $request = new \Magento\Framework\DataObject();
- $request->setCountryId(
- $this->_scopeConfig->getValue(
- \Magento\Shipping\Model\Config::XML_PATH_ORIGIN_COUNTRY_ID,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $store
- )
- )->setRegionId(
- $this->_scopeConfig->getValue(
- \Magento\Shipping\Model\Config::XML_PATH_ORIGIN_REGION_ID,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $store
- )
- )->setPostcode(
- $this->_scopeConfig->getValue(
- \Magento\Shipping\Model\Config::XML_PATH_ORIGIN_POSTCODE,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $store
- )
- )->setCustomerClassId(
- $this->getDefaultCustomerTaxClass($store)
- )->setStore(
- $store
- );
- return $request;
- }
- /**
- * Return the default rate request. It can be either based on store address or customer address
- *
- * @param null|int|string|Store $store
- * @param int $customerId
- * @return \Magento\Framework\DataObject
- */
- public function getDefaultRateRequest($store = null, $customerId = null)
- {
- if ($this->_isCrossBorderTradeEnabled($store)) {
- //If cross border trade is enabled, we will use customer tax rate as store tax rate
- return $this->getRateRequest(null, null, null, $store, $customerId);
- } else {
- return $this->getRateOriginRequest($store);
- }
- }
- /**
- * Return whether cross border trade is enabled or not
- *
- * @param null|int|string|Store $store
- * @return bool
- */
- protected function _isCrossBorderTradeEnabled($store = null)
- {
- return (bool)$this->_config->crossBorderTradeEnabled($store);
- }
- /**
- * Get request object with information necessary for getting tax rate
- *
- * Request object contain:
- * country_id (->getCountryId())
- * region_id (->getRegionId())
- * postcode (->getPostcode())
- * customer_class_id (->getCustomerClassId())
- * store (->getStore())
- *
- * @param null|bool|\Magento\Framework\DataObject|CustomerAddress $shippingAddress
- * @param null|bool|\Magento\Framework\DataObject|CustomerAddress $billingAddress
- * @param null|int $customerTaxClass
- * @param null|int|\Magento\Store\Model\Store $store
- * @param int $customerId
- * @return \Magento\Framework\DataObject
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- public function getRateRequest(
- $shippingAddress = null,
- $billingAddress = null,
- $customerTaxClass = null,
- $store = null,
- $customerId = null
- ) {
- if ($shippingAddress === false && $billingAddress === false && $customerTaxClass === false) {
- return $this->getRateOriginRequest($store);
- }
- $address = new \Magento\Framework\DataObject();
- $basedOn = $this->_scopeConfig->getValue(
- \Magento\Tax\Model\Config::CONFIG_XML_PATH_BASED_ON,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $store
- );
- if ($shippingAddress === false && $basedOn == 'shipping' || $billingAddress === false && $basedOn == 'billing'
- ) {
- $basedOn = 'default';
- } else {
- if (($billingAddress === null || !$billingAddress->getCountryId())
- && $basedOn == 'billing'
- || ($shippingAddress === null || !$shippingAddress->getCountryId())
- && $basedOn == 'shipping'
- ) {
- if ($customerId) {
- //fallback to default address for registered customer
- try {
- $defaultBilling = $this->customerAccountManagement->getDefaultBillingAddress($customerId);
- } catch (NoSuchEntityException $e) {
- }
- try {
- $defaultShipping = $this->customerAccountManagement->getDefaultShippingAddress($customerId);
- } catch (NoSuchEntityException $e) {
- }
- if ($basedOn == 'billing' && isset($defaultBilling) && $defaultBilling->getCountryId()) {
- $billingAddress = $defaultBilling;
- } elseif ($basedOn == 'shipping' && isset($defaultShipping) && $defaultShipping->getCountryId()) {
- $shippingAddress = $defaultShipping;
- } else {
- $basedOn = 'default';
- }
- } else {
- //fallback for guest
- if ($basedOn == 'billing' && is_object($shippingAddress) && $shippingAddress->getCountryId()) {
- $billingAddress = $shippingAddress;
- } elseif ($basedOn == 'shipping' && is_object($billingAddress) && $billingAddress->getCountryId()) {
- $shippingAddress = $billingAddress;
- } else {
- $basedOn = 'default';
- }
- }
- }
- }
- switch ($basedOn) {
- case 'billing':
- $address = $billingAddress;
- break;
- case 'shipping':
- $address = $shippingAddress;
- break;
- case 'origin':
- $address = $this->getRateOriginRequest($store);
- break;
- case 'default':
- $address->setCountryId(
- $this->_scopeConfig->getValue(
- \Magento\Tax\Model\Config::CONFIG_XML_PATH_DEFAULT_COUNTRY,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $store
- )
- )->setRegionId(
- $this->_scopeConfig->getValue(
- \Magento\Tax\Model\Config::CONFIG_XML_PATH_DEFAULT_REGION,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $store
- )
- )->setPostcode(
- $this->_scopeConfig->getValue(
- \Magento\Tax\Model\Config::CONFIG_XML_PATH_DEFAULT_POSTCODE,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $store
- )
- );
- break;
- default:
- break;
- }
- if ($customerTaxClass === null || $customerTaxClass === false) {
- if ($customerId) {
- $customerData = $this->customerRepository->getById($customerId);
- $customerTaxClass = $this->customerGroupRepository
- ->getById($customerData->getGroupId())
- ->getTaxClassId();
- } else {
- $customerTaxClass = $this->customerGroupManagement->getNotLoggedInGroup()->getTaxClassId();
- }
- }
- $request = new \Magento\Framework\DataObject();
- //TODO: Address is not completely refactored to use Data objects
- if ($address->getRegion() instanceof AddressRegion) {
- $regionId = $address->getRegion()->getRegionId();
- } else {
- $regionId = $address->getRegionId();
- }
- $request->setCountryId($address->getCountryId())
- ->setRegionId($regionId)
- ->setPostcode($address->getPostcode())
- ->setStore($store)
- ->setCustomerClassId($customerTaxClass);
- return $request;
- }
- /**
- * Get information about tax rates applied to request
- *
- * @param \Magento\Framework\DataObject $request
- * @return array
- */
- public function getAppliedRates($request)
- {
- if (!$request->getCountryId() || !$request->getCustomerClassId() || !$request->getProductClassId()) {
- return [];
- }
- $cacheKey = $this->_getRequestCacheKey($request);
- if (!isset($this->_rateCalculationProcess[$cacheKey])) {
- $this->_rateCalculationProcess[$cacheKey] = $this->_getResource()->getCalculationProcess($request);
- }
- return $this->_rateCalculationProcess[$cacheKey];
- }
- /**
- * Gets the calculation process
- *
- * @param array $rates
- * @return array
- */
- public function reproduceProcess($rates)
- {
- return $this->getResource()->getCalculationProcess(null, $rates);
- }
- /**
- * Calculate rated tax amount based on price and tax rate.
- * If you are using price including tax $priceIncludeTax should be true.
- *
- * @param float $price
- * @param float $taxRate
- * @param boolean $priceIncludeTax
- * @param boolean $round
- * @return float
- */
- public function calcTaxAmount($price, $taxRate, $priceIncludeTax = false, $round = true)
- {
- $taxRate = $taxRate / 100;
- if ($priceIncludeTax) {
- $amount = $price * (1 - 1 / (1 + $taxRate));
- } else {
- $amount = $price * $taxRate;
- }
- if ($round) {
- return $this->round($amount);
- }
- return $amount;
- }
- /**
- * Round tax amount
- *
- * @param float $price
- * @return float
- */
- public function round($price)
- {
- return $this->priceCurrency->round($price);
- }
- /**
- * @param array $billingAddress
- * @param array $shippingAddress
- * @param int $customerTaxClassId
- * @return array
- */
- public function getTaxRates($billingAddress, $shippingAddress, $customerTaxClassId)
- {
- $billingAddressObj = null;
- $shippingAddressObj = null;
- if (!empty($billingAddress)) {
- $billingAddressObj = new \Magento\Framework\DataObject($billingAddress);
- }
- if (!empty($shippingAddress)) {
- $shippingAddressObj = new \Magento\Framework\DataObject($shippingAddress);
- }
- $rateRequest = $this->getRateRequest($shippingAddressObj, $billingAddressObj, $customerTaxClassId);
- $searchCriteria = $this->searchCriteriaBuilder->addFilters(
- [$this->filterBuilder->setField(ClassModel::KEY_TYPE)
- ->setValue(\Magento\Tax\Api\TaxClassManagementInterface::TYPE_PRODUCT)
- ->create()]
- )->create();
- $ids = $this->taxClassRepository->getList($searchCriteria)->getItems();
- $productRates = [];
- $idKeys = array_keys($ids);
- foreach ($idKeys as $idKey) {
- $rateRequest->setProductClassId($idKey);
- $rate = $this->getRate($rateRequest);
- $productRates[$idKey] = $rate;
- }
- return $productRates;
- }
- }
|