Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More

vat : Adding VAT (Value-added tax) functionality

The lino_xl.lib.vat plugin is used when the site operator has sales and purchase operations that are subject to value-added tax (VAT). Installing this plugin adds a vat_id field to every business partner. It also adds a voucher type for making VAT declarations. When using this plugin, you will probably also install one of the national VAT modules.

See also the end-user docs about this plugin: The vat plugin.

Lines starting with >>> in this document are code snippets that get tested as part of our development workflow.

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

Plugin configuration settings

A Lino site that uses this plugin will usually specify the national VAT module for their VAT declarations by setting the vat.declaration_plugin plugin attribute.

Here is a list of the plugin settings for this plugin.


A set of ISO codes that are to be considered part of the EU. See Who is member of the European Union?.


The default VAT regime. If this is specified as a string, Lino will resolve it at startup into an item of VatRegimes.


The default VAT class. If this is specified as a string, Lino will resolve it at startup into an item of VatClasses.


The plugin to use as your national VAT module.

See National VAT modules for a list of available plugins.

This may remain None in applications that don’t care about general accounting functionality.


Whether item prices in trade documents are meant VAT included.


Whether to verify VAT numbers online using the VIES service of the EU. See VAT number validation using VIES.

If this is True, install will install pyvat.

National VAT modules

A national VAT module is a normal Lino plugin that implements the VAT declaration and rules for a given country. It is activated by setting the vat.declaration_plugin plugin attribute.

Currently we have three national VAT modules:

Accounting applications to be used by site operators who don’t care about VAT might use lino_xl.lib.vatless instead (though this plugin might become deprecated). The modules lino_xl.lib.vatless and lino_xl.lib.vat can theoretically both be installed though obviously this wouldn’t make sense.

VAT regimes

>>> rt.show(vat.VatRegimes, language="en")
======= ======== ======== ========== ==============
 value   name     text     VAT area   Needs VAT id
------- -------- -------- ---------- --------------
 10      normal   Normal              No
======= ======== ======== ========== ==============
class lino_xl.lib.vat.VatRegime

Base class for the items of VatRegimes. Each VAT regime is an instance of this and has two properties:


In which VAT area this regime is available.


No longer used. See vat.item_vat instead.


Whether this VAT regime requires that partner to have a vat_id.

class lino_xl.lib.vat.VatRegimes

The global list of VAT regimes. Each item of this list is an instance of VatRegime.

Three VAT regimes are considered standard minimum:


Two additional regimes are defined in lino_xl.lib.bevat:


VAT classes

See also VAT classes.

>>> rt.show(vat.VatClasses, language="en")
======= ============= ===========================
 value   name          text
------- ------------- ---------------------------
 010     goods         Goods at normal VAT rate
 020     reduced       Goods at reduced VAT rate
 030     exempt        Goods exempt from VAT
 100     services      Services
 200     investments   Investments
 210     real_estate   Real estate
 220     vehicles      Vehicles
 300     vatless       Without VAT
======= ============= ===========================
class lino_xl.lib.vat.VatClasses

The global list of VAT classes.

Default classes are:


VAT rules

When no national VAT module is installed, we have only one default VAT rule with no condition and zero rate.

>>> rt.show(vat.VatRules, language="en")
| value | Description      |
| 1     | VAT rule 1:      |
|       | apply 0 %        |
|       | and book to None |
class lino_xl.lib.vat.VatRule

A rule which defines how VAT is to be handled for a given invoice item.

Example data see lino_xl.lib.vat.fixtures.euvatrates.

Database fields:


The sequence number.


The regime for which this rule applies.

Pointer to VatRegimes.


The VAT rate to be applied. Note that a VAT rate of 20 percent is stored as 0.20 (not 20).


The general account where VAT is to be booked.


Whether VAT is returnable. Returnable VAT does not increase the total amount of the voucher, it causes an additional movement into the vat_returnable_account. See Returnable VAT.


Where to book returnable VAT. If this field is empty and vat_returnable is True, then VAT will be added to the base account. See Returnable VAT.

get_vat_rule(cls, trade_type, vat_regime,
vat_class=None, country=None, date=None)

Return the VAT rule to be applied for the given criteria.

Lino loops through all rules (ordered by their seqno) and returns the first object which matches.

class lino_xl.lib.vat.VatRules

The table of all VatRule objects.

This table is accessible via Explorer ‣ VAT ‣ VAT rules.

>>> show_menu_path(vat.VatRules, language='en')
Explorer --> VAT --> VAT rules

This table is filled by

VAT areas

