EU VAT changes for sales to consumers in other EU countries
The intended process to handle the EU VAT switch 2015-01-01 is to first a day or a few days before the end of the year disable the creation of renewal invoices.
This is done by going to "Settings" / "Scheduled task settings" in the AdminPanel (URL is https://admin.somedomain.com/ScheduledTaskSettings), searching
for "Subscription", checking the row called "Subscription renewal" and then clicking the pause button.
Once done the row for "Subscription renewal" will have the text "Paused" in the "Next run" column.
The VAT rules configured should then be changed as soon as possible after 2015-01-01, and once done the renewals should be reenabled on the same page
with the "Run now / Resume" button. The process for changing the VAT rules are described below. Also a query for checking if affected (to private
persons in other EU countries than the reseller) orders where placed between 2015-01-01 00:00 and when the VAT rules were updated exists at the
bottom of this document.
Our VAT rules are stored in the table tax_rules for items having the RuleBasedTaxPlugin (default). They are evaluated in order based on the priority field and the first matching rule wins.
Before 2015-01-01 the VAT rules for companies in the EU (or countries with similar rules) we configured where:
priority 1 = customer country is reseller country => reseller country vat is charged for both company and consumer
priority 2 = customer country is in EU and vatno is specified => no VAT is charged (company)
priority 3 = customer country is in EU and vatno is not specified => reseller country vat is charged (consumer)
priority 4 = customer is outside of reseller country and EU => no vat is charged
After 2015-01-01 the rules are to be:
priority 1 (unchanged) = customer country is reseller country => reseller country vat is charged for both company and consumer
priority 2 (unchanged) = customer country is in EU and vatno is specified => no VAT is charged (company)prio
priority 3..31 (changed) = customer country is from a specific EU member state and vatno is not specified => customer country vat is charged (consumer)
priority 32 (unchanged, but prio moved) = customer is outside of reseller country and EU => no vat is charged
The rules have to be changed according to this for each fk_tax_profile_id + reseller_country_code in our customer's environment that currently has EU tax rules configured.
In the case of multiple resellers and/or multiple tax profiles, the following queries should be changed to reflect that.
1.Tax profiles can be found with:
SELECT * FROM tax_profile
2.It is important to first verify that the fk_tax_profile_id + reseller_country_code used has four rules matching the description above, i.e. like the following for a Swedish reseller (SE country code).
SELECT [name]
,[priority]
,[reseller_country_code]
,[customer_country_code]
,[customer_country_tag]
,[vat_number_present]
,[tax_rate]
FROM [AtomiaBilling].[dbo].[tax_rule]
WHERE fk_tax_profile_id = '2DAC8D7E-32C4-4A40-9E69-0422F8BEB505'
AND reseller_country_code = 'SE' -- should be changed to the reseller's country code for each reseller
ORDER BY priority
name |
priority |
reseller_country_code |
customer_country_code |
customer_country_tag |
vat_number_present |
tax_rate |
SE-SE | 1 | SE | SE | NULL | 0 | 25.0000 |
SE-EU 0% | 2 | SE | NULL | EU | 1 | 0.0000 |
SE-EU 25% | 3 | SE | NULL |
EU | 0 | 25.0000 |
SE-All | 4 | SE | NULL |
NULL | 0 | 0.0000 |
3.Here is the update script with variables set to update a
------------------------------------------------------------ Start of EU VAT change script.
BEGIN TRANSACTION
DECLARE @reseller_country_to_update varchar(255)
DECLARE @tax_profile_to_update uniqueidentifier
SET @reseller_country_to_update = 'SE' -- should be changed to the reseller's country code for each reseller
SET @tax_profile_to_update = '2DAC8D7E-32C4-4A40-9E69-0422F8BEB505'
-- Move non EU => no VAT rule last
UPDATE AtomiaBilling..tax_rule SET priority = 32 WHERE fk_tax_profile_id = @tax_profile_to_update AND reseller_country_code = @reseller_country_to_update AND priority = 4
-- Delete current third rule (EU consumer outside of reseller country)
DELETE AtomiaBilling..tax_rule WHERE fk_tax_profile_id = @tax_profile_to_update AND reseller_country_code = @reseller_country_to_update AND priority = 3
-- Insert the new rules
INSERT INTO AtomiaBilling..tax_rule (id, fk_tax_profile_id, name, priority, tax_part, reseller_country_code, customer_country_code, customer_country_tag, vat_number_present, vat_number_valid, tax_rate)
VALUES (NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-AT-' + '20%', 3, 0, @reseller_country_to_update, 'AT', 'EU', 2, 0, 20),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-BE-' + '21%', 4, 0, @reseller_country_to_update, 'BE', 'EU', 2, 0, 21),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-BG-' + '20%', 5, 0, @reseller_country_to_update, 'BG', 'EU', 2, 0, 20),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-HR-' + '25%', 6, 0, @reseller_country_to_update, 'HR', 'EU', 2, 0, 25),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-CY-' + '19%', 7, 0, @reseller_country_to_update, 'CY', 'EU', 2, 0, 19),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-CZ-' + '21%', 8, 0, @reseller_country_to_update, 'CZ', 'EU', 2, 0, 21),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-DK-' + '25%', 9, 0, @reseller_country_to_update, 'DK', 'EU', 2, 0, 25),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-EE-' + '20%', 10, 0, @reseller_country_to_update, 'EE', 'EU', 2, 0, 20),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-FI-' + '24%', 11, 0, @reseller_country_to_update, 'FI', 'EU', 2, 0, 24),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-FR-' + '20%', 12, 0, @reseller_country_to_update, 'FR', 'EU', 2, 0, 20),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-DE-' + '19%', 13, 0, @reseller_country_to_update, 'DE', 'EU', 2, 0, 19),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-GR-' + '23%', 14, 0, @reseller_country_to_update, 'GR', 'EU', 2, 0, 23),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-HU-' + '27%', 15, 0, @reseller_country_to_update, 'HU', 'EU', 2, 0, 27),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-IE-' + '23%', 16, 0, @reseller_country_to_update, 'IE', 'EU', 2, 0, 23),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-IT-' + '22%', 17, 0, @reseller_country_to_update, 'IT', 'EU', 2, 0, 22),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-LV-' + '21%', 18, 0, @reseller_country_to_update, 'LV', 'EU', 2, 0, 21),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-LT-' + '21%', 19, 0, @reseller_country_to_update, 'LT', 'EU', 2, 0, 21),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-LU-' + '15%', 20, 0, @reseller_country_to_update, 'LU', 'EU', 2, 0, 15),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-MT-' + '18%', 21, 0, @reseller_country_to_update, 'MT', 'EU', 2, 0, 18),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-NL-' + '21%', 22, 0, @reseller_country_to_update, 'NL', 'EU', 2, 0, 21),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-PL-' + '23%', 23, 0, @reseller_country_to_update, 'PL', 'EU', 2, 0, 23),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-PT-' + '23%', 24, 0, @reseller_country_to_update, 'PT', 'EU', 2, 0, 23),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-RO-' + '24%', 25, 0, @reseller_country_to_update, 'RO', 'EU', 2, 0, 24),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-SK-' + '20%', 26, 0, @reseller_country_to_update, 'SK', 'EU', 2, 0, 20),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-SI-' + '22%', 27, 0, @reseller_country_to_update, 'SI', 'EU', 2, 0, 22),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-ES-' + '21%', 28, 0, @reseller_country_to_update, 'ES', 'EU', 2, 0, 21),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-SE-' + '25%', 29, 0, @reseller_country_to_update, 'SE', 'EU', 2, 0, 25),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-UK-' + '20%', 30, 0, @reseller_country_to_update, 'UK', 'EU', 2, 0, 20),
(NEWID(), @tax_profile_to_update, @reseller_country_to_update + '-GB-' + '20%', 31, 0, @reseller_country_to_update, 'GB', 'EU', 2, 0, 20)
COMMIT TRANSACTION
------------------------------------------------------------ End of EU VAT change script.
4.Which after the update should give a result like:
SELECT [name]
,[priority]
,[reseller_country_code]
,[customer_country_code]
,[customer_country_tag]
,[vat_number_present]
,[tax_rate]
FROM [AtomiaBilling].[dbo].[tax_rule]
WHERE fk_tax_profile_id = '2DAC8D7E-32C4-4A40-9E69-0422F8BEB505' AND reseller_country_code = 'SE'
ORDER BY priority
name | priority | reseller_country_code | customer_country_code | customer_country_tag | vat_number_present | tax_rate |
SE-SE | 1 | SE | SE | NULL | 0 | 25 |
SE-EU 0% | 2 | SE | NULL | EU | 1 | 0 |
SE-AT-20% | 3 | SE | AT | EU | 2 | 20 |
SE-BE-21% | 4 | SE | BE | EU | 2 | 21 |
SE-BG-20% | 5 | SE | BG | EU | 2 | 20 |
SE-HR-25% | 6 | SE | HR | EU | 2 | 25 |
SE-CY-19% | 7 | SE | CY | EU | 2 | 19 |
SE-CZ-21% | 8 | SE | CZ | EU | 2 | 21 |
SE-DK-25% | 9 | SE | DK | EU | 2 | 25 |
SE-EE-20% | 10 | SE | EE | EU | 2 | 20 |
SE-FI-24% | 11 | SE | FI | EU | 2 | 24 |
SE-FR-20% | 12 | SE | FR | EU | 2 | 20 |
SE-DE-19% | 13 | SE | DE | EU | 2 | 19 |
SE-GR-23% | 14 | SE | GR | EU | 2 | 23 |
SE-HU-27% | 15 | SE | HU | EU | 2 | 27 |
SE-IE-23% | 16 | SE | IE | EU | 2 | 23 |
SE-IT-22% | 17 | SE | IT | EU | 2 | 22 |
SE-LV-21% | 18 | SE | LV | EU | 2 | 21 |
SE-LT-21% | 19 | SE | LT | EU | 2 | 21 |
SE-LU-15% | 20 | SE | LU | EU | 2 | 15 |
SE-MT-18% | 21 | SE | MT | EU | 2 | 18 |
SE-NL-21% | 22 | SE | NL | EU | 2 | 21 |
SE-PL-23% | 23 | SE | PL | EU | 2 | 23 |
SE-PT-23% | 24 | SE | PT | EU | 2 | 23 |
SE-RO-24% | 25 | SE | RO | EU | 2 | 24 |
SE-SK-20% | 26 | SE | SK | EU | 2 | 20 |
SE-SI-22% | 27 | SE | SI | EU | 2 | 22 |
SE-ES-21% | 28 | SE | ES | EU | 2 | 21 |
SE-SE-25% | 29 | SE | SE | EU | 2 | 25 |
SE-UK-20% | 30 | SE | UK | EU | 2 | 20 |
SE-GB-20% | 31 | SE | GB | EU | 2 | 20 |
SE-All | 32 | SE | NULL | NULL | 0 | 0 |
Here is an SQL query to list all orders made after 2015-01-01 before the tax rules are updated. These should be checked and their invoices corrected/credited if a wrong tax is applied.
select reseller.id ResellerId, reseller.name ResellerNo, r_address.company_name ResellerName, r_country.name + ' (' + r_country.code + ')' ResellerCountry,
customer.id CustomerId, customer.name CustomerNo, c_country.name + ' (' + c_country.code + ')' CustomerCountry, o.id OrderId, o.number OrderNo, o.created_time OrderCreationTime
from AtomiaBilling..order_data o
inner join AtomiaAccount..account reseller on reseller.id = o.reseller_id
inner join AtomiaAccount..account_address r_address on r_address.id = reseller.fk_main_address_id
inner join AtomiaAccount..country r_country on r_address.fk_country_id = r_country.id and r_country.tag = 'EU'
inner join AtomiaAccount..account customer on customer.id = o.customer_id
inner join AtomiaAccount..account_address c_address on customer.fk_main_address_id = c_address.id
inner join AtomiaAccount..country c_country on c_address.fk_country_id = c_country.id and c_country.tag = 'EU'
where o.created_time > '2015-01-01' and c_country.code <> r_country.code
order by reseller.name, o.created_time
All of the above queries are for SQL database and they have the numeric mark next to each query.
Below you will find all these queries for PostgreSQL database where their numerics match numerics of SQL queries.
IMPORTANT: You need to be INTO AtomiaBilling database!!!
1.Tax profiles can be found with:
SELECT * FROM tax_profile;
2.It is important to first verify that the fk_tax_profile_id + reseller_country_code used has four rules matching the description above, i.e. like the following for a Swedish reseller (SE country code).
SELECT name, priority, reseller_country_code, customer_country_code, customer_country_tag, vat_number_present, tax_rate
FROM tax_rule
WHERE fk_tax_profile_id = '2DAC8D7E-32C4-4A40-9E69-0422F8BEB505'
AND reseller_country_code = 'SE'
ORDER BY priority;
3.Here is the update script with variables set to update a
--you need first to create function with required reseller country code
CREATE or REPLACE function reseller_country() returns text language plpgsql AS $$
DECLARE
res_count text;
BEGIN
res_count := 'SE';
return res_count;
END;
$$
--run below query to check did you set function to correct reseller country code
SELECT reseller_country();
--you need first to create function with required tax_profile_id
CREATE or REPLACE function tax_profile() returns uuid language plpgsql AS $$
DECLARE
tax_prof uuid;
BEGIN
tax_prof := '2DAC8D7E-32C4-4A40-9E69-0422F8BEB505';
return tax_prof;
END;
$$
--run below query to check did you set function to correct tax_profile_id
select tax_profile();
>Firsto to run this query
CREATE EXTENSION "uuid-ossp";
>Then to run this query
BEGIN;
UPDATE tax_rule SET priority = 32 WHERE fk_tax_profile_id = tax_profile() AND reseller_country_code = reseller_country() AND priority = 4;
-- Delete current third rule (EU consumer outside of reseller country)
DELETE from tax_rule WHERE fk_tax_profile_id = tax_profile() AND reseller_country_code = reseller_country() AND priority = 3;
-- Insert the new rules
INSERT INTO tax_rule (id, fk_tax_profile_id, name, priority, tax_part, reseller_country_code, customer_country_code, customer_country_tag, vat_number_present, vat_number_valid, tax_rate)
VALUES (uuid_generate_v4(), tax_profile(), reseller_country() || '-AT-' || '20%', 3, 0, reseller_country(), 'AT', 'EU', 2, 0, 20),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-BE-' || '21%', 4, 0, reseller_country(), 'BE', 'EU', 2, 0, 21),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-BG-' || '20%', 5, 0, reseller_country(), 'BG', 'EU', 2, 0, 20),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-HR-' || '25%', 6, 0, reseller_country(), 'HR', 'EU', 2, 0, 25),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-CY-' || '19%', 7, 0, reseller_country(), 'CY', 'EU', 2, 0, 19),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-CZ-' || '21%', 8, 0, reseller_country(), 'CZ', 'EU', 2, 0, 21),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-DK-' || '25%', 9, 0, reseller_country(), 'DK', 'EU', 2, 0, 25),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-EE-' || '20%', 10, 0, reseller_country(), 'EE', 'EU', 2, 0, 20),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-FI-' || '24%', 11, 0, reseller_country(), 'FI', 'EU', 2, 0, 24),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-FR-' || '20%', 12, 0, reseller_country(), 'FR', 'EU', 2, 0, 20),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-DE-' || '19%', 13, 0, reseller_country(), 'DE', 'EU', 2, 0, 19),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-GR-' || '23%', 14, 0, reseller_country(), 'GR', 'EU', 2, 0, 23),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-HU-' || '27%', 15, 0, reseller_country(), 'HU', 'EU', 2, 0, 27),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-IE-' || '23%', 16, 0, reseller_country(), 'IE', 'EU', 2, 0, 23),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-IT-' || '22%', 17, 0, reseller_country(), 'IT', 'EU', 2, 0, 22),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-LV-' || '21%', 18, 0, reseller_country(), 'LV', 'EU', 2, 0, 21),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-LT-' || '21%', 19, 0, reseller_country(), 'LT', 'EU', 2, 0, 21),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-LU-' || '15%', 20, 0, reseller_country(), 'LU', 'EU', 2, 0, 15),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-MT-' || '18%', 21, 0, reseller_country(), 'MT', 'EU', 2, 0, 18),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-NL-' || '21%', 22, 0, reseller_country(), 'NL', 'EU', 2, 0, 21),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-PL-' || '23%', 23, 0, reseller_country(), 'PL', 'EU', 2, 0, 23),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-PT-' || '23%', 24, 0, reseller_country(), 'PT', 'EU', 2, 0, 23),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-RO-' || '24%', 25, 0, reseller_country(), 'RO', 'EU', 2, 0, 24),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-SK-' || '20%', 26, 0, reseller_country(), 'SK', 'EU', 2, 0, 20),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-SI-' || '22%', 27, 0, reseller_country(), 'SI', 'EU', 2, 0, 22),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-ES-' || '21%', 28, 0, reseller_country(), 'ES', 'EU', 2, 0, 21),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-SE-' || '25%', 29, 0, reseller_country(), 'SE', 'EU', 2, 0, 25),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-UK-' || '20%', 30, 0, reseller_country(), 'UK', 'EU', 2, 0, 20),
(uuid_generate_v4(), tax_profile(), reseller_country() || '-GB-' || '20%', 31, 0, reseller_country(), 'GB', 'EU', 2, 0, 20);
END;
COMMIT TRANSACTION;
4.Which after the update should give a result like:
SELECT name, priority, reseller_country_code, customer_country_code, customer_country_tag, vat_number_present, tax_rate
FROM tax_rule
WHERE fk_tax_profile_id = '2DAC8D7E-32C4-4A40-9E69-0422F8BEB505'
AND reseller_country_code = 'SE'
ORDER BY priority;
5.In order to have tax rules to work properly, table tax_item_category in AtomiaBilling must be UPDATED. To update tax_item_category table you must run next script.
INSERT INTO tax_item_category(id, fk_tax_profile_id, name, pretty_name)
VALUES (uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'Hosting', 'Hosting'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'DNS', 'DNS'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'Domain', 'Domain'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'ExtraService', 'ExtraService'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'DomainTransfer', 'DomainTransfer'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'DomainOwnerChange', 'DomainOwnerChange'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'DomainRedemptionRestore', 'DomainRedemptionRestore'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'VPS', 'VPS'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'Cloud', 'Cloud'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'VDC', 'VDC'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'SSL', 'SSL'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'PrepaidCredit', 'PrepaidCredit'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'Billing', 'Billing'),
(uuid_generate_v4(), '2dac8d7e-32c4-4a40-9e69-0422f8beb505', 'Campaign', 'Campaign');
NOTE: Results of all queries for PostgreSQL are the same as for SQL queries.