Rate.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Tax\Model\Calculation;
  7. use Magento\Directory\Model\Region;
  8. use Magento\Framework\Api\AttributeValueFactory;
  9. use Magento\Framework\Exception\CouldNotDeleteException;
  10. use Magento\Tax\Api\Data\TaxRateInterface;
  11. /**
  12. * Tax Rate Model
  13. *
  14. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  15. */
  16. class Rate extends \Magento\Framework\Model\AbstractExtensibleModel implements TaxRateInterface
  17. {
  18. /**#@+
  19. * Constants defined for keys of array, makes typos less likely
  20. */
  21. const KEY_ID = 'id';
  22. const KEY_COUNTRY_ID = 'tax_country_id';
  23. const KEY_REGION_ID = 'tax_region_id';
  24. const KEY_REGION_NAME = 'region_name';
  25. const KEY_POSTCODE = 'tax_postcode';
  26. const KEY_ZIP_IS_RANGE = 'zip_is_range';
  27. const KEY_ZIP_RANGE_FROM = 'zip_from';
  28. const KEY_ZIP_RANGE_TO = 'zip_to';
  29. const KEY_PERCENTAGE_RATE = 'rate';
  30. const KEY_CODE = 'code';
  31. const KEY_TITLES = 'titles';
  32. /**#@-*/
  33. /**#@-*/
  34. protected $_titles = null;
  35. /**
  36. * @var \Magento\Tax\Model\Calculation\Rate\Title
  37. */
  38. protected $_titleModel = null;
  39. /**
  40. * @var \Magento\Directory\Model\RegionFactory
  41. */
  42. protected $_regionFactory;
  43. /**
  44. * @var \Magento\Tax\Model\Calculation\Rate\TitleFactory
  45. */
  46. protected $_titleFactory;
  47. /**
  48. * @var Region
  49. */
  50. protected $directoryRegion;
  51. /**
  52. * @param \Magento\Framework\Model\Context $context
  53. * @param \Magento\Framework\Registry $registry
  54. * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory
  55. * @param AttributeValueFactory $customAttributeFactory
  56. * @param \Magento\Directory\Model\RegionFactory $regionFactory
  57. * @param Rate\TitleFactory $taxTitleFactory
  58. * @param Region $directoryRegion
  59. * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
  60. * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
  61. * @param array $data
  62. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  63. */
  64. public function __construct(
  65. \Magento\Framework\Model\Context $context,
  66. \Magento\Framework\Registry $registry,
  67. \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
  68. AttributeValueFactory $customAttributeFactory,
  69. \Magento\Directory\Model\RegionFactory $regionFactory,
  70. \Magento\Tax\Model\Calculation\Rate\TitleFactory $taxTitleFactory,
  71. Region $directoryRegion,
  72. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  73. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  74. array $data = []
  75. ) {
  76. $this->_regionFactory = $regionFactory;
  77. $this->_titleFactory = $taxTitleFactory;
  78. $this->directoryRegion = $directoryRegion;
  79. parent::__construct(
  80. $context,
  81. $registry,
  82. $extensionFactory,
  83. $customAttributeFactory,
  84. $resource,
  85. $resourceCollection,
  86. $data
  87. );
  88. }
  89. /**
  90. * Magento model constructor
  91. *
  92. * @return void
  93. */
  94. protected function _construct()
  95. {
  96. $this->_init(\Magento\Tax\Model\ResourceModel\Calculation\Rate::class);
  97. }
  98. /**
  99. * Prepare location settings and tax postcode before save rate
  100. *
  101. * @return \Magento\Tax\Model\Calculation\Rate
  102. * @throws \Magento\Framework\Exception\LocalizedException
  103. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  104. * @SuppressWarnings(PHPMD.NPathComplexity)
  105. */
  106. public function beforeSave()
  107. {
  108. $isWrongRange = $this->getZipIsRange() && ($this->getZipFrom() === '' || $this->getZipTo() === '');
  109. $isEmptyValues = $this->getCode() === '' ||
  110. $this->getTaxCountryId() === '' ||
  111. $this->getRate() === '' ||
  112. ($this->getTaxPostcode() === '' && !$this->getZipIsRange());
  113. if ($isEmptyValues || $isWrongRange) {
  114. throw new \Magento\Framework\Exception\LocalizedException(
  115. __('The required information is invalid. Verify the information and try again.')
  116. );
  117. }
  118. if (!is_numeric($this->getRate()) || $this->getRate() < 0) {
  119. throw new \Magento\Framework\Exception\LocalizedException(
  120. __('The Rate Percent is invalid. Enter a positive number and try again.')
  121. );
  122. }
  123. if ($this->getZipIsRange()) {
  124. $zipFrom = $this->getZipFrom();
  125. $zipTo = $this->getZipTo();
  126. if (strlen($zipFrom) > 9 || strlen($zipTo) > 9) {
  127. throw new \Magento\Framework\Exception\LocalizedException(
  128. __(
  129. 'The ZIP Code length is invalid. '
  130. . 'Verify that the length is nine characters or fewer and try again.'
  131. )
  132. );
  133. }
  134. if (!is_numeric($zipFrom) || !is_numeric($zipTo) || $zipFrom < 0 || $zipTo < 0) {
  135. throw new \Magento\Framework\Exception\LocalizedException(
  136. __('The ZIP Code is invalid. Use numbers only.')
  137. );
  138. }
  139. if ($zipFrom > $zipTo) {
  140. throw new \Magento\Framework\Exception\LocalizedException(
  141. __('Range To should be equal or greater than Range From.')
  142. );
  143. }
  144. $this->setTaxPostcode($zipFrom . '-' . $zipTo);
  145. } else {
  146. $taxPostCode = $this->getTaxPostcode();
  147. if (strlen($taxPostCode) > 10) {
  148. $taxPostCode = substr($taxPostCode, 0, 10);
  149. }
  150. $this->setTaxPostcode($taxPostCode)->setZipIsRange(null)->setZipFrom(null)->setZipTo(null);
  151. }
  152. parent::beforeSave();
  153. $country = $this->getTaxCountryId();
  154. $region = $this->getTaxRegionId();
  155. /** @var $regionModel \Magento\Directory\Model\Region */
  156. $regionModel = $this->_regionFactory->create();
  157. $regionModel->load($region);
  158. if ($regionModel->getCountryId() != $country) {
  159. $this->setTaxRegionId('*');
  160. }
  161. return $this;
  162. }
  163. /**
  164. * Save rate titles
  165. *
  166. * @return \Magento\Tax\Model\Calculation\Rate
  167. */
  168. public function afterSave()
  169. {
  170. $this->saveTitles();
  171. $this->_eventManager->dispatch('tax_settings_change_after');
  172. return parent::afterSave();
  173. }
  174. /**
  175. * Processing object before delete data
  176. *
  177. * @return \Magento\Tax\Model\Calculation\Rate
  178. * @throws \Magento\Framework\Exception\LocalizedException
  179. */
  180. public function beforeDelete()
  181. {
  182. if ($this->_isInRule()) {
  183. throw new CouldNotDeleteException(
  184. __("The tax rate can't be removed because it exists in a tax rule.")
  185. );
  186. }
  187. return parent::beforeDelete();
  188. }
  189. /**
  190. * After rate delete
  191. * redeclared for dispatch tax_settings_change_after event
  192. *
  193. * @return \Magento\Tax\Model\Calculation\Rate
  194. */
  195. public function afterDelete()
  196. {
  197. $this->_eventManager->dispatch('tax_settings_change_after');
  198. return parent::afterDelete();
  199. }
  200. /**
  201. * Saves the tax titles
  202. *
  203. * @param array|null $titles
  204. * @return void
  205. */
  206. public function saveTitles($titles = null)
  207. {
  208. if ($titles === null) {
  209. $titles = $this->getTitle();
  210. }
  211. $this->getTitleModel()->deleteByRateId($this->getId());
  212. if (is_array($titles) && $titles) {
  213. foreach ($titles as $store => $title) {
  214. if ($title !== '') {
  215. $this->getTitleModel()->setId(
  216. null
  217. )->setTaxCalculationRateId(
  218. $this->getId()
  219. )->setStoreId(
  220. (int)$store
  221. )->setValue(
  222. $title
  223. )->save();
  224. }
  225. }
  226. }
  227. }
  228. /**
  229. * Returns a tax title
  230. *
  231. * @return \Magento\Tax\Model\Calculation\Rate\Title
  232. */
  233. public function getTitleModel()
  234. {
  235. if ($this->_titleModel === null) {
  236. $this->_titleModel = $this->_titleFactory->create();
  237. }
  238. return $this->_titleModel;
  239. }
  240. /**
  241. * {@inheritdoc}
  242. */
  243. public function getTitles()
  244. {
  245. if ($this->getData(self::KEY_TITLES)) {
  246. return $this->getData(self::KEY_TITLES);
  247. }
  248. if ($this->_titles === null) {
  249. $this->_titles = $this->getTitleModel()->getCollection()->loadByRateId($this->getId())->getItems();
  250. }
  251. return $this->_titles;
  252. }
  253. /**
  254. * Deletes all tax rates
  255. *
  256. * @return \Magento\Tax\Model\Calculation\Rate
  257. */
  258. public function deleteAllRates()
  259. {
  260. $this->_getResource()->deleteAllRates();
  261. $this->_eventManager->dispatch('tax_settings_change_after');
  262. return $this;
  263. }
  264. /**
  265. * Load rate model by code
  266. *
  267. * @param string $code
  268. * @return \Magento\Tax\Model\Calculation\Rate
  269. */
  270. public function loadByCode($code)
  271. {
  272. $this->load($code, 'code');
  273. return $this;
  274. }
  275. /**
  276. * Check if rate exists in tax rule
  277. *
  278. * @return array
  279. */
  280. protected function _isInRule()
  281. {
  282. return $this->getResource()->isInRule($this->getId());
  283. }
  284. /**
  285. * {@inheritdoc}
  286. */
  287. public function getRegionName()
  288. {
  289. if (!$this->getData(self::KEY_REGION_NAME)) {
  290. $regionName = $this->directoryRegion->load($this->getTaxRegionId())->getCode();
  291. $this->setData(self::KEY_REGION_NAME, $regionName);
  292. }
  293. return $this->getData(self::KEY_REGION_NAME);
  294. }
  295. /**
  296. * @codeCoverageIgnoreStart
  297. * {@inheritdoc}
  298. */
  299. public function getTaxCalculationRateId()
  300. {
  301. return $this->getData(self::KEY_ID);
  302. }
  303. /**
  304. * {@inheritdoc}
  305. */
  306. public function getTaxCountryId()
  307. {
  308. return $this->getData(self::KEY_COUNTRY_ID);
  309. }
  310. /**
  311. * {@inheritdoc}
  312. */
  313. public function getTaxRegionId()
  314. {
  315. return $this->getData(self::KEY_REGION_ID);
  316. }
  317. /**
  318. * {@inheritdoc}
  319. */
  320. public function getTaxPostcode()
  321. {
  322. return $this->getData(self::KEY_POSTCODE);
  323. }
  324. /**
  325. * {@inheritdoc}
  326. */
  327. public function getZipFrom()
  328. {
  329. return $this->getData(self::KEY_ZIP_RANGE_FROM);
  330. }
  331. /**
  332. * {@inheritdoc}
  333. */
  334. public function getZipTo()
  335. {
  336. return $this->getData(self::KEY_ZIP_RANGE_TO);
  337. }
  338. /**
  339. * {@inheritdoc}
  340. */
  341. public function getRate()
  342. {
  343. return $this->getData(self::KEY_PERCENTAGE_RATE);
  344. }
  345. /**
  346. * {@inheritdoc}
  347. */
  348. public function getCode()
  349. {
  350. return $this->getData(self::KEY_CODE);
  351. }
  352. /**
  353. * {@inheritdoc}
  354. */
  355. public function getZipIsRange()
  356. {
  357. return $this->getData(self::KEY_ZIP_IS_RANGE);
  358. }
  359. /**
  360. * Set country id
  361. *
  362. * @param string $taxCountryId
  363. * @return $this
  364. */
  365. public function setTaxCountryId($taxCountryId)
  366. {
  367. return $this->setData(self::KEY_COUNTRY_ID, $taxCountryId);
  368. }
  369. /**
  370. * Set region id
  371. *
  372. * @param int $taxRegionId
  373. * @return $this
  374. */
  375. public function setTaxRegionId($taxRegionId)
  376. {
  377. return $this->setData(self::KEY_REGION_ID, $taxRegionId);
  378. }
  379. /**
  380. * Set region name
  381. *
  382. * @param string $regionName
  383. * @return $this
  384. */
  385. public function setRegionName($regionName)
  386. {
  387. return $this->setData(self::KEY_REGION_NAME, $regionName);
  388. }
  389. /**
  390. * Set postcode
  391. *
  392. * @param string $taxPostCode
  393. * @return $this
  394. */
  395. public function setTaxPostcode($taxPostCode)
  396. {
  397. return $this->setData(self::KEY_POSTCODE, $taxPostCode);
  398. }
  399. /**
  400. * Set zip is range
  401. *
  402. * @param int $zipIsRange
  403. * @return $this
  404. */
  405. public function setZipIsRange($zipIsRange)
  406. {
  407. return $this->setData(self::KEY_ZIP_IS_RANGE, $zipIsRange);
  408. }
  409. /**
  410. * Set zip range from
  411. *
  412. * @param int $zipFrom
  413. * @return $this
  414. */
  415. public function setZipFrom($zipFrom)
  416. {
  417. return $this->setData(self::KEY_ZIP_RANGE_FROM, $zipFrom);
  418. }
  419. /**
  420. * Set zip range to
  421. *
  422. * @param int $zipTo
  423. * @return $this
  424. */
  425. public function setZipTo($zipTo)
  426. {
  427. return $this->setData(self::KEY_ZIP_RANGE_TO, $zipTo);
  428. }
  429. /**
  430. * Set tax rate in percentage
  431. *
  432. * @param float $rate
  433. * @return $this
  434. */
  435. public function setRate($rate)
  436. {
  437. return $this->setData(self::KEY_PERCENTAGE_RATE, $rate);
  438. }
  439. /**
  440. * Set tax rate code
  441. *
  442. * @param string $code
  443. * @return $this
  444. */
  445. public function setCode($code)
  446. {
  447. return $this->setData(self::KEY_CODE, $code);
  448. }
  449. /**
  450. * Set tax rate titles
  451. *
  452. * @param \Magento\Tax\Api\Data\TaxRateTitleInterface[] $titles
  453. * @return $this
  454. */
  455. public function setTitles(array $titles = null)
  456. {
  457. return $this->setData(self::KEY_TITLES, $titles);
  458. }
  459. // @codeCoverageIgnoreEnd
  460. /**
  461. * {@inheritdoc}
  462. *
  463. * @return \Magento\Tax\Api\Data\TaxRateExtensionInterface|null
  464. */
  465. public function getExtensionAttributes()
  466. {
  467. return $this->_getExtensionAttributes();
  468. }
  469. /**
  470. * {@inheritdoc}
  471. *
  472. * @param \Magento\Tax\Api\Data\TaxRateExtensionInterface $extensionAttributes
  473. * @return $this
  474. */
  475. public function setExtensionAttributes(\Magento\Tax\Api\Data\TaxRateExtensionInterface $extensionAttributes)
  476. {
  477. return $this->_setExtensionAttributes($extensionAttributes);
  478. }
  479. }