The VatAreas choice list contains the list of available VAT areas.

>>> rt.show(vat.VatAreas, language="en")
======= =============== ===============
 value   name            text
------- --------------- ---------------
 10      national        National
 20      eu              EU
 30      international   International
======= =============== ===============

The plugin property vat.eu_country_codes defines which countries are considered part of the EU.

Available VAT regimes

The declaration plugin controls which VAT regimes are available for selection on a partner or on a voucher.

The available VAT regimes vary depending on which VAT declaration plugin is installed. When no declaration module is installed, we have only one default regime.

The list of available VAT regimes for a partner depends on the VAT area and on whether the partner has a VAT id or not.

get_vat_regime_choices(country=None, vat_id=None):

Used for the choosers of the vat_regime field of a partner and a voucher.

class lino_xl.lib.vat.VatAreas

The global list of VAT areas.

classmethod get_for_country(cls, country)

Return the VAT area for this country.

Why differentiate between VAT regimes and VAT classes?

You might ask why we use two sets of categories for specifying the VAT rate. Some other accounting programs do not have two different categories for the subtle difference between “exempt from VAT” and “VAT 0%”, they have just a category “VAT rate” which you can set per invoice item (and a default value per provider).

The problem with this simplified vision is that at least for Belgian VAT declarations there is a big difference between having 0% of VAT because the provider is a private person and having 0% of VAT because you are buying post stamps or flight tickets (which are exempt from VAT).

Another thing to consider is that in Lino we want to be able to have partners who are both a provider and a customer. Their VAT regime remains the same for both trade types (sales and purchase) while the default VAT class to use in invoice items depends on the account or the product.

Account invoices

The lino_xl.lib.vat plugin provides the VatAccountInvoice voucher type. It is implemented in two database models:

class lino_xl.lib.vat.VatAccountInvoice

Django model for storing account vouchers.

A VAT-capable of account voucher. It is one of the most basic voucher types, which can be used even in accounting applications that don’t have lino_xl.lib.trading.

class lino_xl.lib.vat.InvoiceItem

Django model for representing items of an account voucher.

There are several views:

class lino_xl.lib.vat.Invoices

The table of all VatAccountInvoice objects.

class lino_xl.lib.vat.InvoicesByJournal

Shows all invoices of a given journal. Works only when the voucher_type of the specified master instance is VatAccountInvoice.

class lino_xl.lib.vat.PrintableInvoicesByJournal

Purchase journal

class lino_xl.lib.vat.InvoiceDetail

The detail layout used by Invoices.

class lino_xl.lib.vat.ItemsByInvoice
class lino_xl.lib.vat.VouchersByPartner


This plugin contains some utility functions.

lino_xl.lib.vat.add_vat(base, rate)

Add to the given base amount base the VAT of rate rate.

lino_xl.lib.vat.remove_vat(incl, rate)

Remove from the given amount incl the VAT of rate rate.

Code examples:

>>> from lino_xl.lib.vat.utils import add_vat, remove_vat
>>> add_vat(100, 21)
>>> remove_vat(121, 21)
>>> add_vat(10, 21)
>>> add_vat(1, 21)
>>> remove_vat(100, 20)

Showing the invoices covered by a VAT declaration

The plugin defines two tables that show the invoices covered by a VAT declaration, IOW the invoices that have contributed to the numbers in the declaration.

class lino_xl.lib.vat.SalesByDeclaration

Show a list of all sales invoices whose VAT regime is Intra-Community.

class lino_xl.lib.vat.PurchasesByDeclaration

Show a list of all purchase invoices whose VAT regime is Intra-Community.

class lino_xl.lib.vat.VatInvoices

Common base class for SalesByDeclaration and PurchasesByDeclaration

Intracom sales and purchases

The plugin defines two reports accessible via the Reports ‣ Accounting menu and integrated in the printout of a VAT declaration:

class lino_xl.lib.vat.IntracomSales

Show a list of all sales invoices having VAT regime is Intra-Community.

class lino_xl.lib.vat.IntracomPurchases

Show a list of all purchase invoices having VAT regime is Intra-Community.

class lino_xl.lib.vat.IntracomInvoices

Common base class for IntracomSales and IntracomPurchases.

These reports are empty when you have no national VAT module installed:

>>> rt.show(vat.IntracomSales, language='en')
No data to display
>>> rt.show(vat.IntracomPurchases, language='en')
No data to display

See eevat : Estonian VAT declarations for a non-empty example.

A data table on an abstract database model

