├── README.md
├── composer.json
├── modman
└── src
└── app
├── code
└── community
│ └── IntegerNet
│ └── EuropeanTax
│ ├── Helper
│ ├── Customer
│ │ └── Data.php
│ └── Data.php
│ ├── Model
│ ├── Customer
│ │ └── Observer.php
│ ├── Observer.php
│ └── Sales
│ │ └── Quote.php
│ ├── etc
│ └── config.xml
│ └── sql
│ └── integernet_europeantax_setup
│ ├── install-0.1.0.php
│ ├── upgrade-0.1.0-0.2.0.php
│ ├── upgrade-0.2.0-0.3.0.php
│ └── upgrade-0.3.0-0.4.0.php
├── etc
└── modules
│ └── IntegerNet_EuropeanTax.xml
└── locale
└── de_DE
└── IntegerNet_EuropeanTax.csv
/README.md:
--------------------------------------------------------------------------------
1 | IntegerNet_EuropeanTax
2 | =====================
3 | Tax calculation independant of customer groups, using different tax IDs for different customer addresses
4 |
5 | Facts
6 | -----
7 | - version: 0.5.1
8 | - extension key: IntegerNet_EuropeanTax
9 | - [extension on GitHub](https://github.com/integer-net/EuropeanTax)
10 | - [direct download link](https://github.com/integer-net/EuropeanTax/archive/master.zip)
11 |
12 | Description
13 | -----------
14 | This module creates an additional field in the customer group form called "Tax Class with valid VAT ID". It
15 | determines on the fly which tax class to use depending on a valid VAT ID being entered for the currently selected
16 | shipping address.
17 |
18 | 
19 |
20 | Read more about the context at [https://www.integer-net.com/tax-configuration-eu-for-b2b-and-b2c-stores/](https://www.integer-net.com/tax-configuration-eu-for-b2b-and-b2c-stores/).
21 |
22 | Requirements
23 | ------------
24 | - PHP >= 5.3.0
25 |
26 | Compatibility
27 | -------------
28 | - Magento >= 1.6
29 |
30 | Installation Instructions
31 | -------------------------
32 | 1. Clone the module into your document root.
33 | 2. Update the fields "Tax Class with valid VAT ID" in the customer groups
34 |
35 | Uninstallation
36 | --------------
37 | 1. Remove all extension files from your Magento installation
38 |
39 | Support
40 | -------
41 | If you have any issues with this extension, open an issue on [GitHub](https://github.com/integer-net/EuropeanTax/issues).
42 |
43 | Contribution
44 | ------------
45 | Any contribution is highly appreciated. The best way to contribute code is to open a [pull request on GitHub](https://help.github.com/articles/using-pull-requests).
46 |
47 | Developer
48 | ---------
49 | Andreas von Studnitz, integer_net GmbH
50 | [http://www.integer-net.com](http://www.integer-net.com)
51 | [@integer_net](https://twitter.com/integer_net)
52 |
53 | Licence
54 | -------
55 | [GNU General Public License 3.0](http://www.gnu.org/licenses/)
56 |
57 | Copyright
58 | ---------
59 | (c) 2016 integer_net GmbH
60 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "integer_net/european-tax",
3 | "type": "magento-module",
4 | "description": "European Tax",
5 | "homepage": "https://github.com/integer-net/EuropeanTax"
6 | }
--------------------------------------------------------------------------------
/modman:
--------------------------------------------------------------------------------
1 | src/app/code/community/IntegerNet/* app/code/community/IntegerNet/
2 | src/app/etc/modules/* app/etc/modules/
3 | src/app/locale/de_DE/* app/locale/de_DE/
--------------------------------------------------------------------------------
/src/app/code/community/IntegerNet/EuropeanTax/Helper/Customer/Data.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class IntegerNet_EuropeanTax_Helper_Customer_Data extends Mage_Customer_Helper_Data
11 | {
12 | /**
13 | * Rewrite - Allow VAT ID to contain country code at the beginning
14 | *
15 | * @param string $countryCode
16 | * @param string $vatNumber
17 | * @param string $requesterCountryCode
18 | * @param string $requesterVatNumber
19 | * @return Varien_Object
20 | */
21 | public function checkVatNumber($countryCode, $vatNumber, $requesterCountryCode = '', $requesterVatNumber = '')
22 | {
23 | if (substr($vatNumber, 0, 2) == $countryCode) {
24 | $vatNumber = substr($vatNumber, 2);
25 | }
26 |
27 | if ($requesterVatNumber && substr($requesterVatNumber, 0, 2) == $requesterCountryCode) {
28 | $requesterVatNumber = substr($requesterVatNumber, 2);
29 | }
30 |
31 | return parent::checkVatNumber($countryCode, $vatNumber, $requesterCountryCode, $requesterVatNumber);
32 | }
33 | }
--------------------------------------------------------------------------------
/src/app/code/community/IntegerNet/EuropeanTax/Helper/Data.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class IntegerNet_EuropeanTax_Helper_Data extends Mage_Core_Helper_Abstract
11 | {}
--------------------------------------------------------------------------------
/src/app/code/community/IntegerNet/EuropeanTax/Model/Customer/Observer.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class IntegerNet_EuropeanTax_Model_Customer_Observer extends Mage_Customer_Model_Observer
11 | {
12 | /**
13 | * Validate vat id if given and assign tax class to customer address
14 | *
15 | * @param Varien_Event_Observer $observer
16 | */
17 | public function afterAddressSave($observer)
18 | {
19 | /** @var $customerAddress Mage_Customer_Model_Address */
20 | $customerAddress = $observer->getCustomerAddress();
21 | $customer = $customerAddress->getCustomer();
22 |
23 | if (!Mage::helper('customer/address')->isVatValidationEnabled($customer->getStore())
24 | || Mage::registry(self::VIV_PROCESSED_FLAG)
25 | || !$this->_canProcessAddress($customerAddress)
26 | ) {
27 | return;
28 | }
29 |
30 | try {
31 | Mage::register(self::VIV_PROCESSED_FLAG, true);
32 |
33 | /** @var $customerHelper Mage_Customer_Helper_Data */
34 | $customerHelper = Mage::helper('customer');
35 |
36 | if ($customerAddress->getVatId() == ''
37 | || !Mage::helper('core')->isCountryInEU($customerAddress->getCountry()))
38 | {
39 | $defaultGroupId = $customerHelper->getDefaultCustomerGroupId($customer->getStore());
40 |
41 | if (!$customer->getDisableAutoGroupChange() && $customer->getGroupId() != $defaultGroupId) {
42 | $customerGroup = Mage::getModel('customer/group')->load($customer->getGroupId());
43 | $customerAddress->setTaxClassId($customerGroup->getTaxClassId());
44 | $customerAddress->save();
45 | }
46 | } else {
47 |
48 | $result = $customerHelper->checkVatNumber(
49 | $customerAddress->getCountryId(),
50 | $customerAddress->getVatId()
51 | );
52 |
53 | if (!$customer->getDisableAutoGroupChange()) {
54 | $customerGroup = Mage::getModel('customer/group')->load($customer->getGroupId());
55 | if ($result->getIsValid()) {
56 | $customerAddress->setTaxClassId($customerGroup->getTaxClassIdVatId());
57 | } else {
58 | $customerAddress->setTaxClassId($customerGroup->getTaxClassId());
59 | }
60 | $customerAddress->save();
61 | }
62 |
63 | if (!Mage::app()->getStore()->isAdmin()) {
64 | $validationMessage = Mage::helper('customer')->getVatValidationUserMessage($customerAddress,
65 | $customer->getDisableAutoGroupChange(), $result);
66 |
67 | if (!$validationMessage->getIsError()) {
68 | Mage::getSingleton('customer/session')->addSuccess($validationMessage->getMessage());
69 | } else {
70 | Mage::getSingleton('customer/session')->addError($validationMessage->getMessage());
71 | }
72 | }
73 | }
74 | } catch (Exception $e) {
75 | Mage::register(self::VIV_PROCESSED_FLAG, false, true);
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/src/app/code/community/IntegerNet/EuropeanTax/Model/Observer.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class IntegerNet_EuropeanTax_Model_Observer
11 | {
12 | /**
13 | * VAT ID validation processed flag code
14 | */
15 | const VIV_PROCESSED_FLAG = 'viv_after_address_save_processed';
16 |
17 | /**
18 | * Retrieve correct tax class - from quote address, if given, otherwise from default shipping address
19 | *
20 | * @param Varien_Event_Observer $observer
21 | */
22 | public function customerLoadAfter(Varien_Event_Observer $observer)
23 | {
24 | /** @var $customer Mage_Customer_Model_Customer */
25 | $customer = $observer->getCustomer();
26 | if ($shippingAddress = $customer->getDefaultShippingAddress()) {
27 |
28 | if ($taxClassId = $shippingAddress->getTaxClassId()) {
29 | $customer->setTaxClassId($taxClassId);
30 | }
31 | }
32 |
33 | if ($quoteId = $this->_getSession()->getQuoteId()) {
34 | /** @var $quoteShippingAddressCollection Mage_Sales_Model_Resource_Quote_Address_Collection */
35 | $quoteShippingAddressCollection = Mage::getResourceModel('sales/quote_address_collection');
36 | $quoteShippingAddressCollection->addFieldToFilter('quote_id', $quoteId);
37 | $quoteShippingAddressCollection->addFieldToFilter('address_type', 'shipping');
38 |
39 | $quoteShippingAddress = $quoteShippingAddressCollection->getFirstItem();
40 | if ($quoteShippingAddress->getId()) {
41 | if ($taxClassId = $quoteShippingAddress->getTaxClassId()) {
42 | $customer->setTaxClassId($taxClassId);
43 | }
44 | }
45 | }
46 | }
47 |
48 | /**
49 | * Add fields to customer group form
50 | *
51 | * @param Varien_Event_Observer $observer
52 | */
53 | public function coreBlockAbstractPrepareLayoutAfter(Varien_Event_Observer $observer)
54 | {
55 | $block = $observer->getBlock();
56 |
57 | if ($block instanceof Mage_Adminhtml_Block_Customer_Group_Edit_Form) {
58 |
59 | $form = $block->getForm();
60 |
61 | $fieldset = $form->getElement('base_fieldset');
62 |
63 | if (Mage::getSingleton('adminhtml/session')->getCustomerGroupData()) {
64 | $values = Mage::getSingleton('adminhtml/session')->getCustomerGroupData();
65 | } else {
66 | $values = Mage::registry('current_group')->getData();
67 | }
68 |
69 | $fieldset->addField('tax_class_id_vat_id', 'select',
70 | array(
71 | 'name' => 'tax_class_id_vat_id',
72 | 'label' => Mage::helper('integernet_europeantax')->__('Tax Class with valid VAT ID'),
73 | 'title' => Mage::helper('integernet_europeantax')->__('Tax Class with valid VAT ID'),
74 | 'class' => 'required-entry',
75 | 'required' => true,
76 | 'values' => Mage::getSingleton('tax/class_source_customer')->toOptionArray(),
77 | 'value' => isset($values['tax_class_id_vat_id']) ? $values['tax_class_id_vat_id'] : null,
78 | )
79 | );
80 |
81 | $fieldset->addField('request_vat_id', 'select', array(
82 | 'name' => 'request_vat_id',
83 | 'label' => Mage::helper('integernet_europeantax')->__('Request VAT ID'),
84 | 'title' => Mage::helper('integernet_europeantax')->__('Request VAT ID'),
85 | 'class' => '',
86 | 'values' => Mage::getSingleton('eav/entity_attribute_source_boolean')->getAllOptions(),
87 | 'value' => isset($values['request_vat_id']) ? $values['request_vat_id'] : 0,
88 | 'required' => false,
89 | ));
90 |
91 | }
92 | }
93 |
94 | /**
95 | * Handle data of new fields in customer group when saving
96 | *
97 | * @param Varien_Event_Observer $observer
98 | */
99 | public function customerGroupSaveBefore($observer)
100 | {
101 | /** @var Mage_Customer_Model_Group $group */
102 | $group = $observer->getObject();
103 |
104 | $group->setData('request_vat_id', Mage::app()->getRequest()->getParam('request_vat_id'));
105 | $group->setData('tax_class_id_vat_id', Mage::app()->getRequest()->getParam('tax_class_id_vat_id'));
106 | }
107 |
108 | /**
109 | * Validate vat id if given and assign tax class to quote shipping address
110 | *
111 | * @param Varien_Event_Observer $observer
112 | */
113 | public function salesQuoteAddressSaveAfter(Varien_Event_Observer $observer)
114 | {
115 | /** @var Mage_Sales_Model_Quote_Address $quoteAddress */
116 | $quoteAddress = $observer->getQuoteAddress();
117 |
118 | if ($quoteAddress->getAddressType() != 'shipping') {
119 | return;
120 | }
121 |
122 | $customer = $quoteAddress->getQuote()->getCustomer();
123 |
124 | if (!Mage::helper('customer/address')->isVatValidationEnabled($customer->getStore())
125 | || Mage::registry(self::VIV_PROCESSED_FLAG)
126 | ) {
127 | return;
128 | }
129 |
130 | try {
131 | Mage::register(self::VIV_PROCESSED_FLAG, true);
132 |
133 | /** @var $customerHelper Mage_Customer_Helper_Data */
134 | $customerHelper = Mage::helper('customer');
135 |
136 | if ($quoteAddress->getVatId() == ''
137 | || !Mage::helper('core')->isCountryInEU($quoteAddress->getCountry()))
138 | {
139 | $defaultGroupId = $customerHelper->getDefaultCustomerGroupId($customer->getStore());
140 |
141 | if (!$customer->getDisableAutoGroupChange() && $customer->getGroupId() != $defaultGroupId) {
142 | $customerGroup = Mage::getModel('customer/group')->load($customer->getGroupId());
143 | $quoteAddress->setTaxClassId($customerGroup->getTaxClassId());
144 | $quoteAddress->save();
145 | }
146 | } else {
147 |
148 | $result = $customerHelper->checkVatNumber(
149 | $quoteAddress->getCountryId(),
150 | $quoteAddress->getVatId()
151 | );
152 |
153 | if (!$customer->getDisableAutoGroupChange()) {
154 | $customerGroup = Mage::getModel('customer/group')->load($customer->getGroupId());
155 | if ($result->getIsValid()) {
156 | $quoteAddress->setTaxClassId($customerGroup->getTaxClassIdVatId());
157 | } else {
158 | $quoteAddress->setTaxClassId($customerGroup->getTaxClassId());
159 | }
160 | $quoteAddress->save();
161 | }
162 |
163 | if (!Mage::app()->getStore()->isAdmin()) {
164 | $validationMessage = Mage::helper('customer')->getVatValidationUserMessage($quoteAddress,
165 | $customer->getDisableAutoGroupChange(), $result);
166 |
167 | if (!$validationMessage->getIsError()) {
168 | Mage::getSingleton('customer/session')->addSuccess($validationMessage->getMessage());
169 | } else {
170 | Mage::getSingleton('customer/session')->addError($validationMessage->getMessage());
171 | }
172 | }
173 | }
174 | } catch (Exception $e) {
175 | Mage::register(self::VIV_PROCESSED_FLAG, false, true);
176 | }
177 | }
178 |
179 | /**
180 | * @return Mage_Checkout_Model_Session|Mage_Adminhtml_Model_Session_Quote
181 | */
182 | protected function _getSession()
183 | {
184 | if ($this->_isAdmin()) {
185 | return Mage::getSingleton('adminhtml/session_quote');
186 | }
187 |
188 | return Mage::getSingleton('checkout/session');
189 | }
190 |
191 | /**
192 | * @return bool
193 | */
194 | protected function _isAdmin()
195 | {
196 | return Mage::app()->getStore()->isAdmin() || Mage::getDesign()->getArea() == 'adminhtml';
197 | }
198 |
199 | }
--------------------------------------------------------------------------------
/src/app/code/community/IntegerNet/EuropeanTax/Model/Sales/Quote.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class IntegerNet_EuropeanTax_Model_Sales_Quote extends Mage_Sales_Model_Quote
11 | {
12 | public function getCustomerTaxClassId()
13 | {
14 | if ($this->_isAdmin()) {
15 |
16 | /** @var $customer Mage_Customer_Model_Customer */
17 | $customer = Mage::getSingleton('adminhtml/session_quote')->getCustomer();
18 |
19 | } else {
20 |
21 | /** @var $customer Mage_Customer_Model_Customer */
22 | $customer = Mage::getSingleton('customer/session')->getCustomer();
23 | }
24 |
25 | if ($taxClassId = $customer->getTaxClassId()) {
26 | $this->setCustomerTaxClassId($taxClassId);
27 | return $this->getData('customer_tax_class_id');
28 | }
29 |
30 | return parent::getCustomerTaxClassId();
31 | }
32 |
33 | /**
34 | * @return bool
35 | */
36 | protected function _isAdmin()
37 | {
38 | return Mage::app()->getStore()->isAdmin() || Mage::getDesign()->getArea() == 'adminhtml';
39 | }
40 | }
--------------------------------------------------------------------------------
/src/app/code/community/IntegerNet/EuropeanTax/etc/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 0.5.1
6 |
7 |
8 |
9 |
10 |
11 | IntegerNet_EuropeanTax_Model
12 |
13 |
14 |
15 | IntegerNet_EuropeanTax_Model_Sales_Quote
16 |
17 |
18 |
19 |
20 |
21 | IntegerNet_EuropeanTax_Helper
22 |
23 |
24 |
25 | IntegerNet_EuropeanTax_Helper_Customer_Data
26 |
27 |
28 |
29 |
30 |
31 |
32 | IntegerNet_EuropeanTax
33 | Mage_Customer_Model_Resource_Setup
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | singleton
42 | integernet_europeantax/observer
43 | customerLoadAfter
44 |
45 |
46 |
47 |
48 |
49 |
50 | integernet_europeantax/customer_observer
51 | afterAddressSave
52 |
53 |
54 |
55 |
56 |
57 |
58 | integernet_europeantax/observer
59 | salesQuoteAddressSaveAfter
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | *
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | singleton
79 | integernet_europeantax/observer
80 | coreBlockAbstractPrepareLayoutAfter
81 |
82 |
83 |
84 |
85 |
86 |
87 | singleton
88 | integernet_europeantax/observer
89 | customerGroupSaveBefore
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | IntegerNet_EuropeanTax.csv
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | disabled
112 |
113 |
114 |
115 |
116 |
117 |
118 | disabled
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/src/app/code/community/IntegerNet/EuropeanTax/sql/integernet_europeantax_setup/install-0.1.0.php:
--------------------------------------------------------------------------------
1 | startSetup();
6 |
7 | $installer->addAttribute('customer_address', 'tax_class_id', array(
8 | 'label' => 'Steuerklasse',
9 | 'type' => 'int',
10 | 'input' => 'select',
11 | 'source' => 'tax/class_source_customer',
12 | 'visible' => true,
13 | ));
14 |
15 | Mage::getSingleton('eav/config')
16 | ->getAttribute('customer_address', 'tax_class_id')
17 | ->setData('used_in_forms', array('adminhtml_customer_address'))
18 | ->save();
19 |
20 | $installer->endSetup();
--------------------------------------------------------------------------------
/src/app/code/community/IntegerNet/EuropeanTax/sql/integernet_europeantax_setup/upgrade-0.1.0-0.2.0.php:
--------------------------------------------------------------------------------
1 | startSetup();
6 |
7 | $installer->getConnection()->addColumn($this->getTable('sales_flat_quote_address'), 'tax_class_id', 'int(11)');
8 |
9 | $installer->endSetup();
--------------------------------------------------------------------------------
/src/app/code/community/IntegerNet/EuropeanTax/sql/integernet_europeantax_setup/upgrade-0.2.0-0.3.0.php:
--------------------------------------------------------------------------------
1 | startSetup();
6 |
7 | $installer->getConnection()->addColumn($this->getTable('customer/customer_group'), 'request_vat_id', 'int(1)');
8 | $installer->getConnection()->addColumn($this->getTable('customer/customer_group'), 'tax_class_id_vat_id', 'int(11)');
9 |
10 | $installer->endSetup();
--------------------------------------------------------------------------------
/src/app/code/community/IntegerNet/EuropeanTax/sql/integernet_europeantax_setup/upgrade-0.3.0-0.4.0.php:
--------------------------------------------------------------------------------
1 | startSetup();
6 |
7 | $installer->setConfigData('customer/address/taxvat_show', '');
8 | $installer->setConfigData('customer/create_account/auto_group_assign', 0);
9 | $installer->setConfigData('customer/create_account/tax_calculation_address_type', 'shipping');
10 | $installer->setConfigData('customer/create_account/vat_frontend_visibility', 1);
11 |
12 | $installer->endSetup();
13 |
--------------------------------------------------------------------------------
/src/app/etc/modules/IntegerNet_EuropeanTax.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 | community
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/app/locale/de_DE/IntegerNet_EuropeanTax.csv:
--------------------------------------------------------------------------------
1 |
2 | "Tax Class with valid VAT ID","Steuerklasse bei gültiger USt.-ID"
3 | "Request VAT ID","USt.-ID anfordern"
--------------------------------------------------------------------------------