From fd7fdcd4e5dc5d1e82015ec396ebf1a78ca79f55 Mon Sep 17 00:00:00 2001 From: Graham Wharton Date: Mon, 9 Sep 2024 14:14:01 +0100 Subject: [PATCH] First Issue of Updated Modules --- .github/workflows/autorelease.yaml | 26 + Api/Data/OrderTaxCollectedInterface.php | 11 + Api/Data/OrderTaxSchemeInterface.php | 131 +++++ Api/Data/TaxIdCheckResponseInterface.php | 60 ++ Api/Data/TaxSchemeInterface.php | 92 ++++ Block/Adminhtml/CheckTaxIdRenderer.php | 100 ++++ Block/Adminhtml/Rule/Form.php | 114 ++++ Block/Adminhtml/ThresholdSummary.php | 74 +++ Controller/Adminhtml/Createorder/Validate.php | 132 +++++ Controller/Adminhtml/Rule/Save.php | 59 ++ Controller/CheckTaxId/Validate.php | 102 ++++ LICENSE.md | 44 ++ Model/AutoCustomerGroup.php | 179 ++++++ Model/Calculation/RowBaseCalculator.php | 80 +++ Model/Calculation/TaxRuleExtractor.php | 65 +++ Model/Calculation/TotalBaseCalculator.php | 80 +++ Model/Calculation/UnitBaseCalculator.php | 80 +++ Model/Collector/AutoCustomerGroup.php | 237 ++++++++ Model/Config/Source/Environment.php | 36 ++ Model/Config/Source/Group.php | 60 ++ Model/JSConfig.php | 72 +++ Model/OrderTaxCollected.php | 48 ++ Model/OrderTaxScheme.php | 127 +++++ Model/ResourceModel/OrderTaxScheme.php | 12 + .../OrderTaxScheme/Collection.php | 43 ++ Model/Source/TaxSchemes.php | 35 ++ Model/TaxIdCheckResponse.php | 58 ++ Model/TaxRuleCollection.php | 31 ++ Model/TaxSchemes.php | 73 +++ .../SalesModelServiceQuoteSubmitBefore.php | 89 +++ .../SalesModelServiceQuoteSubmitSuccess.php | 132 +++++ Plugin/ConvertAppliedTaxesPlugin.php | 38 ++ .../BeforeAddressSaveObserverPlugin.php | 86 +++ .../DisableAfterAddressSaveObserverPlugin.php | 54 ++ Plugin/Directory/CurrencyConfigPlugin.php | 56 ++ .../DisableCollectTotalsObserverPlugin.php | 58 ++ Plugin/Tax/TaxRuleRepositoryPlugin.php | 108 ++++ README.md | 92 ++++ Test/Integration/AutoGroupTest.php | 514 ++++++++++++++++++ Test/Integration/CreateOrderTest.php | 364 +++++++++++++ Test/Integration/CustomerAccountTest.php | 281 ++++++++++ .../Listing/Column/OrderTaxCollected.php | 56 ++ composer.json | 1 + etc/adminhtml/di.xml | 10 + etc/adminhtml/routes.xml | 9 + etc/adminhtml/system.xml | 68 +++ etc/config.xml | 15 + etc/db_schema.xml | 39 ++ etc/db_schema_whitelist.json | 27 + etc/di.xml | 59 ++ etc/events.xml | 12 + etc/extension_attributes.xml | 12 + etc/fieldset.xml | 26 + etc/frontend/routes.xml | 9 + etc/module.xml | 6 +- etc/sales.xml | 8 + images/frontend1.png | Bin 0 -> 17182 bytes images/frontend2.png | Bin 0 -> 9153 bytes images/frontend3.png | Bin 0 -> 7228 bytes images/frontend4.png | Bin 0 -> 9969 bytes images/general.png | Bin 0 -> 67333 bytes images/menu.png | Bin 0 -> 6103 bytes images/sogcolumn.png | Bin 0 -> 2909 bytes images/taxrules1.png | Bin 0 -> 38050 bytes images/taxrules2.png | Bin 0 -> 25746 bytes registration.php | 1 + .../layout/sales_order_create_index.xml | 8 + view/adminhtml/layout/tax_rule_block.xml | 20 + .../adminhtml/templates/taxidvalidation.phtml | 10 + .../ui_component/sales_order_grid.xml | 15 + view/adminhtml/web/css/source/_module.less | 15 + view/adminhtml/web/js/taxidvalidation.js | 58 ++ view/frontend/layout/checkout_index_index.xml | 38 ++ view/frontend/web/css/source/_module.less | 20 + view/frontend/web/js/tax-id-element.js | 277 ++++++++++ .../frontend/web/template/tax-id-element.html | 24 + 76 files changed, 4975 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/autorelease.yaml create mode 100644 Api/Data/OrderTaxCollectedInterface.php create mode 100644 Api/Data/OrderTaxSchemeInterface.php create mode 100644 Api/Data/TaxIdCheckResponseInterface.php create mode 100644 Api/Data/TaxSchemeInterface.php create mode 100644 Block/Adminhtml/CheckTaxIdRenderer.php create mode 100644 Block/Adminhtml/Rule/Form.php create mode 100644 Block/Adminhtml/ThresholdSummary.php create mode 100644 Controller/Adminhtml/Createorder/Validate.php create mode 100644 Controller/Adminhtml/Rule/Save.php create mode 100644 Controller/CheckTaxId/Validate.php create mode 100644 LICENSE.md create mode 100644 Model/AutoCustomerGroup.php create mode 100644 Model/Calculation/RowBaseCalculator.php create mode 100644 Model/Calculation/TaxRuleExtractor.php create mode 100644 Model/Calculation/TotalBaseCalculator.php create mode 100644 Model/Calculation/UnitBaseCalculator.php create mode 100644 Model/Collector/AutoCustomerGroup.php create mode 100644 Model/Config/Source/Environment.php create mode 100644 Model/Config/Source/Group.php create mode 100644 Model/JSConfig.php create mode 100644 Model/OrderTaxCollected.php create mode 100644 Model/OrderTaxScheme.php create mode 100644 Model/ResourceModel/OrderTaxScheme.php create mode 100644 Model/ResourceModel/OrderTaxScheme/Collection.php create mode 100644 Model/Source/TaxSchemes.php create mode 100644 Model/TaxIdCheckResponse.php create mode 100644 Model/TaxRuleCollection.php create mode 100644 Model/TaxSchemes.php create mode 100644 Observer/SalesModelServiceQuoteSubmitBefore.php create mode 100644 Observer/SalesModelServiceQuoteSubmitSuccess.php create mode 100644 Plugin/ConvertAppliedTaxesPlugin.php create mode 100644 Plugin/Customer/BeforeAddressSaveObserverPlugin.php create mode 100644 Plugin/Customer/DisableAfterAddressSaveObserverPlugin.php create mode 100644 Plugin/Directory/CurrencyConfigPlugin.php create mode 100644 Plugin/Quote/DisableCollectTotalsObserverPlugin.php create mode 100644 Plugin/Tax/TaxRuleRepositoryPlugin.php create mode 100644 README.md create mode 100644 Test/Integration/AutoGroupTest.php create mode 100644 Test/Integration/CreateOrderTest.php create mode 100644 Test/Integration/CustomerAccountTest.php create mode 100644 Ui/Component/Listing/Column/OrderTaxCollected.php create mode 100644 etc/adminhtml/di.xml create mode 100644 etc/adminhtml/routes.xml create mode 100644 etc/adminhtml/system.xml create mode 100644 etc/config.xml create mode 100644 etc/db_schema.xml create mode 100644 etc/db_schema_whitelist.json create mode 100644 etc/di.xml create mode 100644 etc/events.xml create mode 100644 etc/extension_attributes.xml create mode 100644 etc/fieldset.xml create mode 100644 etc/frontend/routes.xml create mode 100644 etc/sales.xml create mode 100644 images/frontend1.png create mode 100644 images/frontend2.png create mode 100644 images/frontend3.png create mode 100644 images/frontend4.png create mode 100644 images/general.png create mode 100644 images/menu.png create mode 100644 images/sogcolumn.png create mode 100644 images/taxrules1.png create mode 100644 images/taxrules2.png create mode 100644 view/adminhtml/layout/sales_order_create_index.xml create mode 100644 view/adminhtml/layout/tax_rule_block.xml create mode 100644 view/adminhtml/templates/taxidvalidation.phtml create mode 100644 view/adminhtml/ui_component/sales_order_grid.xml create mode 100644 view/adminhtml/web/css/source/_module.less create mode 100644 view/adminhtml/web/js/taxidvalidation.js create mode 100644 view/frontend/layout/checkout_index_index.xml create mode 100644 view/frontend/web/css/source/_module.less create mode 100644 view/frontend/web/js/tax-id-element.js create mode 100644 view/frontend/web/template/tax-id-element.html diff --git a/.github/workflows/autorelease.yaml b/.github/workflows/autorelease.yaml new file mode 100644 index 0000000..0288c33 --- /dev/null +++ b/.github/workflows/autorelease.yaml @@ -0,0 +1,26 @@ +name: AutoRelease +on: + push: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Auto Increment Semver Action + uses: MCKanpolat/auto-semver-action@1.0.11 + id: versioning + with: + releaseType: patch + incrementPerCommit: true + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Next Release Number + run: echo ${{ steps.versioning.outputs.version }} + - name: Checkout + uses: actions/checkout@v4 + - name: Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.versioning.outputs.version }} + make_latest: true diff --git a/Api/Data/OrderTaxCollectedInterface.php b/Api/Data/OrderTaxCollectedInterface.php new file mode 100644 index 0000000..736066a --- /dev/null +++ b/Api/Data/OrderTaxCollectedInterface.php @@ -0,0 +1,11 @@ +secureRenderer = $secureRenderer; + $this->scopeConfig = $scopeConfig; + $this->autoCustomerGroup = $autoCustomerGroup; + } + + /** + * @return Button + */ + public function getValidateButton() + { + if ( $this->autoCustomerGroup->isModuleEnabled()) { + if ($this->_validateButton === null) { + $form = $this->_element->getForm(); + $taxValidateOptions = $this->_jsonEncoder->encode( + [ + 'taxIdElementId' => $this->_element->getHtmlId(), + 'countryElementId' => $form->getElement('country_id')->getHtmlId(), + 'postcodeElementId' => $form->getElement('postcode')->getHtmlId(), + 'groupIdHtmlId' => 'group_id', + 'validateUrl' => $this->_urlBuilder->getUrl('autocustomergroup/createorder/validate') + ] + ); + $optionsVarName = $this->getJsVariablePrefix() . 'TaxParameters'; + $scriptString = 'var ' . $optionsVarName . ' = ' . $taxValidateOptions . ';'; + $beforeHtml = $this->secureRenderer->renderTag('script', [], $scriptString, false); + $beforeHtml .= '
'; + $beforeHtml .= '
We can validate the Tax Identifer and set the Customer Group automatically using the AutoCustomerGroup module.
'; + $beforeHtml .= '
(For UK Orders going to NI, ensure postcode is set before validating).
'; + $beforeHtml .= '
'; + + $this->_validateButton = $this->getLayout()->createBlock( + Button::class + )->setData( + [ + 'label' => __('Validate TAX Identifier'), + 'before_html' => $beforeHtml, + 'onclick' => 'order.validateTaxId(' . $optionsVarName . ')' + ] + ); + } + return $this->_validateButton; + } else { + return parent::getValidateButton(); + } + } +} diff --git a/Block/Adminhtml/Rule/Form.php b/Block/Adminhtml/Rule/Form.php new file mode 100644 index 0000000..de31718 --- /dev/null +++ b/Block/Adminhtml/Rule/Form.php @@ -0,0 +1,114 @@ +taxSchemes = $taxSchemes; + } + + /** + * @return $this + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + protected function _prepareForm(): Form + { + parent::_prepareForm(); + + $form = $this->getForm(); + $fieldset = $form->getElement('base_fieldset'); + $taxSchemes = $this->taxSchemes->toOptionArray(); + array_unshift($taxSchemes, ['value' => null, 'label' => 'Not Linked']); + + $fieldset->addField( + 'tax_scheme_id', + 'select', + ['name' => 'tax_scheme_id', 'label' => __('Tax Scheme'), 'required' => false, 'values' => $taxSchemes] + ); + + $taxRuleId = $this->_coreRegistry->registry('tax_rule_id'); + $sessionFormValues = (array)$this->_coreRegistry->registry('tax_rule_form_data'); + try { + $taxRule = $this->ruleService->get($taxRuleId); + $taxRuleData = $this->extractTaxRuleData($taxRule); + } catch (NoSuchEntityException $e) { + $taxRuleData = []; + } + $formValues = array_merge($taxRuleData, $sessionFormValues); + $formValues['tax_calculation_rule_id'] = $taxRuleId; + $form->setValues($formValues); + return $this; + } + + /** + * @param TaxRuleInterface $taxRule + * @return array + */ + protected function extractTaxRuleData($taxRule): array + { + $taxRuleData = parent::extractTaxRuleData($taxRule); + $taxScheme = $taxRule->getExtensionAttributes()->getTaxScheme(); + if ($taxScheme) { + $taxRuleData['tax_scheme_id'] = $taxScheme->getSchemeId(); + } + return $taxRuleData; + } +} diff --git a/Block/Adminhtml/ThresholdSummary.php b/Block/Adminhtml/ThresholdSummary.php new file mode 100644 index 0000000..49c736d --- /dev/null +++ b/Block/Adminhtml/ThresholdSummary.php @@ -0,0 +1,74 @@ +taxScheme = $taxScheme; + $this->scopeConfig = $scopeConfig; + $this->storeManager = $storeManager; + } + + /** + * Retrieve element HTML markup + * + * @param AbstractElement $element + * @return string + * @SuppressWarnings(PHPMD.CamelCaseMethodName) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function _getElementHtml(AbstractElement $element) + { + $storeId = $this->getRequest()->getParam('store', null); + $baseCurrency = $this->scopeConfig->getValue( + "currency/options/base", + ScopeInterface::SCOPE_STORE, + $storeId + ); + $thresholdInBaseCurrency = $this->taxScheme->getSchemeExchangeRate($storeId) * $this->taxScheme->getThresholdInSchemeCurrency($storeId); + + return '
' . + '
' . sprintf("%.2f", $thresholdInBaseCurrency) . ' ' . $baseCurrency . '
' . + '
'; + } +} diff --git a/Controller/Adminhtml/Createorder/Validate.php b/Controller/Adminhtml/Createorder/Validate.php new file mode 100644 index 0000000..753ae1b --- /dev/null +++ b/Controller/Adminhtml/Createorder/Validate.php @@ -0,0 +1,132 @@ +validator = $validator; + $this->autoCustomerGroup = $autoCustomerGroup; + $this->request = $request; + $this->redirectFactory = $redirectFactory; + $this->jsonFactory = $jsonFactory; + $this->validateAdvanced = $validateAdvanced; + $this->quoteSession = $quoteSession; + } + + /** + * @return ResponseInterface|RedirectInterface|ResultInterface|void + */ + public function execute() + { + + $storeId = (int)$this->request->getParam('store_id', 0); + if ($this->autoCustomerGroup->isModuleEnabled($storeId)) { + $taxIdToCheck = $this->request->getParam('tax'); + $countryCode = $this->request->getParam('country'); + $postcode = $this->request->getParam('postcode'); + $quote = $this->quoteSession->getQuote(); + $taxIdCheckResponse = null; + if (!empty($countryCode) && !empty($taxIdToCheck) && $storeId) { + $taxIdCheckResponse = $this->autoCustomerGroup->checkTaxId( + $countryCode, + $taxIdToCheck, + $storeId + ); + } + $responseData = [ + 'valid' => false, + 'group' => null, + 'message' => __('Error checking TAX Identifier'), + 'success' => false + ]; + + if ($taxIdCheckResponse && $quote) { + + $groupId = $this->autoCustomerGroup->getCustomerGroup( + $countryCode, + $postcode, + $taxIdCheckResponse->getIsValid(), + $quote, + $storeId + ); + $responseData = [ + 'valid' => $taxIdCheckResponse->getIsValid(), + 'group' => (int)$groupId, + 'message' => $taxIdCheckResponse->getRequestMessage(), + 'success' => $taxIdCheckResponse->getRequestSuccess() + ]; + } + } else { + $responseData = $this->validateAdvanced->execute(); + } + $resultJson = $this->jsonFactory->create(); + return $resultJson->setData($responseData); + } +} diff --git a/Controller/Adminhtml/Rule/Save.php b/Controller/Adminhtml/Rule/Save.php new file mode 100644 index 0000000..3e75260 --- /dev/null +++ b/Controller/Adminhtml/Rule/Save.php @@ -0,0 +1,59 @@ +taxSchemes = $taxSchemes; + parent::__construct( + $context, + $coreRegistry, + $ruleService, + $taxRuleDataObjectFactory + ); + } + + /** + * @param $postData + * @return TaxRuleInterface + */ + protected function populateTaxRule($postData): TaxRuleInterface + { + $taxRule = parent::populateTaxRule($postData); + if (isset($postData['tax_scheme_id']) && $postData['tax_scheme_id'] != "") { + $extensionAttributes = $taxRule->getExtensionAttributes(); + $extensionAttributes->setTaxScheme($this->taxSchemes->getTaxScheme($postData['tax_scheme_id'])); + $taxRule->setExtensionAttributes($extensionAttributes); + } + return $taxRule; + } +} diff --git a/Controller/CheckTaxId/Validate.php b/Controller/CheckTaxId/Validate.php new file mode 100644 index 0000000..7c847a7 --- /dev/null +++ b/Controller/CheckTaxId/Validate.php @@ -0,0 +1,102 @@ +validator = $validator; + $this->autoCustomerGroup = $autoCustomerGroup; + $this->request = $request; + $this->redirectFactory = $redirectFactory; + $this->jsonFactory = $jsonFactory; + } + + /** + * @return ResponseInterface|RedirectInterface|ResultInterface|void + */ + public function execute() + { + $taxIdToCheck = $this->request->getParam('tax_id'); + $countryCode = $this->request->getParam('country_code'); + $storeId = (int)$this->request->getParam('store_id', 0); + if (!$this->validator->validate($this->request)) { + $redirect = $this->redirectFactory->create(); + return $redirect->setPath('*/*/'); + } + + $taxIdCheckResponse = null; + if (!empty($countryCode) && !empty($taxIdToCheck) && $storeId) { + $taxIdCheckResponse = $this->autoCustomerGroup->checkTaxId( + $countryCode, + $taxIdToCheck, + $storeId + ); + } + + $responseData = [ + 'valid' => false, + 'message' => __('There was an error validating your Tax Id'), + 'success' => false + ]; + if ($taxIdCheckResponse) { + $responseData = [ + 'valid' => $taxIdCheckResponse->getIsValid(), + 'message' => $taxIdCheckResponse->getRequestMessage(), + 'success' => $taxIdCheckResponse->getRequestSuccess() + ]; + } + $resultJson = $this->jsonFactory->create(); + return $resultJson->setData($responseData); + } +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..5c7798b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,44 @@ +Licensed under the Open Software License version 3.0 + +
    +
  1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + +a) to reproduce the Original Work in copies, either alone or as part of a collective work; + +b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + +c) to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + +d) to perform the Original Work publicly; and + +e) to display the Original Work publicly. +
  2. +
  3. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.
  4. + +
  5. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.
  6. + +
  7. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.
  8. + +
  9. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).
  10. + +
  11. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
  12. + +
  13. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.
  14. + +
  15. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.
  16. + +
  17. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).
  18. + +
  19. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
  20. + +
  21. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.
  22. + +
  23. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
  24. + +
  25. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
  26. + +
  27. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
  28. + +
  29. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
  30. + +
  31. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.
  32. diff --git a/Model/AutoCustomerGroup.php b/Model/AutoCustomerGroup.php new file mode 100644 index 0000000..1d0ac46 --- /dev/null +++ b/Model/AutoCustomerGroup.php @@ -0,0 +1,179 @@ +taxSchemes = $taxSchemes; + $this->scopeConfig = $scopeConfig; + } + + /** + * @param string $countryCode + * @param string $taxId + * @param int $storeId + * @return TaxIdCheckResponseInterface|null + */ + public function checkTaxId( + string $countryCode, + string $taxId, + int $storeId + ): ?TaxIdCheckResponseInterface { + if ($this->isModuleEnabled($storeId)) { + foreach ($this->taxSchemes->getEnabledTaxSchemes($storeId) as $taxScheme) { + if (in_array($countryCode, $taxScheme->getSchemeCountries())) { + return $taxScheme->checkTaxId($countryCode, $taxId); + } + } + } + return null; + } + + /** + * @param string $customerCountryCode + * @param string $customerPostCode + * @param TaxIdCheckResponseInterface $validationResults + * @param Quote $quote + * @param int $storeId + * @return int|null + */ + public function getCustomerGroup( + string $customerCountryCode, + string $customerPostCode, + bool $taxIdValidated, + Quote $quote, + int $storeId + ): ?int { + if ($this->isModuleEnabled($storeId)) { + foreach ($this->taxSchemes->getEnabledTaxSchemes($storeId) as $taxScheme) { + if (in_array($customerCountryCode, $taxScheme->getSchemeCountries())) { + $schemeOrderValue = $taxScheme->getOrderValue($quote); + return $taxScheme->getCustomerGroup( + $customerCountryCode, + $customerPostCode, + $taxIdValidated, + $schemeOrderValue, + $storeId + ); + } + } + } + return null; + } + + /** + * Check if module is enabled + * + * @param int|null $storeId + * @return boolean + */ + public function isModuleEnabled(?int $storeId = 0): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_PATH_MODULE_ENABLED, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * Check if sales_order_tax_scheme table is enabled + * + * @param int|null $storeId + * @return boolean + */ + public function isSalesOrderTaxSchemeEnabled(?int $storeId): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_PATH_SALES_ORDER_TAX_SCHEME, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * Is currency download enabled for scheme currencies + * + * @return boolean + */ + public function isCurrencyDownloadEnabled(): bool + { + return $this->scopeConfig->isSetFlag( + self::XML_PATH_ENABLE_CURRENCY_DOWNLOAD + ); + } + + /** + * Retrieve Frontend Control Label + * + * @param int $storeId + * @return string + */ + public function getFrontendLabel(int $storeId): string + { + return (string)$this->scopeConfig->getValue( + self::XML_PATH_FRONTEND_LABEL, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * Retrieve Default Group + * + * @param int $storeId + * @return int + */ + public function getDefaultGroup(int $storeId): int + { + return (int)$this->scopeConfig->getValue( + self::XML_PATH_DEFAULT_GROUP, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + + /** + * Is Validation on each Transaction Enabled + * + * @param int $storeId + * @return string + */ + public function isValidateOnEachTransactionEnabled(int $storeId): string + { + return (string)$this->scopeConfig->getValue( + self::XML_PATH_VALIDATE_ON_EACH, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } +} diff --git a/Model/Calculation/RowBaseCalculator.php b/Model/Calculation/RowBaseCalculator.php new file mode 100644 index 0000000..e63df08 --- /dev/null +++ b/Model/Calculation/RowBaseCalculator.php @@ -0,0 +1,80 @@ +taxRuleExtractor = $taxRuleExtractor; + } + + /** + * @param float $rowTax + * @param array $appliedRate + * @return AppliedTaxInterface + */ + protected function getAppliedTax($rowTax, $appliedRate): AppliedTaxInterface + { + return $this->taxRuleExtractor->extractSingle(parent::getAppliedTax($rowTax, $appliedRate), $appliedRate); + } + + /** + * @param float $rowTax + * @param float $totalTaxRate + * @param array $appliedRates + * @return AppliedTaxInterface[] + */ + protected function getAppliedTaxes($rowTax, $totalTaxRate, $appliedRates): array + { + return $this->taxRuleExtractor->extractMultiple(parent::getAppliedTaxes($rowTax, $totalTaxRate, $appliedRates), $appliedRates); + } +} diff --git a/Model/Calculation/TaxRuleExtractor.php b/Model/Calculation/TaxRuleExtractor.php new file mode 100644 index 0000000..043a9d6 --- /dev/null +++ b/Model/Calculation/TaxRuleExtractor.php @@ -0,0 +1,65 @@ +getRates(); + foreach ($originalRate['rates'] as $rate) { + $rule_id = $rate['rule_id']; + if ($rule_id) { + $code = $rate['code']; + $extensionAtt = $rates[$code]->getExtensionAttributes(); + $taxrulesarray = $extensionAtt->getTaxRuleIds() ?: []; + if (!in_array($rule_id, $taxrulesarray)) { + $taxrulesarray[] = $rule_id; + } + $extensionAtt->setTaxRuleIds($taxrulesarray); + $rates[$code]->setExtensionAttributes($extensionAtt); + } + } + $appliedTax->setRates($rates); + return $appliedTax; + } + + /** + * @param AppliedTaxInterface[] $appliedTaxes + * @param array $originalRates + * @return array + */ + public function extractMultiple(array $appliedTaxes, array $originalRates): array + { + foreach ($originalRates as $appliedRate) { + $id = $appliedRate['id']; + $outputRates = $appliedTaxes[$id]->getRates(); + foreach ($appliedRate['rates'] as $rate) { + $rule_id = $rate['rule_id']; + if ($rule_id) { + $code = $rate['code']; + $extensionAtt = $outputRates[$code]->getExtensionAttributes(); + $taxrulesarray = $extensionAtt->getTaxRuleIds() ?: []; + if (!in_array($rule_id, $taxrulesarray)) { + $taxrulesarray[] = $rule_id; + } + $extensionAtt->setTaxRuleIds($taxrulesarray); + $outputRates[$code]->setExtensionAttributes($extensionAtt); + } + } + $appliedTaxes[$id]->setRates($outputRates); + } + return $appliedTaxes; + } +} diff --git a/Model/Calculation/TotalBaseCalculator.php b/Model/Calculation/TotalBaseCalculator.php new file mode 100644 index 0000000..3c0c29d --- /dev/null +++ b/Model/Calculation/TotalBaseCalculator.php @@ -0,0 +1,80 @@ +taxRuleExtractor = $taxRuleExtractor; + } + + /** + * @param float $rowTax + * @param array $appliedRate + * @return AppliedTaxInterface + */ + protected function getAppliedTax($rowTax, $appliedRate): AppliedTaxInterface + { + return $this->taxRuleExtractor->extractSingle(parent::getAppliedTax($rowTax, $appliedRate), $appliedRate); + } + + /** + * @param float $rowTax + * @param float $totalTaxRate + * @param array $appliedRates + * @return AppliedTaxInterface[] + */ + protected function getAppliedTaxes($rowTax, $totalTaxRate, $appliedRates): array + { + return $this->taxRuleExtractor->extractMultiple(parent::getAppliedTaxes($rowTax, $totalTaxRate, $appliedRates), $appliedRates); + } +} diff --git a/Model/Calculation/UnitBaseCalculator.php b/Model/Calculation/UnitBaseCalculator.php new file mode 100644 index 0000000..200ad69 --- /dev/null +++ b/Model/Calculation/UnitBaseCalculator.php @@ -0,0 +1,80 @@ +taxRuleExtractor = $taxRuleExtractor; + } + + /** + * @param float $rowTax + * @param array $appliedRate + * @return AppliedTaxInterface + */ + protected function getAppliedTax($rowTax, $appliedRate): AppliedTaxInterface + { + return $this->taxRuleExtractor->extractSingle(parent::getAppliedTax($rowTax, $appliedRate), $appliedRate); + } + + /** + * @param float $rowTax + * @param float $totalTaxRate + * @param array $appliedRates + * @return AppliedTaxInterface[] + */ + protected function getAppliedTaxes($rowTax, $totalTaxRate, $appliedRates): array + { + return $this->taxRuleExtractor->extractMultiple(parent::getAppliedTaxes($rowTax, $totalTaxRate, $appliedRates), $appliedRates); + } +} diff --git a/Model/Collector/AutoCustomerGroup.php b/Model/Collector/AutoCustomerGroup.php new file mode 100644 index 0000000..733a9c4 --- /dev/null +++ b/Model/Collector/AutoCustomerGroup.php @@ -0,0 +1,237 @@ +setCode('autocustomergroup'); + $this->autoCustomerGroup = $autoCustomerGroup; + $this->customerSession = $customerSession; + $this->logger = $logger; + $this->additionalCollectors = $additionalCollectors; + $this->ticrFactory = $ticrFactory; + } + + /** + * @param Quote $quote + * @param ShippingAssignmentInterface $shippingAssignment + * @param Total $total + * @return $this + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function collect( + Quote $quote, + ShippingAssignmentInterface $shippingAssignment, + Total $total + ): AutoCustomerGroup { + parent::collect($quote, $shippingAssignment, $total); + + $items = $shippingAssignment->getItems(); + if (!count($items)) { + return $this; + } + + /** @var CustomerInterface $customer */ + $customer = $quote->getCustomer(); + $storeId = $quote->getStoreId(); + + if (!($storeId && $this->autoCustomerGroup->isModuleEnabled($storeId))) { + return $this; + } + + if ($customer->getId()) { + $this->logger->debug( + "Gw/AutoCustomerGroup/Model/Collector/AutoCustomerGroup::updateGroup() : Existing Customer Group " . + $customer->getGroupId() + ); + } + + if ($customer->getDisableAutoGroupChange()) { + $this->logger->debug( + "Gw/AutoCustomerGroup/Model/Collector/AutoCustomerGroup::updateGroup() : AutoGroupChange disabled " . + "for customer" + ); + return $this; + } + + $quoteAddress = $quote->getShippingAddress(); + + if (empty($quoteAddress->getCountryId())) { + $this->logger->debug( + "Gw/AutoCustomerGroup/Model/Collector/AutoCustomerGroup::updateGroup() : Quote Country Id empty " + ); + return $this; + } + //If we have a customer, start with their CustomerGroupId, otherwise use default group + $customerGroupId = $customer->getId() ? + $customer->getGroupId() : + $this->autoCustomerGroup->getDefaultGroup($storeId); + + $this->logger->debug( + "Gw/AutoCustomerGroup/Model/Collector/AutoCustomerGroup::updateGroup() : Starting Group is " . + $customerGroupId + ); + + $validationResult = $this->ticrFactory->create(); + //No point in validating if we haven't got a tax ID + if (!empty($quoteAddress->getVatId())) { + if (!$this->autoCustomerGroup->isValidateOnEachTransactionEnabled($storeId) && + !empty($quoteAddress->getData('validated_country_code')) && + !empty($quoteAddress->getData('validated_vat_number')) + ) { + //If we have previous validation data in the address, and we don't have to validate every time + //Then reuse the validation data + $this->logger->debug( + "Gw/AutoCustomerGroup/Model/Collector/AutoCustomerGroup::updateGroup() : Reusing validation data " . + "from quote address." + ); + $validationResult->setIsValid((bool)$quoteAddress->getData('vat_is_valid')); + $validationResult->setRequestIdentifier((string)$quoteAddress->getData('vat_request_id')); + $validationResult->setRequestDate((string)$quoteAddress->getData('vat_request_date')); + } else { + //Validate every time + $result = $this->autoCustomerGroup->checkTaxId( + $quoteAddress->getCountryId(), + $quoteAddress->getVatId(), + $storeId + ); + //Must check $result as it could be null if a tax ID is entered for a non supported country + if ($result) { + $validationResult->setIsValid($result->getIsValid()); + $validationResult->setRequestDate($result->getRequestDate()); + $validationResult->setRequestIdentifier($result->getRequestIdentifier()); + $validationResult->setRequestMessage($result->getRequestMessage()); + if ($validationResult->getIsValid()) { + // Store validation results in corresponding quote address + $quoteAddress->setData('vat_is_valid', $validationResult->getIsValid()); + $quoteAddress->setData('vat_request_id', $validationResult->getRequestIdentifier()); + $quoteAddress->setData('vat_request_date', $validationResult->getRequestDate()); + $quoteAddress->setData('validated_vat_number', $quoteAddress->getVatId()); + $quoteAddress->setData('validated_country_code', $quoteAddress->getCountryId()); + $quote->setShippingAddress($quoteAddress); + } + } + } + } + + //Get the auto assigned group for customer, returns null if group shouldn't be changed. + $newGroup = $this->autoCustomerGroup->getCustomerGroup( + $quoteAddress->getCountryId(), + $quoteAddress->getPostcode() ?: "", + $validationResult ? $validationResult->getIsValid() : false, + $quote, + $storeId + ); + + if ($newGroup) { + $this->logger->debug( + "Gw/AutoCustomerGroup/Model/Collector/AutoCustomerGroup::updateGroup() : New Group Required " . + $newGroup + ); + } else { + $this->logger->debug( + "Gw/AutoCustomerGroup/Model/Collector/AutoCustomerGroup::updateGroup() : No Group Change Required " + ); + } + + //Set the group of the $quote object, so the collectTotals will be performed on the + //correct group. Use newGroup if set, otherwise use $customerGroupId + $this->updateGroup($newGroup ?: $customerGroupId, $quote, $customer, $shippingAssignment, $total); + + //Also store the group in the quote Extension Attribute. We will check in quote submit + //observer and set group appropriately (Guest orders will reset group to NOT_LOGGED_IN) + $extensionAttr = $quote->getExtensionAttributes(); + $extensionAttr->setAutocustomergroupNewId($newGroup ?: $customerGroupId); + $quote->setExtensionAttributes($extensionAttr); + + return $this; + } + + /** + * Process Group Change + * @param $newGroup + * @param Quote $quote + * @param $customer + * @param $shippingAssignment + * @param $total + */ + private function updateGroup($newGroup, Quote $quote, $customer, $shippingAssignment, $total) + { + if ($newGroup != $quote->getCustomerGroupId()) { + $this->customerSession->setCustomerGroupId($newGroup); + $customer = $quote->getCustomer(); + if ($customer && $customer->getId() !== null) { + $customer->setGroupId($newGroup); + $quote->setCustomer($customer); + } + $this->logger->info( + "Gw/AutoCustomerGroup/Model/Collector/AutoCustomerGroup::updateGroup() : Setting quote Group to " . + $newGroup + ); + $quote->setCustomerGroupId($newGroup); + + //The group has changed. We need to rerun the Tax collectors. + foreach ($this->additionalCollectors as $collector) { + if ($collector) { + $collector->collect($quote, $shippingAssignment, $total); + } + } + } + } +} diff --git a/Model/Config/Source/Environment.php b/Model/Config/Source/Environment.php new file mode 100644 index 0000000..4ae9ed3 --- /dev/null +++ b/Model/Config/Source/Environment.php @@ -0,0 +1,36 @@ + self::ENVIRONMENT_SANDBOX, 'label' => __('Sandbox')], + ['value' => self::ENVIRONMENT_PRODUCTION, 'label' => __('Production')] + ]; + } + + /** + * Get options in "key-value" format + * + * @return array + */ + public function toArray() + { + return [ + self::ENVIRONMENT_SANDBOX => __('Sandbox'), + self::ENVIRONMENT_PRODUCTION => __('Production') + ]; + } +} diff --git a/Model/Config/Source/Group.php b/Model/Config/Source/Group.php new file mode 100644 index 0000000..0895db4 --- /dev/null +++ b/Model/Config/Source/Group.php @@ -0,0 +1,60 @@ +_groupManagement = $groupManagement; + $this->_converter = $converter; + $this->groupSourceLoggedInOnly = $groupSourceForLoggedInCustomers + ?: ObjectManager::getInstance()->get(GroupSourceLoggedInOnlyInterface::class); + } + + /** + * @inheritdoc + */ + public function toOptionArray() + { + if (!$this->_options) { + $this->_options = $this->groupSourceLoggedInOnly->toOptionArray(); + array_unshift($this->_options, ['value' => 0, 'label' => __('NOT LOGGED IN')]); + } + + return $this->_options; + } +} diff --git a/Model/JSConfig.php b/Model/JSConfig.php new file mode 100644 index 0000000..d6a9253 --- /dev/null +++ b/Model/JSConfig.php @@ -0,0 +1,72 @@ +autoCustomerGroup = $autoCustomerGroup; + $this->storeManager = $storeManager; + $this->taxSchemes = $taxSchemes; + } + + /** + * The config for the JS VAT Component + * + * @return array + */ + public function getComponentConfig() + { + $storeId = $this->storeManager->getStore()->getId(); + if ($this->autoCustomerGroup->isModuleEnabled($storeId)) { + $jsSchemes = []; + + foreach ($this->taxSchemes->getEnabledTaxSchemes($storeId) as $taxScheme) { + $jsSchemes[$taxScheme->getSchemeId()] = [ + 'countries' => $taxScheme->getSchemeCountries(), + 'prompt' => $taxScheme->getFrontEndPrompt($storeId) + ]; + } + return [ + 'template' => 'Gw_AutoCustomerGroup/tax-id-element', + 'label' => $this->autoCustomerGroup->getFrontendLabel($storeId), + 'additionalClasses' => 'autocustomergroup_tax_id', + 'delay' => 1500, + 'validationEnabled' => $this->autoCustomerGroup->isModuleEnabled($storeId), + 'schemes' => $jsSchemes, + 'storeId' => $storeId, + 'component' => 'Gw_AutoCustomerGroup/js/tax-id-element' + ]; + } else { + //Legacy config if module is disabled + return []; + } + } +} diff --git a/Model/OrderTaxCollected.php b/Model/OrderTaxCollected.php new file mode 100644 index 0000000..361951e --- /dev/null +++ b/Model/OrderTaxCollected.php @@ -0,0 +1,48 @@ +orderTaxSchemeCollectionFactory = $orderTaxSchemeCollectionFactory; + } + + public function getTaxCollectedDetails(int $orderId): array + { + try { + $taxDetails = []; + $orderTaxSchemes = $this->orderTaxSchemeCollectionFactory->create()->loadByOrderId($orderId); + foreach ($orderTaxSchemes->getItems() as $orderTaxScheme) { + /** @var OrderTaxSchemeInterface $orderTaxScheme */ + $taxDetails[] = [ + 'name' => $orderTaxScheme->getName(), + 'id' => $orderTaxScheme->getReference(), + 'type' => null + ]; + } + return $taxDetails; + } catch (Exception $e) { + return []; + } + } +} diff --git a/Model/OrderTaxScheme.php b/Model/OrderTaxScheme.php new file mode 100644 index 0000000..d28fb48 --- /dev/null +++ b/Model/OrderTaxScheme.php @@ -0,0 +1,127 @@ +_init(OrderTaxSchemeResource::class); + } + + public function getOrderId(): int + { + return $this->getData('order_id'); + } + + public function setOrderId(int $orderId): void + { + $this->setData('order_id', $orderId); + } + + public function getReference(): ?string + { + return $this->getData('reference'); + } + + public function setReference(?string $reference): void + { + $this->setData('reference', $reference); + } + + public function getName(): ?string + { + return $this->getData('name'); + } + + public function setName(?string $name): void + { + $this->setData('name', $name); + } + + public function getStoreCurrency(): ?string + { + return $this->getData('store_currency'); + } + + public function setStoreCurrency(?string $currency): void + { + $this->setData('store_currency', $currency); + } + + public function getBaseCurrency(): ?string + { + return $this->getData('base_currency'); + } + + public function setBaseCurrency(?string $currency): void + { + $this->setData('base_currency', $currency); + } + + public function getSchemeCurrency(): ?string + { + return $this->getData('scheme_currency'); + } + + public function setSchemeCurrency(?string $currency): void + { + $this->setData('scheme_currency', $currency); + } + + public function getExchangeRateBaseToStore(): float + { + return $this->getData('exchange_rate_base_to_store'); + } + + public function setExchangeRateBaseToStore(float $rate): void + { + $this->setData('exchange_rate_base_to_store', $rate); + } + + public function getExchangeRateSchemeToBase(): float + { + return $this->getData('exchange_rate_scheme_to_base'); + } + + public function setExchangeRateSchemeToBase(float $rate): void + { + $this->setData('exchange_rate_scheme_to_base', $rate); + } + + public function getImportThresholdStore(): float + { + return $this->getData('import_threshold_store'); + } + + public function setImportThresholdStore(float $threshold): void + { + $this->setData('import_threshold_store', $threshold); + } + + public function getImportThresholdBase(): float + { + return $this->getData('import_threshold_base'); + } + + public function setImportThresholdBase(float $threshold): void + { + $this->setData('import_threshold_base', $threshold); + } + + public function getImportThresholdScheme(): float + { + return $this->getData('import_threshold_scheme'); + } + + public function setImportThresholdScheme(float $threshold): void + { + $this->setData('import_threshold_scheme', $threshold); + } +} diff --git a/Model/ResourceModel/OrderTaxScheme.php b/Model/ResourceModel/OrderTaxScheme.php new file mode 100644 index 0000000..6597dac --- /dev/null +++ b/Model/ResourceModel/OrderTaxScheme.php @@ -0,0 +1,12 @@ +_init('sales_order_tax_scheme', 'order_tax_scheme_id'); + } +} diff --git a/Model/ResourceModel/OrderTaxScheme/Collection.php b/Model/ResourceModel/OrderTaxScheme/Collection.php new file mode 100644 index 0000000..6284e02 --- /dev/null +++ b/Model/ResourceModel/OrderTaxScheme/Collection.php @@ -0,0 +1,43 @@ +_init( + OrderTaxScheme::class, + OrderTaxSchemeResource::class + ); + } + + /** + * Retrieve order tax scheme collection by order + * + * @param OrderInterface $order + * @return Collection + */ + public function loadByOrder(OrderInterface $order): Collection + { + $orderId = $order->getId(); + $this->getSelect()->where('main_table.order_id = ?', (int)$orderId); + return $this->load(); + } + + /** + * Retrieve order tax scheme collection by order identifier + * + * @param int $orderId + * @return Collection + */ + public function loadByOrderId(int $orderId): Collection + { + $this->getSelect()->where('main_table.order_id = ?', $orderId); + return $this->load(); + } +} diff --git a/Model/Source/TaxSchemes.php b/Model/Source/TaxSchemes.php new file mode 100644 index 0000000..c2bb491 --- /dev/null +++ b/Model/Source/TaxSchemes.php @@ -0,0 +1,35 @@ +taxSchemes = $taxSchemes; + } + + /** + * @return array + */ + public function getAllOptions() + { + if (empty($this->_options)) { + $this->_options = $this->taxSchemes->toOptionArray(); + } + return $this->_options; + } +} diff --git a/Model/TaxIdCheckResponse.php b/Model/TaxIdCheckResponse.php new file mode 100644 index 0000000..b81d157 --- /dev/null +++ b/Model/TaxIdCheckResponse.php @@ -0,0 +1,58 @@ +setData('is_valid', $valid); + } + + public function getIsValid(): bool + { + return $this->getData('is_valid') ?: false; + } + + public function setRequestSuccess(bool $request_success): void + { + $this->setData('request_success', $request_success); + } + + public function getRequestSuccess(): bool + { + return $this->getData('request_success') ?: false; + } + + public function setRequestDate(string $date): void + { + $this->setData('request_date', $date); + } + + public function getRequestDate(): string + { + return $this->getData('request_date') ?: ""; + } + + public function setRequestIdentifier(string $identifier): void + { + $this->setData('request_identifier', $identifier); + } + + public function getRequestIdentifier(): string + { + return $this->getData('request_identifier') ?: ""; + } + + public function setRequestMessage(string $message): void + { + $this->setData('request_message', $message); + } + + public function getRequestMessage(): string + { + return $this->getData('request_message') ?: ""; + } +} diff --git a/Model/TaxRuleCollection.php b/Model/TaxRuleCollection.php new file mode 100644 index 0000000..2869f19 --- /dev/null +++ b/Model/TaxRuleCollection.php @@ -0,0 +1,31 @@ +getExtensionAttributes()->getTaxScheme(); + if ($taxScheme) { + $collectionItem->setData( + 'extension_attributes', + $taxRule->getExtensionAttributes() + ); + $collectionItem->setData('tax_scheme_id', $taxScheme->getSchemeId()); + } + return $collectionItem; + } +} diff --git a/Model/TaxSchemes.php b/Model/TaxSchemes.php new file mode 100644 index 0000000..7ac92f5 --- /dev/null +++ b/Model/TaxSchemes.php @@ -0,0 +1,73 @@ +taxSchemes = $taxSchemes; + } + + /** + * Get available Tax Schemes + * + * @return TaxSchemeInterface[] + */ + public function getTaxSchemes(): array + { + return $this->taxSchemes; + } + + /** + * Get list of enabled tax schemes + * + * @param int|null $storeId + * @return TaxSchemeInterface[] + */ + public function getEnabledTaxSchemes($storeId): array + { + $enabledSchemes = []; + foreach ($this->taxSchemes as $taxScheme) { + if ($taxScheme->isEnabled($storeId)) { + $enabledSchemes[] = $taxScheme; + } + } + return $enabledSchemes; + } + + /** + * return list of Tax Schemes as option array + * + * @return array + */ + public function toOptionArray(): array + { + $array = []; + foreach ($this->taxSchemes as $taxScheme) { + $array[] = ['value' => $taxScheme->getSchemeId(), 'label' => $taxScheme->getSchemeName()]; + } + return $array; + } + + /** + * Get the instance of the tax scheme by code + * + * @param string $code + * @return null|TaxSchemeInterface + */ + public function getTaxScheme($code): ?TaxSchemeInterface + { + return isset($this->taxSchemes[$code]) ? $this->taxSchemes[$code] : null; + } +} diff --git a/Observer/SalesModelServiceQuoteSubmitBefore.php b/Observer/SalesModelServiceQuoteSubmitBefore.php new file mode 100644 index 0000000..e235acf --- /dev/null +++ b/Observer/SalesModelServiceQuoteSubmitBefore.php @@ -0,0 +1,89 @@ +customerSession = $customerSession; + $this->logger = $logger; + $this->autoCustomerGroup = $autoCustomerGroup; + } + + /** + * Observer for sales_model_service_quote_submit_before + * + * @param Observer $observer + * @return void + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function execute(Observer $observer) + { + /** @var Quote $quote */ + $quote = $observer->getData('quote'); + /** @var Order $order */ + $order = $observer->getData('order'); + + if (!$this->autoCustomerGroup->isModuleEnabled($quote->getStoreId()) || + $quote->getCustomer()->getDisableAutoGroupChange() || + !$quote->getItemsCount()) { + return; + } + + $extensionAttr = $quote->getExtensionAttributes(); + $newGroup = null; + if ($extensionAttr && $extensionAttr->getAutocustomergroupNewId()) { + $newGroup = $extensionAttr->getAutocustomergroupNewId(); + } + if ($newGroup && $newGroup != $quote->getCustomerGroupId()) { + $this->customerSession->setCustomerGroupId($newGroup); + $quoteCustomer = $quote->getCustomer(); + if ($quoteCustomer && $quoteCustomer->getId() !== null) { + $quoteCustomer->setGroupId($newGroup); + $quote->setCustomer($quoteCustomer); + } + $quote->setCustomerGroupId($newGroup); + $order->setCustomerGroupId($newGroup); + $this->logger->info( + "Gw/AutoCustomerGroup/Observer/SalesModelServiceQuoteSubmitBefore::execute() : " . + "Overriding Quote and Order CustomerGroupId just before order is placed. Adjusting to " . + $newGroup + ); + } + } +} diff --git a/Observer/SalesModelServiceQuoteSubmitSuccess.php b/Observer/SalesModelServiceQuoteSubmitSuccess.php new file mode 100644 index 0000000..a97c46a --- /dev/null +++ b/Observer/SalesModelServiceQuoteSubmitSuccess.php @@ -0,0 +1,132 @@ +taxRuleRepository = $taxRuleRepository; + $this->otsFactory = $otsFactory; + $this->autoCustomerGroup = $autoCustomerGroup; + $this->logger = $logger; + } + + /** + * @param Observer $observer + * @return void + */ + public function execute(Observer $observer) + { + //Loop through the applied taxes on the order and extract the Tax Rule IDs that have been triggered + /** @var Order $order */ + $order = $observer->getData('order'); + $storeId = $order->getStoreId(); + if (!$this->autoCustomerGroup->isSalesOrderTaxSchemeEnabled($storeId)) { + return; + } + $orderEA = $order->getExtensionAttributes(); + $orderrules = []; + if ($orderEA) { + $appliedTaxes = $orderEA->getAppliedTaxes(); + if ($appliedTaxes) { + foreach ($appliedTaxes as $appliedTax) { + $appliedTaxEA = $appliedTax->getExtensionAttributes(); + if ($appliedTaxEA) { + $rates = $appliedTaxEA->getRates(); + if ($rates) { + foreach ($rates as $rate) { + $ratesEA = $rate->getExtensionAttributes(); + if ($ratesEA) { + $orderrules = array_unique( + array_merge($orderrules, $ratesEA->getTaxRuleIds() ?: []) + ); + } + } + } + } + } + } + } + + //Loop through the Tax Rules that have been triggered and extract the Tax Schemes Linked to those rules. + $taxSchemes = []; + foreach ($orderrules as $orderrule) { + try { + $taxRule = $this->taxRuleRepository->get($orderrule); + $taxRuleEA = $taxRule->getExtensionAttributes(); + if ($taxRuleEA && $taxRuleEA->getTaxScheme()) { + $taxSchemes[] = $taxRuleEA->getTaxScheme(); + } + } catch (Exception $e) { + //Could not load Tax Rule + } + } + $taxSchemes = array_unique($taxSchemes); + + //Save the tax scheme info in the sales_order_tax_scheme table. + foreach ($taxSchemes as $taxScheme) { + $storeId = $order->getStoreId(); + $baseToStore = 1 / ($order->getStoreToBaseRate() == 0.0 ? 1.0 : $order->getStoreToBaseRate()); + $thresholdInBaseCurrency = (float)$taxScheme->getThresholdInSchemeCurrency($storeId) * $taxScheme->getSchemeExchangeRate($storeId); + $orderTaxScheme = $this->otsFactory->create(); + $orderTaxScheme->setOrderId((int)$order->getEntityId()); + $orderTaxScheme->setReference($taxScheme->getSchemeRegistrationNumber($storeId)); + $orderTaxScheme->setName($taxScheme->getSchemeName()); + $orderTaxScheme->setStoreCurrency($order->getOrderCurrencyCode()); + $orderTaxScheme->setBaseCurrency($order->getBaseCurrencyCode()); + $orderTaxScheme->setSchemeCurrency($taxScheme->getSchemeCurrencyCode()); + $orderTaxScheme->setExchangeRateBaseToStore((float)$baseToStore); + $orderTaxScheme->setExchangeRateSchemeToBase((float)$taxScheme->getSchemeExchangeRate($storeId)); + $orderTaxScheme->setImportThresholdBase((float)$thresholdInBaseCurrency); + $orderTaxScheme->setImportThresholdStore((float)$thresholdInBaseCurrency * $baseToStore); + $orderTaxScheme->setImportThresholdScheme((float)$taxScheme->getThresholdInSchemeCurrency($storeId)); + $orderTaxScheme->save(); + $this->logger->info( + "Gw/AutoCustomerGroup/Observer/SalesModelServiceQuoteSubmitSuccess::execute() : Saving Tax " . + "Scheme to database " . $orderTaxScheme->getName() + ); + } + } +} diff --git a/Plugin/ConvertAppliedTaxesPlugin.php b/Plugin/ConvertAppliedTaxesPlugin.php new file mode 100644 index 0000000..a843dda --- /dev/null +++ b/Plugin/ConvertAppliedTaxesPlugin.php @@ -0,0 +1,38 @@ + $appliedTax) { + foreach ($appliedTax['rates'] as $index2 => $rate) { + $extAtt = $appliedTaxes[$appliedTax['id']]->getRates()[$rate['code']]->getExtensionAttributes(); + $result[$index1]['rates'][$index2]['extension_attributes']['tax_rule_ids'] = $extAtt->getTaxRuleIds() ?: []; + } + } + return $result; + } +} diff --git a/Plugin/Customer/BeforeAddressSaveObserverPlugin.php b/Plugin/Customer/BeforeAddressSaveObserverPlugin.php new file mode 100644 index 0000000..a6d15fc --- /dev/null +++ b/Plugin/Customer/BeforeAddressSaveObserverPlugin.php @@ -0,0 +1,86 @@ +autoCustomerGroup = $autoCustomerGroup; + $this->storeManager = $storeManager; + } + + /** + * Disable Before Address Save Observer + * + * @param BeforeAddressSaveObserver $subject + * @param callable $proceed + * @param Observer $observer + * @return void + * @throws NoSuchEntityException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundExecute( + BeforeAddressSaveObserver $subject, + callable $proceed, + Observer $observer + ) { + $returnValue = null; + $storeId = $this->storeManager->getStore()->getId(); + if (!$this->autoCustomerGroup->isModuleEnabled($storeId)) { + $returnValue = $proceed($observer); + } else { + /** Magento\Customer\Model\Address $customerAddress */ + $customerAddress = $observer->getCustomerAddress(); + $customer = $customerAddress->getCustomer(); + + if ($customerAddress->getShouldIgnoreValidation()) { + return $returnValue; + } + + //We only validate the VAT Number and store the results. We do not change the + //Customer group at this stage, as the this depends on order value, which we + //Don't have at this stage. + if (!empty($customerAddress->getVatId()) && + !empty($customerAddress->getVatId()) && + $customer->getStore()->getId()) { + $taxIdCheckResponse = $this->autoCustomerGroup->checkTaxId( + $customerAddress->getCountryId(), + $customerAddress->getVatId(), + $customer->getStore()->getId() + ); + + if ($taxIdCheckResponse) { + // Store validation results in corresponding customer address + //$customerAddress->setVatValidationResult($validationResult); + $customerAddress->setVatIsValid($taxIdCheckResponse->getIsValid()); + $customerAddress->setVatRequestId($taxIdCheckResponse->getRequestIdentifier()); + $customerAddress->setVatRequestDate($taxIdCheckResponse->getRequestDate()); + } + } + } + return $returnValue; + } +} diff --git a/Plugin/Customer/DisableAfterAddressSaveObserverPlugin.php b/Plugin/Customer/DisableAfterAddressSaveObserverPlugin.php new file mode 100644 index 0000000..99a421c --- /dev/null +++ b/Plugin/Customer/DisableAfterAddressSaveObserverPlugin.php @@ -0,0 +1,54 @@ +autoCustomerGroup = $autoCustomerGroup; + $this->storeManager = $storeManager; + } + + /** + * Disable After Address Save Observer + * + * @param AfterAddressSaveObserver $subject + * @param callable $proceed + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundExecute( + AfterAddressSaveObserver $subject, + callable $proceed, + Observer $observer + ) { + $returnValue = null; + $storeId = $this->storeManager->getStore()->getId(); + if (!$this->autoCustomerGroup->isModuleEnabled($storeId)) { + $returnValue = $proceed($observer); + } + return $returnValue; + } +} diff --git a/Plugin/Directory/CurrencyConfigPlugin.php b/Plugin/Directory/CurrencyConfigPlugin.php new file mode 100644 index 0000000..30586ac --- /dev/null +++ b/Plugin/Directory/CurrencyConfigPlugin.php @@ -0,0 +1,56 @@ +autoCustomerGroup = $autoCustomerGroup; + $this->taxSchemes = $taxSchemes; + } + + /** + * Add the scheme currencies to the list of base currencies. This ensures that + * currency downloads will be performed for all scheme currencies. + * + * @param CurrencyConfig $subject + * @param array $result + * @param string $path + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetConfigCurrencies( + CurrencyConfig $subject, + array $result, + string $path + ) { + if ($this->autoCustomerGroup->isModuleEnabled()) { + if ($this->autoCustomerGroup->isCurrencyDownloadEnabled()) { + foreach ($this->taxSchemes->getTaxSchemes() as $taxScheme) { + $result[] = $taxScheme->getSchemeCurrencyCode(); + } + } + } + return array_unique($result); + } +} diff --git a/Plugin/Quote/DisableCollectTotalsObserverPlugin.php b/Plugin/Quote/DisableCollectTotalsObserverPlugin.php new file mode 100644 index 0000000..59c14ab --- /dev/null +++ b/Plugin/Quote/DisableCollectTotalsObserverPlugin.php @@ -0,0 +1,58 @@ +autoCustomerGroup = $autoCustomerGroup; + $this->storeManager = $storeManager; + } + + /** + * Disable Collect Totals Observer + * + * @param CollectTotalsObserver $subject + * @param callable $proceed + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundExecute( + CollectTotalsObserver $subject, + callable $proceed, + Observer $observer + ) { + $returnValue = null; + $storeId = $this->storeManager->getStore()->getId(); + if (!$this->autoCustomerGroup->isModuleEnabled($storeId)) { + $returnValue = $proceed($observer); + } + return $returnValue; + } +} diff --git a/Plugin/Tax/TaxRuleRepositoryPlugin.php b/Plugin/Tax/TaxRuleRepositoryPlugin.php new file mode 100644 index 0000000..014b15c --- /dev/null +++ b/Plugin/Tax/TaxRuleRepositoryPlugin.php @@ -0,0 +1,108 @@ +taxSchemes = $taxSchemes; + } + + /** + * Restore the value of tax_scheme_id to extension attribute + * + * @param TaxRuleRepository $subject + * @param Rule $result + * @param int $ruleId + * @return Rule + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGet( + TaxRuleRepository $subject, + Rule $result, + int $ruleId + ): Rule { + $taxSchemeId = $result->getData('tax_scheme_id'); + $taxScheme = $this->taxSchemes->getTaxScheme($taxSchemeId); + if ($taxScheme) { + $extensionAttributes = $result->getExtensionAttributes(); + $extensionAttributes->setTaxScheme($this->taxSchemes->getTaxScheme($taxSchemeId)); + $result->setExtensionAttributes($extensionAttributes); + } + $result->unsetData('tax_scheme_id'); + return $result; + } + + /** + * Save the value of tax_scheme_id from extension attribute + * + * @param TaxRuleRepository $subject + * @param TaxRuleInterface $entity + * @return TaxRuleInterface[] + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeSave( + TaxRuleRepository $subject, + TaxRuleInterface $entity + ): array { + $extensionAttributes = $entity->getExtensionAttributes(); + $taxScheme = $extensionAttributes->getTaxScheme(); + /** @var Rule $entity */ + if ($taxScheme) { + $entity->setData('tax_scheme_id', $taxScheme->getSchemeId()); + } else { + $entity->setData('tax_scheme_id'); + } + return [$entity]; + } + + /** + * Restore the value of tax_scheme_id to extension attribute + * + * @param TaxRuleRepository $subject + * @param TaxRuleSearchResultsInterface $result + * @param SearchCriteriaInterface $searchCriteria + * @return TaxRuleSearchResultsInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetList( + TaxRuleRepository $subject, + TaxRuleSearchResultsInterface $result, + SearchCriteriaInterface $searchCriteria + ): TaxRuleSearchResultsInterface { + $taxRules = []; + foreach ($result->getItems() as $entity) { + /** @var Rule $entity */ + $taxSchemeId = $entity->getData('tax_scheme_id'); + $taxScheme = $this->taxSchemes->getTaxScheme($taxSchemeId); + if ($taxScheme) { + $extensionAttributes = $entity->getExtensionAttributes(); + $extensionAttributes->setTaxScheme($taxScheme); + $entity->setExtensionAttributes($extensionAttributes); + } + $taxRules[] = $entity; + } + $result->setItems($taxRules); + return $result; + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..2fa4d5e --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +

    AutoCustomerGroup

    +

    Magento 2 Module - Auto Assign Customer Group based on Tax Scheme validation

    + +

    Features

    + + +

    Overview

    +

    Changes introduced around the world to various countries tax schemes to better support online shopping, are not well support by the Magento Tax system. These changes are required URGENTLY, and while Magento consider the changes required and work towards a permanent solution, this module can be used as an interim measure.

    +

    The module should be considered BETA. I encourage users to analyse the code, suggest improvements, generate PR's where applicable.

    +

    Once customers have been placed in an appropriate group, tax rules can be configured to apply different taxes to each group, depending on what is required.

    +

    When enabled, this module replaces the existing Magento VIV functionality with a new, more extendable system. When the module is disabled, the existing Magento functionality is restored.

    +

    The module allows different base currencies to be used per website, if the price scope is set per website.

    + +

    General

    + + +

    Frontend Validation Feedback

    +

    When enabled, this module replaces the frontend Form Element for the VAT/Tax ID Input box. If the currently selected Country has a Tax Scheme +associated with it, and the Tax Scheme enabled, and a valid format VAT/Tax Id is input on the frontend, then the Id is validated by the relevant +Tax Scheme and the results displayed to the customer. As soon as a country is selected that has a valid Tax Scheme associated with +it, the customer is presented with a prompt above the input field, notifying what they need to enter.

    +

    Valid Irish VAT Number

    + +

    Correct format but not valid

    + +

    Wrong Format

    + +

    Valid UK VAT Numnber

    + + +

    Tax Rule to Tax Scheme Links

    +

    The module allows you to link each tax rule to a particular tax scheme. In post order functions, this allows you to query this module using order details, and obtain the list of tax rules that were applicable to the order, and return the TAX Scheme Registration Numbers linked to these rules. This is useful +when generating invoices for example.

    +

    The links can be set under the existing Tax Rules Screens

    + + + +

    Sales Order Grid

    +

    The module introduces a new Sales Order Grid column that will display details of the Tax Scheme used for the order.

    + + +

    Getting Information on Tax Schemes used on Order

    +

    This module stores additional information into the sales_order_tax_scheme table whenever +an order is placed that triggered a tax rule linked to a Tax Scheme.

    +

    This information can be easily accessed so that information on which tax schemes were +used on an order, can be included on the Invoice PDF's for example.

    +

    The following code shows how this can be achieved.

    +
    +
    +    
    +    use Gw\AutoCustomerGroup\Model\ResourceModel\OrderTaxScheme\CollectionFactory;
    +
    +    /**
    +     * @var CollectionFactory
    +     */
    +    private $orderTaxSchemeCollectionFactory;
    +
    +    ...
    +    ...
    +    ...
    +
    +    $orderTaxSchemes = $this->orderTaxSchemeCollectionFactory->create()->loadByOrder($order);
    +    foreach ($orderTaxSchemes as $orderTaxScheme) {
    +        $storeCurrency = $this->currencyFactory->create()->load($orderTaxScheme->getStoreCurrency());
    +        $schemeCurrency = $this->currencyFactory->create()->load($orderTaxScheme->getSchemeCurrency());
    +        $baseCurrency = $this->currencyFactory->create()->load($orderTaxScheme->getBaseCurrency());
    +
    +        output("TAX Summary - " . $orderTaxScheme->getName());
    +        output("Registration Number - " . $orderTaxScheme->getReference());
    +    }
    +
    +
    diff --git a/Test/Integration/AutoGroupTest.php b/Test/Integration/AutoGroupTest.php new file mode 100644 index 0000000..20e039f --- /dev/null +++ b/Test/Integration/AutoGroupTest.php @@ -0,0 +1,514 @@ +objectManager = Bootstrap::getObjectManager(); + $this->groupRepository = $this->objectManager->get(GroupRepositoryInterface::class); + $this->config = $this->objectManager->get(ReinitableConfigInterface::class); + $this->guestCartManagement = $this->objectManager->get(GuestCartManagementInterface::class); + $this->guestCartRepository = $this->objectManager->get(GuestCartRepositoryInterface::class); + $this->orderRepository = $this->objectManager->get(OrderRepository::class); + $this->quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $this->groupFactory = $this->objectManager->get(GroupInterfaceFactory::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->ruleRepository = $this->objectManager->get(RuleRepository::class); + $this->ruleFactory = $this->objectManager->get(RuleFactory::class); + $this->addressFactory = $this->objectManager->get(AddressFactory::class); + $this->productFactory = $this->objectManager->get(ProductFactory::class); + } + + /** + * @param int $qty + * @param float $price + * @param string $merchantCountry + * @param string|null $merchantPostCode + * @param string $destinationCountry + * @param string|null $destinationPostcode + * @param string|null $destinationVatId + * @param string $customerGroup + * @param float $percentageDiscount + * + * @return void + * @throws CouldNotSaveException + * @throws InputException + * @throws InvalidTransitionException + * @throws LocalizedException + * @throws NoSuchEntityException + * @dataProvider dataProviderForTestAutoCustomerGroup + * @magentoConfigFixture current_store autocustomergroup/general/enabled 1 + * @magentoAdminConfigFixture current_store currency/options/default GBP + * @magentoAdminConfigFixture current_store currency/options/base GBP + * @magentoConfigFixture current_store autocustomergroup/ukvat/enabled 1 + * @magentoConfigFixture current_store autocustomergroup/ukvat/registrationnumber GB553557881 + * @magentoConfigFixture current_store autocustomergroup/ukvat/environment sandbox + * @magentoConfigFixture current_store autocustomergroup/ukvat/usemagentoexchangerate 0 + * @magentoConfigFixture current_store autocustomergroup/ukvat/exchangerate 1 + * @magentoConfigFixture current_store autocustomergroup/ukvat/importthreshold 135 + * @magentoConfigFixture current_store autocustomergroup/euvat/enabled 1 + * @magentoConfigFixture current_store autocustomergroup/euvat/registrationnumber IE8256796U + * @magentoConfigFixture current_store autocustomergroup/euvat/environment sandbox + * @magentoConfigFixture current_store autocustomergroup/euvat/usemagentoexchangerate 0 + * @magentoConfigFixture current_store autocustomergroup/euvat/exchangerate 0.88603 + * @magentoConfigFixture current_store autocustomergroup/euvat/importthreshold 150 + * @magentoConfigFixture current_store autocustomergroup/norwayvoec/enabled 1 + * @magentoConfigFixture current_store autocustomergroup/norwayvoec/registrationnumber 12345 + * @magentoConfigFixture current_store autocustomergroup/norwayvoec/usemagentoexchangerate 0 + * @magentoConfigFixture current_store autocustomergroup/norwayvoec/exchangerate 0.08540 + * @magentoConfigFixture current_store autocustomergroup/norwayvoec/importthreshold 3000 + * @magentoConfigFixture current_store autocustomergroup/australiagst/enabled 1 + * @magentoConfigFixture current_store autocustomergroup/australiagst/usemagentoexchangerate 0 + * @magentoConfigFixture current_store autocustomergroup/australiagst/exchangerate 0.5666 + * @magentoConfigFixture current_store autocustomergroup/australiagst/importthreshold 1000 + * @magentoConfigFixture current_store autocustomergroup/newzealandgst/enabled 1 + * @magentoConfigFixture current_store autocustomergroup/newzealandgst/usemagentoexchangerate 0 + * @magentoConfigFixture current_store autocustomergroup/newzealandgst/exchangerate 0.52 + * @magentoConfigFixture current_store autocustomergroup/newzealandgst/importthreshold 1000 + * @magentoConfigFixture current_store general/region/state_required "" + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testAutoCustomerGroup( + int $qty, + float $price, + string $merchantCountry, + ?string $merchantPostCode, + string $destinationCountry, + ?string $destinationPostcode, + ?string $destinationVatId, + string $customerGroup, + float $percentageDiscount, + bool $onlineTest = false + ): void { + $storeId = $this->storeManager->getStore()->getId(); + + $this->config->setValue( + "autocustomergroup/newzealandgst/validate_online", + $onlineTest, + ScopeInterface::SCOPE_STORE + ); + + $groups = [0]; + $groups[] = $this->createGroupAndAssign('uk_domestic', 'autocustomergroup/ukvat/domestic'); + $groups[] = $this->createGroupAndAssign('uk_intraeu_b2b', 'autocustomergroup/ukvat/intraeub2b'); + $groups[] = $this->createGroupAndAssign('uk_intraeu_b2c', 'autocustomergroup/ukvat/intraeub2c'); + $groups[] = $this->createGroupAndAssign('uk_import_b2b', 'autocustomergroup/ukvat/importb2b'); + $groups[] = $this->createGroupAndAssign('uk_import_taxed', 'autocustomergroup/ukvat/importtaxed'); + $groups[] = $this->createGroupAndAssign('uk_import_untaxed', 'autocustomergroup/ukvat/importuntaxed'); + + $groups[] = $this->createGroupAndAssign('eu_domestic', 'autocustomergroup/euvat/domestic'); + $groups[] = $this->createGroupAndAssign('eu_intraeu_b2b', 'autocustomergroup/euvat/intraeub2b'); + $groups[] = $this->createGroupAndAssign('eu_intraeu_b2c', 'autocustomergroup/euvat/intraeub2c'); + $groups[] = $this->createGroupAndAssign('eu_import_b2b', 'autocustomergroup/euvat/importb2b'); + $groups[] = $this->createGroupAndAssign('eu_import_taxed', 'autocustomergroup/euvat/importtaxed'); + $groups[] = $this->createGroupAndAssign('eu_import_untaxed', 'autocustomergroup/euvat/importuntaxed'); + + $groups[] = $this->createGroupAndAssign('norway_domestic', 'autocustomergroup/norwayvoec/domestic'); + $groups[] = $this->createGroupAndAssign('norway_import_b2b', 'autocustomergroup/norwayvoec/importb2b'); + $groups[] = $this->createGroupAndAssign('norway_import_taxed', 'autocustomergroup/norwayvoec/importtaxed'); + $groups[] = $this->createGroupAndAssign('norway_import_untaxed', 'autocustomergroup/norwayvoec/importuntaxed'); + + $groups[] = $this->createGroupAndAssign('australia_domestic', 'autocustomergroup/australiagst/domestic'); + $groups[] = $this->createGroupAndAssign('australia_import_b2b', 'autocustomergroup/australiagst/importb2b'); + $groups[] = $this->createGroupAndAssign('australia_import_taxed', 'autocustomergroup/australiagst/importtaxed'); + $groups[] = $this->createGroupAndAssign( + 'australia_import_untaxed', + 'autocustomergroup/australiagst/importuntaxed' + ); + + $groups[] = $this->createGroupAndAssign('newzealand_domestic', 'autocustomergroup/newzealandgst/domestic'); + $groups[] = $this->createGroupAndAssign('newzealand_import_b2b', 'autocustomergroup/newzealandgst/importb2b'); + $groups[] = $this->createGroupAndAssign( + 'newzealand_import_taxed', + 'autocustomergroup/newzealandgst/importtaxed' + ); + $groups[] = $this->createGroupAndAssign( + 'newzealand_import_untaxed', + 'autocustomergroup/newzealandgst/importuntaxed' + ); + + if ($percentageDiscount > 0) { + $this->createSalesRule($groups, $percentageDiscount, $storeId); + } + + $this->config->setValue( + StoreInformation::XML_PATH_STORE_INFO_COUNTRY_CODE, + $merchantCountry, + ScopeInterface::SCOPE_STORE + ); + $this->config->setValue( + StoreInformation::XML_PATH_STORE_INFO_POSTCODE, + $merchantPostCode, + ScopeInterface::SCOPE_STORE + ); + + $product = $this->productFactory->create(); + $product->setTypeId('simple') + ->setId(1) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Product') + ->setSku('simple') + ->setPrice($price) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 0]) + ->save(); + + $addressData = [ + 'telephone' => 12345, + 'postcode' => $destinationPostcode, + 'country_id' => $destinationCountry, + 'city' => 'City', + 'street' => ['Street'], + 'lastname' => 'Lastname', + 'firstname' => 'Firstname', + 'address_type' => 'shipping', + 'email' => 'some_email@mail.com' + ]; + + $shippingAddress = $this->addressFactory->create(['data' => $addressData]); + $shippingAddress->setAddressType('shipping'); + $shippingAddress->setVatId($destinationVatId); + $billingAddress = $this->addressFactory->create(['data' => $addressData]); + $billingAddress->setAddressType('billing'); + + $maskedCartId = $this->guestCartManagement->createEmptyCart(); + /** @var Quote $quote */ + $quote = $this->guestCartRepository->get($maskedCartId); + + //$quote = $this->quoteFactory->create(); + $quote->setCustomerIsGuest(true) + ->setStoreId($storeId) + ->setReservedOrderId('guest_quote'); + + $quote->addProduct($this->productRepository->get('simple'), $qty); + $quote->setBillingAddress($billingAddress); + $quote->setShippingAddress($shippingAddress); + $quote->getPayment()->setMethod('checkmo'); + $quote->getShippingAddress()->setShippingMethod('flatrate_flatrate')->setCollectShippingRates(true); + $quote->collectTotals(); + + $this->quoteRepository->save($quote); + + $checkoutSession = $this->objectManager->get(CheckoutSession::class); + $checkoutSession->setQuoteId($quote->getId()); + + $orderId = $this->guestCartManagement->placeOrder($maskedCartId); + $order = $this->orderRepository->get($orderId); + $this->assertNotNull($order->getEntityId()); + + $group = $this->groupRepository->getById($order->getCustomerGroupId()); + $this->assertEquals($customerGroup, $group->getCode()); + } + + /** + * @param string $code + * @param string $assign + * @return int + * @throws InputException + * @throws LocalizedException + * @throws NoSuchEntityException + * @throws InvalidTransitionException + */ + public function createGroupAndAssign(string $code, string $assign): int + { + $groupDataObject = $this->groupFactory->create(); + $groupDataObject->setCode($code)->setTaxClassId(3); + $groupId = $this->groupRepository->save($groupDataObject)->getId(); + $this->config->setValue($assign, $groupId, ScopeInterface::SCOPE_STORE); + return (int)$groupId; + } + + public function createSalesRule($groupIds, $percentage, $storeId) + { + $allRules = $this->ruleRepository->getList( + $this->objectManager->get(SearchCriteriaInterface::class) + ); + foreach ($allRules->getItems() as $rule) { + $this->ruleRepository->deleteById($rule->getRuleId()); + } + + $ruleData = [ + 'name' => 'Discount', + 'is_active' => 1, + 'customer_group_ids' => $groupIds, + 'coupon_type' => Rule::COUPON_TYPE_NO_COUPON, + 'simple_action' => 'by_percent', + 'discount_amount' => $percentage, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'website_ids' => [$storeId] + ]; + $salesRule = $this->ruleFactory->create(['data' => $ruleData]); + $salesRule->save(); + } + + /** + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function dataProviderForTestAutoCustomerGroup(): array + { + //Item Qty + //Item Price + //Merchant Country + //Merchant Postcode + //Destination Country + //Destination Postcode + //Tax ID + //Expected Customer Group + //Discount Percentage + //Online Test + return [ + //New Zealand GST + //Threshold is 1000NZD = 520.00GBP + [1, 10, 'NZ', null, 'NZ', "0620", '', 'newzealand_domestic', 0], + + //Online and offline should both show invalid + [1, 10, 'NZ', null, 'NZ', "0620", '1234', 'newzealand_domestic', 0, true], + [1, 10, 'NZ', null, 'NZ', "0620", '1234', 'newzealand_domestic', 0], + + //Online check will show valid but no GST, Offline check will show valid + [1, 10, 'NZ', null, 'NZ', "0620", '9429032097351', 'newzealand_domestic', 0, true], + [1, 10, 'NZ', null, 'NZ', "0620", '9429032097351', 'newzealand_domestic', 0], + + //Online check will show valid with GST, Offline check will show valid + [1, 10, 'GB', 'NE1 1AA', 'NZ', "0620", '9429050853731', 'newzealand_import_b2b', 0, true], + [1, 10, 'GB', 'NE1 1AA', 'NZ', "0620", '9429050853731', 'newzealand_import_b2b', 0], + + //Online check will show valid with GST, Offline check will show valid + [10, 1000, 'GB', 'NE1 1AA', 'NZ', "0620", '9429050853731', 'newzealand_import_b2b', 0, true], + [10, 1000, 'GB', 'NE1 1AA', 'NZ', "0620", '9429050853731', 'newzealand_import_b2b', 0], + + //Online check will show valid with GST, Offline check will show valid + [1, 4000, 'GB', 'NE1 1AA', 'NZ', "0620", '9429049835892', 'newzealand_import_b2b', 0, true], + [1, 4000, 'GB', 'NE1 1AA', 'NZ', "0620", '9429049835892', 'newzealand_import_b2b', 0], + + //Online check will show valid but no GST, Offline check will show valid + [10, 1000, 'GB', 'NE1 1AA', 'NZ', "0620", '9429036975273', 'newzealand_import_untaxed', 0, true], + [10, 1000, 'GB', 'NE1 1AA', 'NZ', "0620", '9429036975273', 'newzealand_import_b2b', 0], + + //Online and offline should both show invalid + [1, 10, 'GB', 'NE1 1AA', 'NZ', "0620", '1234', 'newzealand_import_taxed', 0, true], + [1, 10, 'GB', 'NE1 1AA', 'NZ', "0620", '1234', 'newzealand_import_taxed', 0], + + [1, 10, 'GB', 'NE1 1AA', 'NZ', "0620", '', 'newzealand_import_taxed', 0], + [5, 100, 'GB', 'NE1 1AA', 'NZ', "0620", '', 'newzealand_import_taxed', 0], + [5, 500, 'GB', 'NE1 1AA', 'NZ', "0620", '', 'newzealand_import_taxed', 0], + [1, 520, 'GB', 'NE1 1AA', 'NZ', "0620", '', 'newzealand_import_taxed', 0], + [1, 530, 'GB', 'NE1 1AA', 'NZ', "0620", '', 'newzealand_import_untaxed', 0], + [1, 2000, 'GB', 'NE1 1AA', 'NZ', "0620", '', 'newzealand_import_untaxed', 0], + [5, 600, 'GB', 'NE1 1AA', 'NZ', "0620", '', 'newzealand_import_untaxed', 0], + + //Online and offline should both show invalid + [5, 4000, 'GB', 'NE1 1AA', 'NZ', "0620", '1234', 'newzealand_import_untaxed', 0, true], + [5, 4000, 'GB', 'NE1 1AA', 'NZ', "0620", '1234', 'newzealand_import_untaxed', 0], + + //USA + [1, 10, 'GB', 'NE1 1AA', 'US', '90210', '', 'NOT LOGGED IN', 0 ], + + //Brazil + [1, 10, 'FR', null, 'BR', '73700-000', '', 'NOT LOGGED IN', 0], + [1, 10, 'GB', 'NE1 1AA', 'BR', '73700-000', '', 'NOT LOGGED IN', 0], + + //UK VAT + //Threshold is 135GBP + [1, 10, 'GB', 'NE1 1AA', 'GB', 'NE1 1AA', '', 'uk_domestic', 0], + [1, 10, 'GB', 'BT1 1AA', 'GB', 'NE1 1AA', '', 'uk_domestic', 0], + [1, 10, 'GB', 'NE1 1AA', 'GB', 'BT1 1AA', '', 'uk_domestic', 0], + [1, 10, 'GB', 'NE1 1AA', 'GB', 'NE1 1AA', 'GB948561936944', 'uk_domestic', 0], //VAT is valid + [1, 10, 'IM', 'NE1 1AA', 'GB', 'NE1 1AA', 'GB948561936944', 'uk_domestic', 0], //VAT is valid + [1, 10, 'GB', 'NE1 1AA', 'IM', 'IM1 1AA', 'GB000549615108', 'uk_domestic', 0], //VAT is valid + [1, 10, 'IM', 'IM1 1AA', 'IM', 'IM1 1AA', 'GB000549615108', 'uk_domestic', 0], //VAT is valid + [1, 10, 'GB', 'NE1 1AA', 'GB', 'NE1 1AA', 'GB123', 'uk_domestic', 0], //VAT is invalid + [1, 10, 'GB', 'NE1 1AA', 'GB', 'NE1 1AA', 'GB948561936943', 'uk_domestic', 0], //VAT is invalid + [1, 10, 'FR', null, 'GB', 'BT1 1AA', 'GB948561936944', 'uk_intraeu_b2b', 0], //VAT is invalid + [1, 10, 'FR', null, 'GB', 'BT1 1AA', '', 'uk_intraeu_b2c', 0], + [1, 10, 'FR', null, 'GB', 'NE1 1AA', 'GB948561936944', 'uk_import_b2b', 0], //VAT is valid + [10, 10, 'FR', null, 'GB', 'NE1 1AA', 'GB948561936944', 'uk_import_b2b', 0], //VAT is valid + [1, 10, 'FR', null, 'GB', 'NE1 1AA', '', 'uk_import_taxed', 0], + [20, 10, 'FR', null, 'GB', 'NE1 1AA', '', 'uk_import_taxed', 50], //20 x 10ea = 200 * 50% = 100 + [1, 130, 'FR', null, 'GB', 'NE1 1AA', '', 'uk_import_taxed', 0], + + [14, 10, 'FR', null, 'GB', 'NE1 1AA', '', 'uk_import_untaxed', 0], + [30, 10, 'FR', null, 'GB', 'NE1 1AA', '', 'uk_import_untaxed', 50], //30 x 10ea = 300 * 50% = 200 + + //EU VAT + //45-Threshold is 150EUR = 132.90 GBP + [1, 10, 'IE', null, 'IE', null, '', 'eu_domestic', 0], + [1, 10, 'IE', null, 'IE', null, 'IE8256796U', 'eu_domestic', 0], + [1, 10, 'DE', null, 'IE', null, 'IE8256796U', 'eu_intraeu_b2b', 0], + [1, 10, 'GB', 'BT1 1AA', 'IE', null, 'IE8256796U', 'eu_intraeu_b2b', 0], + [1, 10, 'DE', null, 'IE', null, 'IE8256796Z', 'eu_intraeu_b2c', 0], //VAT is invalid + [1, 10, 'GB', 'BT1 1AA', 'IE', null, '', 'eu_intraeu_b2c', 0], + [1, 10, 'GB', 'NE1 1AA', 'IE', null, 'IE8256796U', 'eu_import_b2b', 0], + //30 x 10ea = 300, * 50% = 150 + [30, 10, 'GB', 'NE1 1AA', 'IE', null, 'IE8256796U', 'eu_import_b2b', 50], + //18 x 10ea = 180, * 50% = 90 + [18, 10, 'GB', 'NE1 1AA', 'IE', null, 'IE8256796U', 'eu_import_b2b', 50], + //16 x 10ea = 160, * 50% = 80 + [16, 10, 'GB', 'NE1 1AA', 'IE', null, 'IE8256796U', 'eu_import_b2b', 50], + [1, 10, 'BR', null, 'IE', null, 'IE8256796U', 'eu_import_b2b', 0], + [1, 10, 'GB', 'NE1 1AA', 'FR', '75001', '', 'eu_import_taxed', 0], + [1, 10, 'GB', 'NE1 1AA', 'IE', null, '123456', 'eu_import_taxed', 0], + [1, 10, 'BR', null, 'IE', null, '', 'eu_import_taxed', 0], + [1, 130, 'BR', null, 'IE', null, '', 'eu_import_taxed', 0], + [1, 140, 'BR', null, 'IE', null, '', 'eu_import_untaxed', 0], + [14, 10, 'GB', 'NE1 1AA', 'FR', '75001', '', 'eu_import_untaxed', 0], + [28, 10, 'GB', 'NE1 1AA', 'FR', '75001', '', 'eu_import_untaxed', 50], //28 x 10ea = 280 * 50% = 140 + + //Norway VOEC + //Threshold is 3000NOK = 256.20GBP + [1, 10, 'NO', '1234', 'NO', '1366', '', 'norway_domestic', 0], + [1, 10, 'NO', '1234', 'NO', '1366', '912345678', 'norway_domestic', 0], //Valid Business No + [1, 10, 'NO', '1234', 'NO', '1366', '2443', 'norway_domestic', 0], //Invalid Business No + [1, 10, 'GB', 'NE1 1AA', 'NO', '1366', '912345678', 'norway_import_b2b', 0], //Valid Business No + [10, 20, 'GB', 'NE1 1AA', 'NO', '1366', '912345678', 'norway_import_b2b', 0], //Valid Business No + [1, 300, 'GB', 'NE1 1AA', 'NO', '1366', '812345678', 'norway_import_b2b', 0], //Valid Business No + [1, 10, 'GB', 'NE1 1AA', 'NO', '1366', '2443', 'norway_import_taxed', 0], //Invalid Business No + [1, 10, 'GB', 'NE1 1AA', 'NO', '1366', '', 'norway_import_taxed', 0], + [10, 200, 'GB', 'NE1 1AA', 'NO', '1366', '', 'norway_import_taxed', 0], + [1, 250, 'GB', 'NE1 1AA', 'NO', '1366', '', 'norway_import_taxed', 0], + [1, 260, 'GB', 'NE1 1AA', 'NO', '1366', '', 'norway_import_untaxed', 0], + [5, 300, 'GB', 'NE1 1AA', 'NO', '1366', '', 'norway_import_untaxed', 0], + [1, 300, 'GB', 'NE1 1AA', 'NO', '1366', '2443', 'norway_import_untaxed', 0], //Invalid Business No + + //Australia GST + //Threshold is 1000AUD = 566.60GBP + [1, 10, 'AU', null, 'AU', '2620', '', 'australia_domestic', 0], + [1, 10, 'AU', null, 'AU', '2620', '1234', 'australia_domestic', 0], //Invalid Business No + //Valid Business No, with GST registration + [1, 10, 'AU', null, 'AU', '2620', '72 629 951 766', 'australia_domestic', 0], + //Valid Business No, with GST registration + [1, 10, 'GB', 'NE1 1AA', 'AU', '2620', '72 629 951 766', 'australia_import_b2b', 0], + //Valid Business No, with GST registration + [10, 1000, 'GB', 'NE1 1AA', 'AU', '2620', '72 629 951 766', 'australia_import_b2b', 0], + //Valid Business No, with GST registration + [1, 4000, 'GB', 'NE1 1AA', 'AU', '2620', '72 629 951 766', 'australia_import_b2b', 0], + //Valid Business No, but no GST registration + [1, 10, 'GB', 'NE1 1AA', 'AU', '2620', '50 110 219 460', 'australia_import_taxed', 0], + [1, 10, 'GB', 'NE1 1AA', 'AU', '2620', '1234', 'australia_import_taxed', 0], //Invalid Business No + [1, 10, 'GB', 'NE1 1AA', 'AU', '2620', '', 'australia_import_taxed', 0], + [1, 10, 'GB', 'NE1 1AA', 'AU', '2620', '', 'australia_import_taxed', 0], + [56, 10, 'GB', 'NE1 1AA', 'AU', '2620', '', 'australia_import_taxed', 0], + [5, 100, 'GB', 'NE1 1AA', 'AU', '2620', '', 'australia_import_taxed', 0], + [1, 560, 'GB', 'NE1 1AA', 'AU', '2620', '', 'australia_import_taxed', 0], + [2, 570, 'GB', 'NE1 1AA', 'AU', '2620', '', 'australia_import_untaxed', 50], + [1, 570, 'GB', 'NE1 1AA', 'AU', '2620', '', 'australia_import_untaxed', 0], + [2, 570, 'GB', 'NE1 1AA', 'AU', '2620', '', 'australia_import_untaxed', 50], + [9, 100, 'GB', 'NE1 1AA', 'AU', '2620', '', 'australia_import_untaxed', 0], + [5, 4000, 'GB', 'NE1 1AA', 'AU', '2620', '1234', 'australia_import_untaxed', 0], //Invalid Business No + ]; + } +} diff --git a/Test/Integration/CreateOrderTest.php b/Test/Integration/CreateOrderTest.php new file mode 100644 index 0000000..56a0177 --- /dev/null +++ b/Test/Integration/CreateOrderTest.php @@ -0,0 +1,364 @@ +objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productFactory = $this->objectManager->get(ProductFactory::class); + $this->addressFactory = $this->objectManager->get(AddressFactory::class); + $this->guestCartManagement = $this->objectManager->get(GuestCartManagementInterface::class); + $this->guestCartRepository = $this->objectManager->get(GuestCartRepositoryInterface::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $this->orderRepository = $this->objectManager->get(OrderRepository::class); + $this->groupFactory = $this->objectManager->get(GroupInterfaceFactory::class); + $this->groupRepository = $this->objectManager->get(GroupRepositoryInterface::class); + $this->config = $this->objectManager->get(ReinitableConfigInterface::class); + $this->orderTaxSchemeCollectionFactory = $this->objectManager->get(CollectionFactory::class); + } + + /** + * @param $algorithm + * @param $grandtotal + * @param $totalTaxStore + * @param $totalTaxBase + * @param $totalTaxSchemeUK + * @param $totalTaxSchemeEU + * @param $taxinprice + * @return void + * @throws CouldNotSaveException + * @throws InputException + * @throws LocalizedException + * @throws NoSuchEntityException + * @throws InvalidTransitionException + * @magentoConfigFixture current_store autocustomergroup/general/enabled 1 + * @magentoConfigFixture current_store autocustomergroup/general/enable_sales_order_tax_scheme_table 1 + * @magentoConfigFixture current_store tax/classes/shipping_tax_class 2 + * + * @magentoConfigFixture current_store autocustomergroup/ukvat/enabled 1 + * @magentoConfigFixture current_store autocustomergroup/ukvat/registrationnumber GB553557881 + * @magentoConfigFixture current_store autocustomergroup/ukvat/environment sandbox + * @magentoConfigFixture current_store autocustomergroup/ukvat/usemagentoexchangerate 0 + * @magentoConfigFixture current_store autocustomergroup/ukvat/exchangerate 0.5 + * @magentoConfigFixture current_store autocustomergroup/ukvat/importthreshold 10000 + * + * @magentoConfigFixture current_store autocustomergroup/euvat/enabled 1 + * @magentoConfigFixture current_store autocustomergroup/euvat/viesregistrationnumber 100 + * @magentoConfigFixture current_store autocustomergroup/euvat/registrationnumber 100 + * @magentoConfigFixture current_store autocustomergroup/ukvat/environment sandbox + * @magentoConfigFixture current_store autocustomergroup/euvat/usemagentoexchangerate 0 + * @magentoConfigFixture current_store autocustomergroup/euvat/exchangerate 0.75 + * @magentoConfigFixture current_store autocustomergroup/euvat/importthreshold 20000 + * + * @magentoConfigFixture current_store general/store_information/country_id US + * @magentoConfigFixture current_store general/store_information/postcode 12345 + * @dataProvider dataProviderForTest + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testCreateOrder( + $algorithm, + $grandtotal, + $totalTaxStore, + $totalTaxBase, + $totalTaxSchemeUK, + $totalTaxSchemeEU, + $taxinprice + ): void { + $storeId = $this->storeManager->getStore()->getId(); + $this->config->setValue('tax/calculation/price_includes_tax', $taxinprice, ScopeInterface::SCOPE_STORE); + $this->config->setValue('tax/calculation/shipping_includes_tax', $taxinprice, ScopeInterface::SCOPE_STORE); + $this->config->setValue('tax/calculation/algorithm', $algorithm, ScopeInterface::SCOPE_STORE); + $product1 = $this->productFactory->create(); + $product1->setTypeId('simple') + ->setId(1) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Product 1') + ->setSku('simple1') + ->setPrice(123.52) + ->setData('tax_class_id', 2) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 0]) + ->setUrlKey('simple1') + ->save(); + $product2 = $this->productFactory->create(); + $product2->setTypeId('simple') + ->setId(2) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Product 2') + ->setSku('simple2') + ->setPrice(145.97) + ->setData('tax_class_id', 2) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 0]) + ->setUrlKey('simple2') + ->save(); + $addressData = [ + 'telephone' => 12345, + 'postcode' => 'SW1 1AA', + 'country_id' => 'GB', + 'city' => 'City', + 'street' => ['Street'], + 'lastname' => 'Lastname', + 'firstname' => 'Firstname', + 'address_type' => 'shipping', + 'email' => 'some_email@mail.com' + ]; + + $groupDataObject = $this->groupFactory->create(); + $groupDataObject->setCode('uk_domestic')->setTaxClassId(3); + $groupId = $this->groupRepository->save($groupDataObject)->getId(); + $this->config->setValue('autocustomergroup/ukvat/uk_import_taxed', $groupId, ScopeInterface::SCOPE_STORE); + + $taxRate1 = [ + 'tax_country_id' => 'GB', + 'tax_region_id' => '0', + 'tax_postcode' => '*', + 'code' => 'UK VAT', + 'rate' => '20.0000' + ]; + $rate1 = $this->objectManager->create(Rate::class)->setData($taxRate1)->save(); + + $taxRate2 = [ + 'tax_country_id' => 'GB', + 'tax_region_id' => '0', + 'tax_postcode' => '*', + 'code' => 'UK Reduced Rate', + 'rate' => '5.0000' + ]; + $rate2 = $this->objectManager->create(Rate::class)->setData($taxRate2)->save(); + + $ruleData1 = [ + 'code' => 'UK VAT Rule', + 'priority' => '0', + 'position' => '0', + 'customer_tax_class_ids' => [3], + 'product_tax_class_ids' => [2], + 'tax_rate_ids' => [$rate1->getId()], + 'tax_rates_codes' => [$rate1->getId() => $rate1->getCode()], + 'tax_scheme_id' => 'ukvat' + ]; + $this->objectManager->create(Rule::class)->setData($ruleData1)->save(); + + $ruleData2 = [ + 'code' => 'EU Reduced VAT Rule', + 'priority' => '0', + 'position' => '0', + 'customer_tax_class_ids' => [3], + 'product_tax_class_ids' => [2], + 'tax_rate_ids' => [$rate2->getId()], + 'tax_rates_codes' => [$rate2->getId() => $rate2->getCode()], + 'tax_scheme_id' => 'euvat' + ]; + $this->objectManager->create(Rule::class)->setData($ruleData2)->save(); + + $shippingAddress = $this->addressFactory->create(['data' => $addressData]); + $shippingAddress->setAddressType('shipping'); + $billingAddress = $this->addressFactory->create(['data' => $addressData]); + $billingAddress->setAddressType('billing'); + + $maskedCartId = $this->guestCartManagement->createEmptyCart(); + /** @var Quote $quote */ + $quote = $this->guestCartRepository->get($maskedCartId); + + //$quote = $this->quoteFactory->create(); + $quote->setCustomerIsGuest(true) + ->setStoreId($storeId) + ->setReservedOrderId('guest_quote'); + + $quote->addProduct($this->productRepository->get('simple1'), 3); + $quote->addProduct($this->productRepository->get('simple2'), 3); + $quote->setBillingAddress($billingAddress); + $quote->setShippingAddress($shippingAddress); + $quote->getPayment()->setMethod('checkmo'); + $quote->getShippingAddress()->setShippingMethod('flatrate_flatrate')->setCollectShippingRates(true); + $quote->collectTotals(); + + $this->quoteRepository->save($quote); + + $checkoutSession = $this->objectManager->get(CheckoutSession::class); + $checkoutSession->setQuoteId($quote->getId()); + + $orderId = $this->guestCartManagement->placeOrder($maskedCartId); + $order = $this->orderRepository->get($orderId); + $this->assertNotNull($order->getEntityId()); + + $this->assertEquals($grandtotal, $order->getGrandTotal()); + $this->assertEquals($totalTaxStore, $order->getTaxAmount()); + $this->assertEquals($totalTaxBase, $order->getBaseTaxAmount()); + + $orderTaxSchemes = $this->orderTaxSchemeCollectionFactory->create()->loadByOrder($order); + /** @var OrderTaxSchemeInterface $orderTaxScheme */ + $orderTaxScheme = $orderTaxSchemes->getItemByColumnValue('name', "UK VAT Scheme"); + $this->assertNotNull($orderTaxScheme); + $this->assertEquals( + $totalTaxSchemeUK, + round($order->getBaseTaxAmount() / $orderTaxScheme->getExchangeRateSchemeToBase(), 2) + ); + $this->assertEquals($order->getEntityId(), $orderTaxScheme->getOrderId()); + $this->assertEquals("GB553557881", $orderTaxScheme->getReference()); + $this->assertEquals("UK VAT Scheme", $orderTaxScheme->getName()); + $this->assertEquals("USD", $orderTaxScheme->getStoreCurrency()); + $this->assertEquals("USD", $orderTaxScheme->getBaseCurrency()); + $this->assertEquals("GBP", $orderTaxScheme->getSchemeCurrencyCode()); + $this->assertEquals(1.0, $orderTaxScheme->getExchangeRateBaseToStore()); + $this->assertEquals(0.5, $orderTaxScheme->getExchangeRateSchemeToBase()); + $this->assertEquals(5000.0, $orderTaxScheme->getImportThresholdStore()); + $this->assertEquals(5000.0, $orderTaxScheme->getImportThresholdBase()); + $this->assertEquals(10000.0, $orderTaxScheme->getImportThresholdScheme()); + + /** @var OrderTaxSchemeInterface $orderTaxScheme */ + $orderTaxScheme = $orderTaxSchemes->getItemByColumnValue('name', "EU VAT OSS/IOSS Scheme"); + $this->assertNotNull($orderTaxScheme); + $this->assertEquals( + $totalTaxSchemeEU, + round($order->getBaseTaxAmount() / $orderTaxScheme->getExchangeRateSchemeToBase(), 2) + ); + $this->assertEquals($order->getEntityId(), $orderTaxScheme->getOrderId()); + $this->assertEquals("100", $orderTaxScheme->getReference()); + $this->assertEquals("EU VAT OSS/IOSS Scheme", $orderTaxScheme->getName()); + $this->assertEquals("USD", $orderTaxScheme->getStoreCurrency()); + $this->assertEquals("USD", $orderTaxScheme->getBaseCurrency()); + $this->assertEquals("EUR", $orderTaxScheme->getSchemeCurrencyCode()); + $this->assertEquals(1.0, $orderTaxScheme->getExchangeRateBaseToStore()); + $this->assertEquals(0.75, $orderTaxScheme->getExchangeRateSchemeToBase()); + $this->assertEquals(15000.0, $orderTaxScheme->getImportThresholdStore()); + $this->assertEquals(15000.0, $orderTaxScheme->getImportThresholdBase()); + $this->assertEquals(20000.0, $orderTaxScheme->getImportThresholdScheme()); + } + + /** + * @return array + */ + public function dataProviderForTest(): array + { + //Tax Calc Method + //Grand Total + //Total Tax Store + //Total Tax Base + //Total Tax Scheme UK + //Total Tax Scheme EU + //Tax In Price + return [ + ['UNIT_BASE_CALCULATION', 1048.08, 209.61, 209.61, 419.22, 279.48, 0], + ['ROW_BASE_CALCULATION', 1048.09, 209.62, 209.62, 419.24, 279.49, 0], + ['TOTAL_BASE_CALCULATION', 1048.09, 209.62, 209.62, 419.24, 279.49, 0], + ['UNIT_BASE_CALCULATION', 1048.08, 209.61, 209.61, 419.22, 279.48, 1], + ['ROW_BASE_CALCULATION', 1048.08, 209.62, 209.62, 419.24, 279.49, 1], + ['TOTAL_BASE_CALCULATION', 1048.08, 209.62, 209.62, 419.24, 279.49, 1] + ]; + } +} diff --git a/Test/Integration/CustomerAccountTest.php b/Test/Integration/CustomerAccountTest.php new file mode 100644 index 0000000..6d3938d --- /dev/null +++ b/Test/Integration/CustomerAccountTest.php @@ -0,0 +1,281 @@ +objectManager = Bootstrap::getObjectManager(); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->groupFactory = $this->objectManager->get(GroupInterfaceFactory::class); + $this->groupRepository = $this->objectManager->get(GroupRepositoryInterface::class); + $this->config = $this->objectManager->get(ReinitableConfigInterface::class); + $this->customerRepository = $this->objectManager->create(CustomerRepositoryInterface::class); + $this->caRepository = $this->objectManager->get(AddressRepositoryInterface::class); + + $storeId = $this->storeManager->getStore()->getId(); + + $this->createGroup("taxclass1", "group1", "autocustomergroup/ukvat/domestic"); + $this->createGroup("taxclass2", "group2", "autocustomergroup/ukvat/intraeub2b"); + $this->createGroup("taxclass3", "group3", "autocustomergroup/ukvat/intraeub2c"); + $this->createGroup("taxclass4", "group4", "autocustomergroup/ukvat/importb2b"); + $this->createGroup("taxclass5", "group5", "autocustomergroup/ukvat/importtaxed"); + $this->createGroup("taxclass6", "group6", "autocustomergroup/ukvat/importuntaxed"); + + /** @var CustomerInterface $customer */ + $this->customer = $this->objectManager->create(CustomerInterfaceFactory::class)->create(); + $this->customer->setWebsiteId(1) + ->setEmail('test@test.com') + ->setGroupId(0) + ->setStoreId($storeId) + ->setFirstname('First') + ->setLastname('Last'); + $this->customer = $this->customerRepository->save($this->customer); + + /** @var ProductInterface $product */ + $this->product = $this->objectManager->create(ProductInterfaceFactory::class)->create(); + $this->product->setWebsiteIds([$this->storeManager->getStore($storeId)->getWebsiteId()]) + ->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setName("Simple") + ->setSku('simple') + ->setPrice(1) + ->setTaxClassId(2) + ->setStoreId($storeId) + ->setStockData(['use_config_manage_stock' => 0]) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->save(); + } + + public function createGroup($classname, $groupname, $configpath) + { + $customerTaxClass = $this->objectManager->create(ClassModel::class) + ->setClassName($classname) + ->setClassType(ClassModel::TAX_CLASS_TYPE_CUSTOMER) + ->save(); + $group = $this->groupFactory->create(); + $group->setCode($groupname) + ->setTaxClassId($customerTaxClass->getClassId()); + $group = $this->groupRepository->save($group); + $this->config->setValue($configpath, $group->getId(), ScopeInterface::SCOPE_STORE); + } + + /** + * @param $startingGroup + * @param $qty + * @param $shopCountry + * @param $buyerCountry + * @param $postCode + * @param $taxid + * @param $expectedGroup + * @return void + * @throws InputException + * @throws InputMismatchException + * @throws LocalizedException + * @throws NoSuchEntityException + * @magentoConfigFixture current_store autocustomergroup/general/enabled 1 + * @magentoConfigFixture current_store autocustomergroup/general/enable_sales_order_tax_scheme_table 0 + * @magentoConfigFixture current_store tax/classes/shipping_tax_class 2 + * @magentoConfigFixture current_store autocustomergroup/ukvat/enabled 1 + * @magentoConfigFixture current_store autocustomergroup/ukvat/registrationnumber GB553557881 + * @magentoConfigFixture current_store autocustomergroup/ukvat/environment sandbox + * @magentoConfigFixture current_store autocustomergroup/ukvat/usemagentoexchangerate 0 + * @magentoConfigFixture current_store autocustomergroup/ukvat/exchangerate 1 + * @magentoConfigFixture current_store autocustomergroup/ukvat/importthreshold 135 + * @magentoConfigFixture current_store general/store_information/postcode 12345 + * @dataProvider dataProviderForTest + */ + public function testCreateOrder( + $startingGroup, + $qty, + $shopCountry, + $buyerCountry, + $postCode, + $taxid, + $expectedGroup + ): void { + $storeId = $this->storeManager->getStore()->getId(); + $this->config->setValue('general/store_information/country_id', $shopCountry, ScopeInterface::SCOPE_STORE); + + /** @var AddressInterface $customerAddress */ + $customerAddress = $this->objectManager->create(AddressInterfaceFactory::class)->create(); + $customerAddress->setTelephone("12345") + ->setPostcode($postCode) + ->setCountryId($buyerCountry) + ->setCity("City") + ->setStreet(["Street1"]) + ->setLastname("Last") + ->setFirstname("First") + ->setCustomerId($this->customer->getId()) + ->setIsDefaultBilling(true) + ->setIsDefaultShipping(true) + ->setVatId($taxid); + + $customerAddress = $this->caRepository->save($customerAddress); + if (is_string($startingGroup)) { + $startingGroup = $this->config->getValue( + "autocustomergroup/ukvat/" . $startingGroup, + ScopeInterface::SCOPE_STORE + ); + } + $this->customer->setGroupId($startingGroup); + $this->customer->setAddresses([$customerAddress]); + $this->customer->setDefaultBilling($customerAddress->getId()); + $this->customer->setDefaultShipping($customerAddress->getId()); + $this->customer = $this->customerRepository->save($this->customer); + + /** @var $quote Quote */ + $quote = $this->objectManager->create(CartInterfaceFactory::class)->create(); + $quote->setCustomer($this->customer); + $quote->setStoreId($storeId); + /** @var $quoteItem Item */ + $quoteItem = $quote->addProduct($this->product); + $quoteItem->setQty($qty); + $quote->getPayment()->setMethod('checkmo'); + $quoteAddress = $this->objectManager->get(QuoteAddressInterfaceFactory::class)->create(); + $quoteAddress->importCustomerAddressData( + $this->caRepository->getById($this->customer->getDefaultBilling()) + ); + $quote->setShippingAddress($quoteAddress); + $quote->setBillingAddress($quoteAddress); + $quote->getShippingAddress()->setCollectShippingRates(true); + $quote->collectTotals(); + $quoteItem->setQuote($quote); + $quote->save(); + $quoteItem->save(); + $extAttr = $quote->getExtensionAttributes(); + $groupToSet = $extAttr->getAutocustomergroupNewId(); + if (is_string($expectedGroup)) { + $expectedGroup = $this->config->getValue( + "autocustomergroup/ukvat/" . $expectedGroup, + ScopeInterface::SCOPE_STORE + ); + } + $this->assertEquals( + $expectedGroup, + $groupToSet + ); + } + + /** + * @return array + */ + public function dataProviderForTest(): array + { + //Starting Group + //Qty to buy + //ShopCountry + //BuyerCountry + //Postcode + //VatID + //ExpectedGroup + return [ + ["domestic", 140, null, "GB", null, null, "domestic"], + ["domestic", 130, "US", "GB", "SW1 1AA", "", "importtaxed"], + ["domestic", 140, "US", "GB", "SW1 1AA", "", "importuntaxed"], + ["domestic", 130, "GB", "GB", "SW1 1AA", "", "domestic"], + ["domestic", 140, "GB", "GB", "SW1 1AA", "", "domestic"], + [0, 140, "GB", "GB", "SW1 1AA", "", "domestic"], + [0, 140, "FR", "FR", "7000", "", 0], + [0, 130, "GB", "GB", "SW1 1AA", "", "domestic"], + [0, 130, "FR", "FR", "7000", "", 0], + ["domestic", 140, "FR", "FR", "7000", "", "domestic"], + ["importuntaxed", 140, "FR", "FR", "7000", "", "importuntaxed"], + [0, 140, "FR", "GB", "BT1 1AA", "", "intraeub2c"], + [0, 140, "FR", "GB", "BT1 1AA", "GB146295999727", "intraeub2b"], + [0, 130, "FR", "GB", "BT1 1AA", "", "intraeub2c"], + [0, 130, "FR", "GB", "BT1 1AA", "GB146295999727", "intraeub2b"], + [0, 140, "FR", "GB", "", "GB146295999727", "importb2b"], + ["domestic", 140, "GB", "GB", null, "GB146295999727", "domestic"], + ["domestic", 140, "GB", "GB", null, null, "domestic"], + ]; + } +} diff --git a/Ui/Component/Listing/Column/OrderTaxCollected.php b/Ui/Component/Listing/Column/OrderTaxCollected.php new file mode 100644 index 0000000..4fdbb9c --- /dev/null +++ b/Ui/Component/Listing/Column/OrderTaxCollected.php @@ -0,0 +1,56 @@ +taxCollectors = $taxCollectors; + parent::__construct($context, $uiComponentFactory, $components, $data); + } + + /** + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource): array + { + if (isset($dataSource['data']['items'])) { + foreach ($dataSource['data']['items'] as & $item) { + $item['tax_collected'] = "No"; + foreach ($this->taxCollectors as $taxCollector) { + foreach ($taxCollector->getTaxCollectedDetails($item['entity_id']) as $detail) { + $item['tax_collected'] = "
    " . implode(" - ", array_filter($detail)) . '
    '; + } + } + } + } + return $dataSource; + } +} diff --git a/composer.json b/composer.json index a629751..f14830b 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "gwharton/module-autocustomergroup", + "license": "OSL-3.0", "type": "magento2-module", "require": { "magento/framework": "*" diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml new file mode 100644 index 0000000..6b413e8 --- /dev/null +++ b/etc/adminhtml/di.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/etc/adminhtml/routes.xml b/etc/adminhtml/routes.xml new file mode 100644 index 0000000..fa8b85f --- /dev/null +++ b/etc/adminhtml/routes.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml new file mode 100644 index 0000000..f94449d --- /dev/null +++ b/etc/adminhtml/system.xml @@ -0,0 +1,68 @@ + + + +
    + separator-top + + customer + Magento_Customer::config_customer + + + +

    LEGAL DISCLAIMER - The author of this module accepts no responsibility for any errors caused by this module. It is up to you + as the store owner/manager/exporter to ensure you are compliant with the laws in place in the countries that you ship to, and it is your + responsibility to ensure that your store is implementing those rules and laws correctly. The author does not make any guarantees as to whether + this module implements the rules and laws correctly. That is for you to determine. The statements given in the descriptions below and the + rules that this module implement are not TAX advice. Please read and understand the scheme documentation yourself.

    + + ]]>
    + + + Magento\Config\Model\Config\Source\Yesno + + + + Magento\Config\Model\Config\Source\Yesno + + 1 + + + + + + 1 + + + + + Magento\Config\Model\Config\Source\Yesno + To enable currency conversion of Import Thresholds using Magento Currency Conversion subsystem, the scheme currencies need to be + configured as additional base currencies within Magento. Currency download will need to be triggered after enabling this setting. You don't need to + enable this is you have already setup the currencies required and have the addon modules configured to use + magento exchange rates. + + + 1 + + + + + Magento\Config\Model\Config\Source\Yesno + + 1 + + + + + Gw\AutoCustomerGroup\Model\Config\Source\Group + If the order is a guest order, which group should be used if none of the schemes generate a group change. (Existing + customers will be assigned their correct customer group prior to being changed by this module) + + 1 + + +
    +
    +
    +
    diff --git a/etc/config.xml b/etc/config.xml new file mode 100644 index 0000000..6a0580a --- /dev/null +++ b/etc/config.xml @@ -0,0 +1,15 @@ + + + + + + 0 + 0 + 1 + Tax Identifier + 0 + 0 + + + + diff --git a/etc/db_schema.xml b/etc/db_schema.xml new file mode 100644 index 0000000..e7290cc --- /dev/null +++ b/etc/db_schema.xml @@ -0,0 +1,39 @@ + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    diff --git a/etc/db_schema_whitelist.json b/etc/db_schema_whitelist.json new file mode 100644 index 0000000..f60b896 --- /dev/null +++ b/etc/db_schema_whitelist.json @@ -0,0 +1,27 @@ +{ + "tax_calculation_rule": { + "column": { + "tax_scheme_id": true + } + }, + "sales_order_tax_scheme": { + "column": { + "order_tax_scheme_id": true, + "order_id": true, + "reference": true, + "name": true, + "store_currency": true, + "base_currency": true, + "scheme_currency": true, + "exchange_rate_base_to_store": true, + "exchange_rate_scheme_to_base": true, + "import_threshold_store": true, + "import_threshold_base": true, + "import_threshold_scheme": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_TAX_SCHEME_ORDER_ID_SALES_ORDER_ENTITY_ID": true + } + } +} \ No newline at end of file diff --git a/etc/di.xml b/etc/di.xml new file mode 100644 index 0000000..eb78a6d --- /dev/null +++ b/etc/di.xml @@ -0,0 +1,59 @@ + + + + + + Gw\AutoCustomerGroup\Model\OrderTaxCollected + + + + + + + Magento\Tax\Model\Sales\Total\Quote\Subtotal + Magento\Tax\Model\Sales\Total\Quote\Shipping + Magento\Tax\Model\Sales\Total\Quote\Tax + Magento\Weee\Model\Total\Quote\WeeeTax + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/etc/events.xml b/etc/events.xml new file mode 100644 index 0000000..e64f8c7 --- /dev/null +++ b/etc/events.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/etc/extension_attributes.xml b/etc/extension_attributes.xml new file mode 100644 index 0000000..72c3de8 --- /dev/null +++ b/etc/extension_attributes.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/etc/fieldset.xml b/etc/fieldset.xml new file mode 100644 index 0000000..98c92ed --- /dev/null +++ b/etc/fieldset.xml @@ -0,0 +1,26 @@ + + + +
    + + + + + + + + + + + + + + + + + + +
    +
    +
    diff --git a/etc/frontend/routes.xml b/etc/frontend/routes.xml new file mode 100644 index 0000000..b1d1e18 --- /dev/null +++ b/etc/frontend/routes.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/etc/module.xml b/etc/module.xml index 57307c9..151b93d 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,5 +1,9 @@ - + + + + + diff --git a/etc/sales.xml b/etc/sales.xml new file mode 100644 index 0000000..5d89ac5 --- /dev/null +++ b/etc/sales.xml @@ -0,0 +1,8 @@ + + +
    + + + +
    +
    diff --git a/images/frontend1.png b/images/frontend1.png new file mode 100644 index 0000000000000000000000000000000000000000..67fe2ff979a7c35766a97dabbe75c79167b302ca GIT binary patch literal 17182 zcmd742UJsC+a`<^5dj4eL5d^uT}cdB1;V*0*NnednE-HEY4j%3kN}bN1f%eckQ46Y^eBn&dXkZ2|%U5?Psd zDg*>qIS2@@T->?|j3l7b!-0=0jw;e`2ucTO(ZHAMX0H`q6A*lhB0e*`0emO=D5K>_ zKtS4l`G2L$F5j4d;7z6MyVq*2(5+d#W4v<)#^?NYl!TT*UME9tOJ%hYDve#WRDC)@ z)oSH^^}AwhPHlb;$5M1mjvgy(*huwYN>6vtYd-UMzN_wOEI%LkD}1;E8s(yWvlzh3 zbL*z$qZo@Papo>0RiM`F|dZBdk_MGPvf_Mm4CPVCoWf~hjhl0 zH)Dgzp>%_gv!nPswpKA?0U&vyTq)yaK8&MpOv$*zBn4aYUISfw3Hh8(&-GGY;zm>@ zXFHS-c!N7wVvdbChw5*JE9PNU33c01))8P&rJ^eO^xc#E4I0QfQylAHPi7Zc_iE!% zPtI->s!zfbApeOl@Bu$O2G|A0!nW_uaZ{{^;_?Gix4c~mQ+L+rNuLdKtM|FHAnRc5w$xBAh zix@{7KE78`5)dAR)=TBZ*$2m=RmqWee;$u6&|CK4xGi)$^{wIkSr@9}&4|~$Lp8mD z%9lGEwlEz4FZ}C=Kz5y!@v2zKiT;quVP;74PmZc$wR$AF>VpzV=x7VMwbpLu!pnpW zJMT%SUse&x+i|(<>8uietzj_JrYmpW4pZ=;aaSYd55P;Ee$tAP5q*J~_*BpHHVN>G znBV1tOuP+I8RhRlolGlZjw5+mPq>SV6~N`+7)L4McAl2R(hhdZGaE|g=-b4vMp_XN z1;7iIh(g5f$E@8H`8--$BIT+a>6)B+0a!90bqE=ilZy-(lu1eum|>b@ft9k$;xF z1~>nVGBuvryWJk@cIW=YE+KTDL>vN0CqyftJT~FS+Z9ReTAJFZI!v(qf$T;KeA@0H zQ7WM)1ON}Yd|EIM_>mFo!8vvW4 zlg7Sfk=n-0w^94uZkW@GwC%)mSqvWmL7k!Ic*O{^NBr0W;>5d?-6sZ7`#q_udOjUxc2otS6ds<{JJYsk03WwP$GvxIoqwmwU9~h0QM~- z)u~C}9!-HquC(~W!yxs`Roco!g5fh6T=-Y3BgyeqlKfSh_BvXZE1yl;B;i=$8J-&$ z@%g!N;I)OfO0!6u?ot`-`odad(pQeX2VC?DxTv?u;E@Y7ML?ZHWPb9F5i}PEEhP~f zew?1iU4JcVJ9>Jih`B0_%K8dHU5q9r@OlKd|3!HE`|?sZP3lLF99iA~XQq5#Ad^?? zD>>yKVd(fbgyCO&mA{cvFEgH~%n|wzaLl-8(TwG?5PperZj`E#VisC)*5#&h=`+&_ zY9<9FNwZW=Gg)Ywu|%@$lm;@<#av&lev;b^T|WteOY|&iR=$7Gs!M?P%_BO$K|;32pRl;+@VqPr_5YYRM<>7w zA^0+C7M7B(*}B-t3KeT%OJgfIKi$FNfKw*FI@<2+G4Cup%!bb>J$CFLK)8NpBX5DM z5C2a%XaOvDY&YNseW;AnTlo)G8tzX%NJ-U5Gga_#d6_xHd`>H8OdO53u90(98=;_?sl*8&Be9ox0% z|2AR<=1M;fgp2qjd00R=yl1^;DWH@IGJ0`L^O15zME6v^Mq=qoq1x^v+j5n0ULy^r zIR{qBct0Dcrg9ux$Q+t5hI~{R^&ilP`44CW|Gx)~QxuREaqfZvXYvNnvbyGU5VxB? z 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 JmVGer{a;qRmqY*n literal 0 HcmV?d00001 diff --git a/images/frontend3.png b/images/frontend3.png new file mode 100644 index 0000000000000000000000000000000000000000..f2480838d319a8f3cec759da0528cc1f4c681b48 GIT binary patch literal 7228 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 IBfGc%4=f2B?EnA( literal 0 HcmV?d00001 diff --git a/images/frontend4.png b/images/frontend4.png new file mode 100644 index 0000000000000000000000000000000000000000..878ff7c5df90d740eaefafc79b8a6941f6b094a0 GIT binary patch literal 9969 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 JLh60+{{mvj<}CmK literal 0 HcmV?d00001 diff --git a/images/general.png b/images/general.png new file mode 100644 index 0000000000000000000000000000000000000000..43322f796f33021e6f8715f5581388e15e7553ec GIT binary patch literal 67333 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

    M3Wed6c@^?Cb|u7>BPR#vH@ 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

      2AhUxEvRUI57c_D>8kYafsQ;YKt`{j>B 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 iI8UrmTD5nZ{d-!(WK#_|Sm4 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 literal 0 HcmV?d00001 diff --git a/images/menu.png b/images/menu.png new file mode 100644 index 0000000000000000000000000000000000000000..0051afb0229f92c30e83aae2abfd79779480b7bb GIT binary patch literal 6103 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 NfgTuZS8F|u`Y&Dj@GAfS literal 0 HcmV?d00001 diff --git a/images/sogcolumn.png b/images/sogcolumn.png new file mode 100644 index 0000000000000000000000000000000000000000..d146c220fd8156aadbf7778c98adc6335c8c11ed GIT binary patch literal 2909 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 NE2a3nM8fdH{|9bG{JH=D literal 0 HcmV?d00001 diff --git a/images/taxrules2.png b/images/taxrules2.png new file mode 100644 index 0000000000000000000000000000000000000000..f6aea8b7d41f15c5f98ceb2ea6f751a124e6842b GIT binary patch literal 25746 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 ZQZuaM658>aXh78_H|HY + + + + + + + diff --git a/view/adminhtml/layout/tax_rule_block.xml b/view/adminhtml/layout/tax_rule_block.xml new file mode 100644 index 0000000..8eb25a6 --- /dev/null +++ b/view/adminhtml/layout/tax_rule_block.xml @@ -0,0 +1,20 @@ + + + + + + + + + Tax Scheme ID + 0 + tax_scheme_id + options + + + + + + + + diff --git a/view/adminhtml/templates/taxidvalidation.phtml b/view/adminhtml/templates/taxidvalidation.phtml new file mode 100644 index 0000000..38d5eaa --- /dev/null +++ b/view/adminhtml/templates/taxidvalidation.phtml @@ -0,0 +1,10 @@ + +renderTag('script', ['type' => 'text/javascript'], $script, false); ?> diff --git a/view/adminhtml/ui_component/sales_order_grid.xml b/view/adminhtml/ui_component/sales_order_grid.xml new file mode 100644 index 0000000..9e528a7 --- /dev/null +++ b/view/adminhtml/ui_component/sales_order_grid.xml @@ -0,0 +1,15 @@ + ++ + + + true + + text + ui/grid/cells/html + + + + + diff --git a/view/adminhtml/web/css/source/_module.less b/view/adminhtml/web/css/source/_module.less new file mode 100644 index 0000000..841737c --- /dev/null +++ b/view/adminhtml/web/css/source/_module.less @@ -0,0 +1,15 @@ + +.autocustomergroup-schemeoverview { + ul { + padding-left: 20px; + } +} +.thresholdsummary-wrapper { + div { + margin-bottom: 10px; + .lib-font-size(@font-size__xl); + } +} +.admin__field-note-vat_id-note { + margin-top: 1rem; +} diff --git a/view/adminhtml/web/js/taxidvalidation.js b/view/adminhtml/web/js/taxidvalidation.js new file mode 100644 index 0000000..8321539 --- /dev/null +++ b/view/adminhtml/web/js/taxidvalidation.js @@ -0,0 +1,58 @@ +define([ + 'jquery', + 'Magento_Sales/order/create/scripts' +], function (jQuery) { + 'use strict'; + AdminOrder.prototype.validateTaxId = function(parameters){ + var params = { + country: $(parameters.countryElementId).value, + postcode: $(parameters.postcodeElementId).value, + tax: $(parameters.taxIdElementId).value + }; + + if (this.storeId !== false) { + params.store_id = this.storeId; + } + + new Ajax.Request(parameters.validateUrl, { + parameters: params, + onSuccess: function (response) { + response = response.responseText.evalJSON(); + let groupActionRequired = 'inform'; + let message = ''; + let currentGroupId = Number($(parameters.groupIdHtmlId).value); + let currentGroupName = $$('#' + parameters.groupIdHtmlId + ' > option[value=' + currentGroupId + ']')[0].text; + let newGroupId = response.group ?? currentGroupId; + let newGroupName = $$('#' + parameters.groupIdHtmlId + ' > option[value=' + newGroupId + ']')[0].text; + if (response.success) { + if (response.valid) { + message = 'Tax Identifier is VALID (' + response.message + ').'; + } else { + message = 'Tax Identifier is INVALID (' + response.message + ').'; + } + } else { + message = 'AutoCustomerGroup encountered an error while checking the Tax Identifier. (' + response.message + ').'; + } + if (response.group && response.group !== currentGroupId) { + message += '\n\nAutoCustomerGroup recommends the group changes from ' + currentGroupName + ' to ' + newGroupName + '.'; + groupActionRequired = 'change'; + } else { + message += '\n\nAutoCustomerGroup does not recommend a group change.'; + } + if (groupActionRequired === "inform") { + alert(message); + } + if (groupActionRequired === "change") { + if (confirm(message + '\n\nProceed with group change?')) { + $$('#' + parameters.groupIdHtmlId + ' option').each(function (o) { + o.selected = Number(o.readAttribute('value')) === newGroupId; + }); + this.saveData(this.serializeData('order-addresses')); + this.loadArea(['data'], true, this.serializeData('order-form_account').toObject()); + this.loadArea(['data'], true, this.serializeData('order-addresses').toObject()); + } + } + }.bind(this) + }); + } +}); diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml new file mode 100644 index 0000000..5851523 --- /dev/null +++ b/view/frontend/layout/checkout_index_index.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/view/frontend/web/css/source/_module.less b/view/frontend/web/css/source/_module.less new file mode 100644 index 0000000..4a0bb69 --- /dev/null +++ b/view/frontend/web/css/source/_module.less @@ -0,0 +1,20 @@ + +.opc { + .autocustomergroup_tax_id { + .prompt { + .lib-font-size(@font-size__xs); + margin-top: @indent__xs; + margin-bottom: @indent__xs; + color: @color-gray60; + font-style: italic; + } + .message { + &.success { + margin-top: @indent__s; + } + } + .field-note { + margin-top: 0; + } + } +} diff --git a/view/frontend/web/js/tax-id-element.js b/view/frontend/web/js/tax-id-element.js new file mode 100644 index 0000000..9c92541 --- /dev/null +++ b/view/frontend/web/js/tax-id-element.js @@ -0,0 +1,277 @@ +define([ + 'jquery', + 'Magento_Ui/js/form/element/abstract', + 'ko', + 'mage/url', + 'uiRegistry', + 'Magento_Checkout/js/action/set-shipping-information', + 'Magento_Checkout/js/model/quote', + 'mage/translate' +], function ( + $, + Abstract, + ko, + url, + registry, + setShippingInformation, + quote, + $t +) { + 'use strict'; + + return Abstract.extend({ + defaults: { + timeout: null, + delay: 0, + success: ko.observable(false), + prompt: ko.observable(false), + isChanging: false, + retry: false, + retryText: $t('Check again'), + schemes: [], + storeId: 0, + patterns: { + 'AT' : '(AT)U[0-9]{8}$', + 'BE' : '(BE)0[0-9]{9}$', + 'BG' : '(BG)[0-9]{9,10}$', + 'CY' : '(CY)[0-9]{8}[A-Z]$', + 'CZ' : '(CZ)[0-9]{8,10}$', + 'DE' : '(DE)[0-9]{9}$', + 'DK' : '(DK)[0-9]{8}$', + 'EE' : '(EE)[0-9]{9}$', + 'GR' : '(EL|GR)[0-9]{9}$', + 'ES' : '(ES)[0-9A-Z][0-9]{7}[0-9A-Z]$', + 'FI' : '(FI)[0-9]{8}$', + 'FR' : '(FR)[0-9A-Z]{2}[0-9]{9}$', + 'HR' : '(HR)[0-9]{11}$', + 'HU' : '(HU)[0-9]{8}$', + 'IE' : '(IE)[0-9][0-9A-Z][0-9]{5}[A-Z]{1,2}$', + 'IT' : '(IT)[0-9]{11}$', + 'LT' : '(LT)([0-9]{9}|[0-9]{12}$)', + 'LU' : '(LU)[0-9]{8}$', + 'LV' : '(LV)[0-9]{11}$', + 'MT' : '(MT)[0-9]{8}$', + 'NL' : '(NL)[0-9]{9}B[0-9]{2}$', + 'PL' : '(PL)[0-9]{10}$', + 'PT' : '(PT)[0-9]{9}$', + 'RO' : '(RO)[0-9]{2,10}$', + 'SE' : '(SE)[0-9]{12}$', + 'SI' : '(SI)[0-9]{8}$', + 'SK' : '(SK)[0-9]{10}$', + 'GB' : '(GB)([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3}$)', + 'IM' : '(GB)00([0-9]{7}([0-9]{3})?$)', + 'NO' : '(8|9)[0-9]{8}$', + 'NZ' : '[0-9]{8,9}$', + 'AU' : '[0-9]{11}$' + }, + prefix: { + 'AT' : 'AT', + 'BE' : 'BE', + 'BG' : 'BG', + 'CY' : 'CY', + 'CZ' : 'CZ', + 'DE' : 'DE', + 'DK' : 'DK', + 'EE' : 'EE', + 'GR' : 'EL', + 'ES' : 'ES', + 'FI' : 'FI', + 'FR' : 'FR', + 'HR' : 'HR', + 'HU' : 'HU', + 'IE' : 'IE', + 'IT' : 'IT', + 'LT' : 'LT', + 'LU' : 'LU', + 'LV' : 'LV', + 'MT' : 'MT', + 'NL' : 'NL', + 'PL' : 'PL', + 'PT' : 'PT', + 'RO' : 'RO', + 'SE' : 'SE', + 'SI' : 'SI', + 'SK' : 'SK', + 'GB' : 'GB', + 'IM' : 'GB' + } + }, + + initialize: function (options) { + this._super(); + this.initObservable(); + return this; + }, + + initObservable: function () { + this._super(); + this.observe('success'); + this.observe('prompt'); + quote.shippingAddress.subscribe(this.shippingAddressObserver.bind(this)); + + return this; + }, + + clearMessages: function () { + this.success(false); + this.warn(false); + this.bubble('success'); + this.bubble('warn'); + }, + + shippingAddressObserver: function (address) { + let schemeId = this.getSchemeIdFromCountry(address.countryId); + if(schemeId != null) { + let prompt = this.schemes[schemeId].prompt; + this.prompt(prompt); + this.bubble('prompt'); + this.onUpdate(this.value()); + } else { + this.prompt(false); + this.bubble('prompt'); + this.clearMessages(); + } + }, + + getSchemeIdFromCountry: function (countryId) { + let retval = null; + let taxSchemes = this.schemes; + + Object.keys(this.schemes).forEach( function(schemeId) { + if (taxSchemes[schemeId]['countries'].includes(countryId)) { + retval = schemeId; + } + }, this.schemes); + return retval; + }, + + onUpdate: function (value) { + if (this.timeout !== null) { + clearTimeout(this.timeout); + } + this.clearMessages(); + var self = this; + + if (value !== '' + && value.length > 3 + && !this.isChanging + ) { + this.timeout = setTimeout(function () { + self.isChanging = true; + self.validate(value); + self.isChanging = false; + }, this.delay); + } + }, + + isSupportedCountry: function (countryId) { + let supported = false; + let taxSchemes = this.schemes; + + Object.keys(this.schemes).forEach( function(schemeId) { + if (taxSchemes[schemeId]['countries'].includes(countryId)) { + supported = true; + } + }, this.schemes); + return supported; + }, + + getCountry: function () { + let country = registry.get(this.parentName + '.' + 'country_id'); + if (typeof(country) !== 'object' || country.value() === '') { + return false; + } + return country.value(); + }, + + sanitiseNumber: function (value) { + value = value.replace(/[\W_]/g, "").toUpperCase().trim(); + this.value(value); + return value; + }, + + addPrefixIfRequired: function (value, countryCode) { + if (typeof(this.prefix[countryCode]) !== 'undefined') { + if (value.substring(0,2) !== this.prefix[countryCode]) { + value = value.replace(/^[A-Za-z]+/, "").trim(); + value = this.prefix[countryCode] + value; + this.value(value); + } + } + return value; + }, + + validate: function (value) { + this.clearMessages(); + var countryCode = this.getCountry(); + + if (this.isChanging && value !== '') + { + if (!this.isSupportedCountry(countryCode)) { + return; + } + + value = this.sanitiseNumber(value); + value = this.addPrefixIfRequired(value, countryCode); + + if (typeof(this.patterns[countryCode]) !== 'undefined') { + var regex = new RegExp(this.patterns[countryCode]); + if (regex.test(value)) { + return this.validateTaxId(countryCode, value); + } else { + this.retry = false; + this.warn($t('Invalid Format')); + this.bubble('warn'); + } + } + } + }, + + validateTaxId: function (countryCode, taxId) { + $('body').trigger('processStart'); + var formKey = $.cookie('form_key'); + var self = this; + quote.shippingAddress().vatId = taxId; + $.ajax({ + type: 'POST', + url: url.build('autocustomergroup/checktaxid/validate/'), + data: { + tax_id: taxId, + country_code: countryCode, + form_key: formKey, + store_id: this.storeId + }, + success: function (response) { + self.clearMessages(); + if (quote.shippingMethod() === null) { + var nullCarrier = {method_code: null, carrier_code: null}; + quote.shippingMethod(nullCarrier); + } + if (response.valid === true) { + self.success(response.message); + self.bubble('success'); + setShippingInformation(); + } else { + self.retry = true; + self.warn(response.message); + self.bubble('warn'); + setShippingInformation(); + } + $('body').trigger('processStop'); + }, + error: function (response) { + self.retry = true; + self.warn($t('There was an error validating your Tax Id')); + self.bubble('warn'); + $('body').trigger('processStop'); + } + }); + }, + + retryValidation: function () { + this.isChanging = true; + this.validate(this.value()); + this.isChanging = false; + } + }); +}); diff --git a/view/frontend/web/template/tax-id-element.html b/view/frontend/web/template/tax-id-element.html new file mode 100644 index 0000000..99784f5 --- /dev/null +++ b/view/frontend/web/template/tax-id-element.html @@ -0,0 +1,24 @@ +
      + +
      + +
      + +
      + + + +
      + +
      + + +
      + + + + +
      + +
      +