IntracomInvoices is an example of a data table on an abstract database model. In Lino Così there are two database models that can contain intracom invoices: VatProductInvoice and VatAccountInvoice.

>>> vat.IntracomPurchases.model
<class 'lino_xl.lib.vat.mixins.VatVoucher'>
>>> list(rt.models_by_base(vat.IntracomPurchases.model))
[<class 'lino_xl.lib.trading.models.VatProductInvoice'>, <class 'lino_xl.lib.vat.models.VatAccountInvoice'>]

Model mixins

class lino_xl.lib.vat.VatSubjectable

Model mixin that defines the database fields vat_regime and vat_id, and adds related behaviour.

This is inherited e.g. by lino_xl.lib.contacts.Partner.

This mixin does nothing when lino_xl.lib.vat is not installed.


The VAT id used to identify this business partner.

Lino verifies validity based on the partner’s country field.


The default VAT regime to use on invoices for this partner.

class lino_xl.lib.vat.VatTotal

Model mixin that defines the database fields total_incl, total_base and total_vat and some related behaviour.

Used for both the voucher (VatDocument) and for each item of the voucher (VatItemBase).


The amount VAT included.


The amount VAT excluded.


The amount of VAT.

All three total fields are lino.core.fields.PriceField instances.

The fields are editable by default, but implementing models can call lino.core.fields.update_field() to change this behaviour. A model that sets all fields to non-editable should also set the class attribute edit_totals to False.


Subclasses of VatTotal must implement this method.


Return the VAT rule for this voucher or voucher item. Called when user edits a total field in the document header when edit_totals is True.


Called when user has edited the total_base field. If total_base has been set to blank, then Lino fills it using reset_totals(). If user has entered a value, compute total_vat and total_incl from this value using the vat rate. If there is no VatRule, total_incl and total_vat are set to None.

If there are rounding differences, total_vat will get them.


Called when user has edited the total_vat field. If it has been set to blank, then Lino fills it using reset_totals(). If user has entered a value, compute total_incl. If there is no VatRule, total_incl is set to None.


Called when user has edited the total_incl field. If total_incl has been set to blank, then Lino fills it using reset_totals(). If user enters a value, compute total_base and total_vat from this value using the vat rate. If there is no VatRule, total_incl should be disabled, so this method will never be called.

If there are rounding differences, total_vat will get them.

class lino_xl.lib.vat.VatVoucher

Model mixin for vouchers that mention VAT.

Inhertis from VatDocument, PaymentRelated and Payable:

class lino_xl.lib.vat.VatDocument

Abstract base class for invoices, offers and other vouchers.

Inherited by VatAccountInvoice as well as in other plugins (e.g. lino_xl.lib.trading.VatProductInvoice and lino_xl.lib.ana.AnaAccountInvoice).

Models that inherit this mixin can set the following class attribute:


Whether total amounts of the voucher are being edited by the end user.

It this is False, Lino fills them as the sum of the vouchers items’ amounts.

The total fields of an invoice are not automatically updated each time an item is modified. Users must click the Σ button (“Compute sums”) or the Save or the Register button to update the invoice’s totals.

Inherits the following database fields from lino_xl.lib.contacts.PartnerRelated:


Inherits the following database fields from VatTotal:


Adds the following database fields:


Pointer to a lino_xl.lib.accounting.Plugin.project_model.


An automatically managed boolean field which says whether the user has manually edited the items of this document. If this is False and edit_totals is True, Lino will automatically update the only invoice item according to partner and vat_regime and total_incl.


The VAT regime to be used in this document.

A pointer to VatRegimes.

Adds an action:


Calls ComputeSums for this document.

class lino_xl.lib.vat.ComputeSums

Compute the sum fields of a VatDocument based on its items.

Represented by a “Σ” button.

class lino_xl.lib.vat.VatItemBase

Model mixin for items of a VatDocument.

Abstract Base class for lino_xl.lib.accounting.InvoiceItem, i.e. the lines of invoices without unit prices and quantities.

Subclasses must define a field called “voucher” which must be a ForeignKey with related_name=”items” to the “owning document”, which in turn must be a subclass of VatDocument).


The VAT class to use on this voucher item.

get_vat_rule(self, tt)

Return the VatRule which applies for this item.

tt is the trade type (which is the same for each item of a voucher, that’s why we expect the caller to provide it).

This basically calls the class method VatRule.get_vat_rule() with appropriate arguments.

When selling certain products (“automated digital services”) in the EU, you have to pay VAT in the buyer’s country at that country’s VAT rate. See e.g. How can I comply with VAT obligations?.

