from decimal import Decimal
import logging
from django.core.exceptions import ImproperlyConfigured
from suds import WebFault
from suds.transport import TransportError
import vatnumber
import stdnum
from plans.taxation import TaxationPolicy
logger = logging.getLogger('plans.taxation.eu.vies')
[docs]class EUTaxationPolicy(TaxationPolicy):
"""
This taxation policy should be correct for all EU countries. It uses following rules:
* if issuer country is not in EU - assert error,
* for buyer of the same country as issuer - return issuer tax,
* for company buyer from EU (with VIES) returns VAT n/a reverse charge,
* for non-company buyer from EU returns VAT from buyer country,
* for non-EU buyer return VAT n/a.
This taxation policy was updated at 1 Jan 2015 after new UE VAT regulations. You should also probably
register in MOSS system.
"""
# Standard VAT rates according to http://ec.europa.eu/taxation_customs/resources/documents/taxation/vat/how_vat_works/rates/vat_rates_en.pdf
# Situation at 1 Jan 2015
EU_COUNTRIES_VAT = {
'BE': Decimal('21'), # Belgium
'BG': Decimal('20'), # Bulgaria
'CZ': Decimal('21'), # Czech Republic
'DK': Decimal('25'), # Denmark
'DE': Decimal('19'), # Germany
'EE': Decimal('20'), # Estonia
'EL': Decimal('23'), # Greece
'ES': Decimal('21'), # Spain
'FR': Decimal('20'), # France
'HR': Decimal('25'), # Croatia
'IE': Decimal('23'), # Ireland
'IT': Decimal('22'), # Italy
'CY': Decimal('19'), # Cyprus
'LV': Decimal('21'), # Latvia
'LT': Decimal('21'), # Lithuania
'LU': Decimal('15'), # Luxembourg
'HU': Decimal('27'), # Hungary
'MT': Decimal('18'), # Malta
'NL': Decimal('21'), # Netherlands
'AT': Decimal('20'), # Austria
'PL': Decimal('23'), # Poland
'PT': Decimal('23'), # Portugal
'RO': Decimal('24'), # Romania
'SI': Decimal('22'), # Slovenia
'SK': Decimal('20'), # Slovakia
'FI': Decimal('24'), # Finland
'SE': Decimal('25'), # Sweden
'GB': Decimal('20'), # United Kingdom (Great Britain)
}
@classmethod
def is_in_EU(cls, country_code):
return country_code.upper() in cls.EU_COUNTRIES_VAT
@classmethod
def get_default_tax(cls):
issuer_country_code = cls.get_issuer_country_code()
try:
return cls.EU_COUNTRIES_VAT[issuer_country_code]
except KeyError:
raise ImproperlyConfigured("EUTaxationPolicy requires that issuer country is in EU")
@classmethod
def get_tax_rate(cls, tax_id, country_code):
issuer_country_code = cls.get_issuer_country_code()
if not cls.is_in_EU(issuer_country_code):
raise ImproperlyConfigured("EUTaxationPolicy requires that issuer country is in EU")
if not tax_id and not country_code:
# No vat id, no country
return cls.get_default_tax()
elif not tax_id and country_code:
# Customer is not a company, we know his country
if cls.is_in_EU(country_code):
# Customer (private person) is from a EU
# Customer pays his VAT rate
return cls.EU_COUNTRIES_VAT[country_code]
else:
# Customer (private person) not from EU
# VAT n/a
return None
else:
# Customer is company, we now country and vat id
if country_code.upper() == issuer_country_code.upper():
# Company is from the same country as issuer
# Normal tax
return cls.get_default_tax()
if cls.is_in_EU(country_code):
# Company is from other EU country
try:
vies_result = vatnumber.check_vies(tax_id)
logger.info("TAX_ID=%s RESULT=%s" % (tax_id, vies_result))
if tax_id and vies_result:
# Company is registered in VIES
# Charge back
return None
else:
return cls.EU_COUNTRIES_VAT[country_code]
except (WebFault, TransportError, stdnum.exceptions.InvalidComponent):
# If we could not connect to VIES or the VAT ID is incorrect
logger.exception("TAX_ID=%s" % (tax_id))
return cls.EU_COUNTRIES_VAT[country_code]
else:
# Company is not from EU
# VAT n/a
return None