Welcome | Get started | Dive into Lino | Contribute | Topics | Reference | More

VAT number validation using VIES

The lino_xl.lib.vat plugin can use the VIES (VAT Information Exchange System) service of the European Union for validating VAT numbers stored in the VatSubjectable.vat_id field.

This page is a tested document and the following instructions are used for initialization:

>>> from lino import startup
>>> startup('lino_book.projects.pierre.settings.doctests')
>>> from lino.api.doctest import *

The VAT number

Every business partner can have a VAT identification number (VATIN).

The following function validate_vat_id is used in this document to demonstrate the capabilities and roles of Lino in managing a VAT number.

>>> from django.core.exceptions import ValidationError
>>> def validate_vat_id(partner):
...     try:
...         partner.full_clean()
...         print(partner.vat_id)
...     except ValidationError as e:
...         print("ValidationError:\n" + e.message)

A partner who has a vat_id (VAT number) field, by default implementation, also has a vid_manager property, which is essentially a pointer to an instance of a VatNumberManager that validates an inserted VAT number and does some other things that we will see below.

Below example displays an error message for an invalid VAT number.

>>> Country = countries.Country
>>> Company = contacts.Company
>>> belgium = Country.objects.get(isocode="BE")
>>> obj = Company(name="foo", country=belgium)
>>> obj.vat_id = "5468523548"
>>> validate_vat_id(obj)
ValidationError:
Modulo 97 check failed for VAT identification number in BE

The validation error message is a little more self explanatory when the inserted VAT number does not match any format explained here. An example:

>>> obj = Company(name="bar Ltd.", vat_id="NL56465")
>>> validate_vat_id(obj)
ValidationError:
You have entered an invalid VAT identification number.
The general format follows: NLxxxxxxxxxBxx
Where each 'x' is a digit.

When the country field is empty, the ISO 3166-1-alpha-2 country code must be given at the beginning of the VAT identification number otherwise lino will treat the VAT identification number as a dummy and will not do any validation check. The following example shows two possibilities where in first case there's no country associated with the partner but has a valid vat_id format and in later case there's no known country associated with the partner not in country field neither in the vat_id. And differences are distinguishable from the returned VAT number, where the valid VAT number returns are formatted with dots (.) and a whitespace.

>>> obj = Company(name="foo Ltd.", vat_id="EE100041561")
>>> validate_vat_id(obj)
EE 100.041.561
>>> obj = Company(name="foo Inc.", vat_id="100041561")
>>> validate_vat_id(obj)
100041561

If the partner's country is different from the country code given in the VAT number, Lino will also raise a ValidationError.

>>> estonia = Country(isocode="EE")
>>> obj = Company(name="foo Inc.", country=estonia, vat_id="BE 100041561")
>>> validate_vat_id(obj)
ValidationError:
Country code (EE) does not match with BE.

By default a VatNumberManager instance includes the validation for the countries with ISO code shown in the following code output.

>>> from lino_xl.lib.vat.choicelists import VAT_ORIGINS
>>> list(VAT_ORIGINS.keys())
['BE', 'AT', 'NL', 'HR', 'DK', 'EE', 'FI', 'FR', 'DE', 'EL', 'HU', 'IT', 'LV', 'LT', 'LU']

If the given country code is not included in the above example, Lino will accept any reasonable format of the VAT number as valid.

>>> obj = Company(name="HyD Inc.", vat_id="BD 545454656484b6563")
>>> validate_vat_id(obj)
BD 54.545.465.648.4B6.563

Adding a VatOrigin:

>>> from lino_xl.lib.vat.choicelists import VAT_ORIGINS, VatOrigin
>>> vo = VatOrigin('CY', 8, dummy_suffix="L",
...     pattern="^(?P<country_code>CY) (?P<number>[0-9]{8})(?P<suffix>L)$")
>>> VAT_ORIGINS[vo.country_code] = vo
>>> validate_vat_id(Company(name="HyD Inc.", vat_id="CY 1234567890"))
ValidationError:
You have entered an invalid VAT identification number.
The general format follows: CYxxxxxxxxL
Where each 'x' is a digit.
>>> validate_vat_id(Company(name="HyD Inc.", vat_id="CY12345678L"))
CY 12.345.678L

When vat.use_online_check is True, the checkdata command will do online verification of every VAT id.

class lino_xl.lib.vat.VatIdChecker

Validate VAT id from online registry.

The demo database contains some fictive VAT ids that are syntactically correct but simply do not exist in reality.

Until 2022-12-28, Lino detected this as a data problem because vat.use_online_check was True in lino_book.projects.pierre. That's why we had a series of corresponding data problems. The following test is now skipped because vat.use_online_check caused the test suite to break when the VIES server was unavailable during inv prep.

>>> chk = checkdata.Checkers.get_by_value('vat.VatIdChecker')
>>> rt.show(checkdata.MessagesByChecker, chk)
... 
+-------------------------+---------------------------------+------------------------------------------------------+
| Utilisateur responsable | Database object                 | Texte du message                                     |
+=========================+=================================+======================================================+
| Robin Rood              | *Bäckerei Ausdemwald*           | Invalid VAT identification number BE 7088.996.857:   |
|                         |                                 | Not registered in BE.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Bäckerei Mießen*               | Invalid VAT identification number BE 4685.739.309:   |
|                         |                                 | Not registered in BE.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Bäckerei Schmitz*              | Invalid VAT identification number BE 4181.505.692:   |
|                         |                                 | Not registered in BE.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Garage Mergelsberg*            | Invalid VAT identification number BE 9045.438.159:   |
|                         |                                 | Not registered in BE.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Donderweer BV*                 | Invalid VAT identification number NL 220.876.686B01: |
|                         |                                 | Not registered in NL.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Van Achter NV*                 | Invalid VAT identification number NL 451.948.587B01: |
|                         |                                 | Not registered in NL.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Hans Flott & Co*               | Invalid VAT identification number DE 143.956.862:    |
|                         |                                 | Not registered in DE.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Bernd Brechts Bücherladen*     | Invalid VAT identification number DE 135.079.295:    |
|                         |                                 | Not registered in DE.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Reinhards Baumschule*          | Invalid VAT identification number DE 138.433.397:    |
|                         |                                 | Not registered in DE.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Moulin Rouge*                  | Invalid VAT identification number FR 86.915.334.564: |
|                         |                                 | Not registered in FR.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Auto École Verte*              | Invalid VAT identification number FR 66.435.589.280: |
|                         |                                 | Not registered in FR.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Maksu- ja Tolliamet*           | Invalid VAT identification number EE 848.217.541:    |
|                         |                                 | Not registered in EE.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Electrabel Customer Solutions* | Invalid VAT identification number BE 4018.258.949:   |
|                         |                                 | Not registered in BE.                                |
+-------------------------+---------------------------------+------------------------------------------------------+
| Robin Rood              | *Leffin Electronics*            | Invalid VAT identification number BE 0650.238.114:   |
|                         |                                 | Not registered in BE.                                |
+-------------------------+---------------------------------+------------------------------------------------------+