TODO: Add a new attribute VatClass.buyers_country or a checkbox Product.buyers_country or some other way to specify this.

class lino_xl.lib.vat.QtyVatItemBase

Model mixin for items of a VatTotal. Extends VatItemBase by adding unit_price and qty.

Abstract Base class for lino_xl.lib.trading.InvoiceItem and lino_xl.lib.trading.OrderItem, i.e. invoice items with unit prices and quantities.


The unit price for this item.


The quantity of units of the product for by this item.

Changing the unit_price ot the qty will automatically reset the total amount of this item: the value unit_price * qty will be stored in total_incl if vat.item_vat is True, otherwise in total_base.

VAT columns

class lino_xl.lib.vat.VatColumns

The list of VAT columns available on this site.

The VAT column of a ledger account indicates where the movements on this account are to be collected in VAT declarations.

VAT declarations

class lino_xl.lib.vat.VatDeclaration

Abstract base class for models that represent a VAT declaration.

Inherits from lino_xl.lib.sepa.Payable lino_xl.lib.accounting.Voucher lino_xl.lib.excerpts.Certifiable lino_xl.lib.accounting.PeriodRange


Yield a list of lino_xl.lib.contacts.Partner objects, annotated with a field total_base that contains the sum of intra-community sales operations with this partner during the declared period range.

Usage example in Intra-community clients.


Implements lino_xl.lib.sepa.Payable.get_payable_sums_dict().

As a side effect this updates values in the computed fields of this declaration.

Declaration fields

Defining the declaration fields is responsibility of each national VAT module. But every individual field in every VAT declaration of every country is an instance of one of the following three classes:

class lino_xl.lib.vat.MvtDeclarationField

A declaration field to be computed by analyzing the ledger movements.

class lino_xl.lib.vat.WritableDeclarationField

A declaration field to be entered manually by the end user.

class lino_xl.lib.vat.SumDeclarationField

A declaration field that computes the sum of its observed fields.

All these three declaration field classes have a common ancestor DeclarationField.

class lino_xl.lib.vat.DeclarationField

Base class for all declaration fields.

It is not instantiated directly but by using one of its subclasses


Whether the value of this field is to be manually entered by the end user.

Most fields are not editable, i.e. computed.


Whether the value of this field is to be manually entered by the end user.


An optional space-separated list of names of observed fields, i.e. other declaration fields to be observed by this field. If a field name is prefixed by a “-”, the observed field will additionally be inverted.

This is used only by sum fields. The values of all observed fields will be added, except inverted fields whose value will be subtracted.

Note that the booking direction (D or C) of the observed fields is ignored when computing the sum.


Whether the value of this field represents an amount to be paid to the tax office.

class lino_xl.lib.vat.DeclarationFieldsBase

Returnable VAT

The vat_returnable_account attribute tells Lino whether this is considered returnable VAT.

The VAT columns checker

class lino_xl.lib.vat.VatColumnsChecker

Check VAT columns configuration.

This is an unbound data checker (lino.modlib.checkdata.Checker.model is None), i.e. the messages aren’t bound to a particular database object.

Generating fictive VAT numbers

Note about #5542 (Two VAT doctests fail because generated VAT numbers differ).

The demo fixture of the vat plugin assigns a fictive (but syntactically valid) VAT number to each business partner.

The seed() method initializes the random number generator, and if you use the same seed value twice you will get the same random number twice. The following code verifies this. It always passes on my machine. It will always return the same sequence of numbers because we seed the random generator with a hard-coded integer. Does it test pass on other machines as well? Yes (at least on GitLab)

>>> import random
>>> random.seed(1)
>>> print([random.randint(111, 999) for i in range(10)])
[248, 693, 978, 932, 893, 175, 372, 231, 618, 890]

Who is member of the European Union?

The plugin attribute eu_country_codes is a set of ISO codes that are to be considered part of the EU. :

>>> pprint(dd.plugins.vat.eu_country_codes, compact=True)
{'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR',
 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PO', 'PT', 'RO', 'SE', 'SI',

This is used to define the VAT area of a partner, which in turn influences the available VAT regimes. See lino_xl.lib.vat.VatAreas.

When a member state leaves or joins the EU (and you have partners there), you can either update your Lino (we plan to keep this list up to date), or you can change it locally. For example in your layouts_module you may write code like this:

if dd.today() > datetime.date(2025, 4, 11):

if dd.today() > datetime.date(2025, 4, 11):

The isocode fields in your countries.Countries table must match the codes specified in the eu_country_codes plugin attribute.