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.
Side note: Code snippets (lines starting with >>>
) in this document get
tested as part of our development workflow. The following
initialization snippet tells you which demo project is being used in
this document.
>>> from lino import startup
>>> startup('lino_book.projects.cosi2.settings.doctests')
>>> from lino.api.doctest import *
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:- item_vat¶
No longer used. See
vat.item_vat
instead.
- needs_vat_id¶
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:
- normal¶
- subject¶
- intracom¶
Two additional regimes are defined in
lino_xl.lib.bevat
:- de¶
- lu¶
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
======= ============= ===========================
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 that defines how VAT is to be handled for a given invoice item.
Example data see
lino_xl.lib.vat.fixtures.euvatrates
.Database fields:
- seqno¶
The sequence number.
- country¶
- vat_class¶
- vat_regime¶
The regime for which this rule applies.
Pointer to
VatRegimes
.
- rate¶
The VAT rate to be applied. Note that a VAT rate of 20 percent is stored as 0.20 (not 20).
- vat_account¶
The general account where VAT is to be booked.
- vat_returnable¶
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.
- vat_returnable_account¶
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.
- class lino_xl.lib.vat.VatRules¶
The table of all
VatRule
objects.This table is accessible via
.>>> show_menu_path(vat.VatRules, language='en') Explorer --> VAT --> VAT rules
This table is filled by the national VAT module.
- get_vat_rule(cls, vat_area, 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 that matches.If no rule matches, Lino raises an exception unless a keyword argument default is given.
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.
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 isVatAccountInvoice
.
- class lino_xl.lib.vat.PrintableInvoicesByJournal¶
Purchase journal
- class lino_xl.lib.vat.ItemsByInvoice¶
- class lino_xl.lib.vat.VouchersByPartner¶
Utilites¶
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)
121.0
>>> remove_vat(121, 21)
100.0
>>> add_vat(10, 21)
12.1
>>> add_vat(1, 21)
1.21
>>> remove_vat(100, 20)
83.33333333333334
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
andPurchasesByDeclaration
Intracom sales and purchases¶
The plugin defines two reports accessible via the
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
andIntracomPurchases
.
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
andvat_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.- vat_id¶
The VAT id used to identify this business partner.
Lino verifies validity based on the partner’s
country
field.
- vat_regime¶
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
andtotal_vat
and some related behaviour.Used for both the voucher (
VatDocument
) and for each item of the voucher (VatItemBase
).- total_incl¶
The amount VAT included.
- total_base¶
The amount VAT excluded.
- total_vat¶
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 attributeedit_totals
to False.- get_trade_type()¶
Subclasses of VatTotal must implement this method.
- get_vat_rule()¶
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.
- total_base_changed()¶
Called when user has edited the
total_base
field. If total_base has been set to blank, then Lino fills it usingreset_totals()
. If user has entered a value, computetotal_vat
andtotal_incl
from this value using the vat rate. If there is no VatRule,total_incl
andtotal_vat
are set to None.If there are rounding differences,
total_vat
will get them.
- total_vat_changed()¶
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, computetotal_incl
. If there is no VatRule, total_incl is set to None.
- total_incl_changed()¶
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, computetotal_base
andtotal_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
andPayable
:
- 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
andlino_xl.lib.ana.AnaAccountInvoice
).Models that inherit this mixin can set the following class attribute:
- edit_totals¶
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.
- xml_file_template¶
The template to use for generating an XML file from this voucher.
Default value is
"vat/peppol-ubl.xml"
.
- xml_file_name¶
The name of the XML file to generate from this voucher.
This is a template for the string
format()
method. The nameself
refers to the database object for which the XML file is being generated.Default value is
"{self.journal.ref}/{self.id}.xml"
.
Inherits the following database fields from
lino_xl.lib.contacts.PartnerRelated
:- partner¶
Inherits the following database fields from
VatTotal
:- total_base¶
- total_vat¶
- total_incl¶
Adds the following database fields:
- project¶
Pointer to a
lino_xl.lib.accounting.Plugin.project_model
.
- items_edited¶
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 topartner
andvat_regime
andtotal_incl
.
- vat_regime¶
The VAT regime to be used in this document.
A pointer to
VatRegimes
.
Adds an action:
- compute_sums¶
Calls
ComputeSums
for this document.
Defines the following methods:
- get_vat_subtotals(self)¶
Yield an iteration of (category, rate, base, vat) for every VAT category and rate occurring in this voucher.
This method is used by the
vat/peppol-ubl.xml
template.Tested usage examples in bevat : Belgian VAT declarations, bevats : Simplified Belgian VAT declarations and eevat : Estonian VAT declarations.
- 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
).- vat_class¶
The VAT class to use on this voucher item.
- get_peppol_vat_category(self)¶
Return the PEPPOL category to be applied for this voucher item.
- get_vat_rule(self, tt)¶
Return the VatRule that 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
. ExtendsVatItemBase
by addingunit_price
andqty
.Abstract Base class for
lino_xl.lib.trading.InvoiceItem
andlino_xl.lib.trading.OrderItem
, i.e. invoice items with unit prices and quantities.- unit_price¶
The unit price for this item.
- qty¶
The quantity of units of the product for by this item.
Changing the
unit_price
ot theqty
will automatically reset the total amount of this item: the value unit_price * qty will be stored intotal_incl
ifvat.item_vat
is True, otherwise intotal_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
- accounting_period¶
- intracom_statement_iterator()¶
Yield a list of
lino_xl.lib.contacts.Partner
objects, annotated with a fieldtotal_base
that contains the sum of intra-community sales operations with this partner during the declared period range.Usage example in Intra-community clients.
- get_payable_sums_dict()¶
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
- editable¶
Whether the value of this field is to be manually entered by the end user.
Most fields are not editable, i.e. computed.
- both_dc¶
Whether the value of this field is to be manually entered by the end user.
- fieldnames¶
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.
- vat_regimes¶
- vat_classes¶
- vat_columns¶
- exclude_vat_regimes¶
- exclude_vat_classes¶
- exclude_vat_columns¶
- is_payable¶
Whether the value of this field represents an amount to be paid to the tax office.
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',
'SK'}
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):
dd.plugins.vat.eu_country_codes.add("GB")
if dd.today() > datetime.date(2025, 4, 11):
dd.plugins.vat.eu_country_codes.remove("BE")
The isocode
fields in your
countries.Countries
table must
match the codes specified in the eu_country_codes
plugin attribute.
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.
- vat.eu_country_codes¶
A set of ISO codes that are to be considered part of the EU. See Who is member of the European Union?.
- vat.default_vat_regime¶
The default VAT regime. If this is specified as a string, Lino will resolve it at startup into an item of
VatRegimes
.
- vat.default_vat_class¶
The default VAT class. If this is specified as a string, Lino will resolve it at startup into an item of
VatClasses
.
- vat.declaration_plugin¶
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.
- vat.item_vat¶
Whether item prices in trade documents are meant VAT included.
- vat.use_online_check¶
Whether to verify VAT numbers online using the VIES service of the EU. See VAT number validation using VIES.
Configuration files¶
- vat/peppol-ubl.xml¶
This template file is derived from the
base-example.xml
file at https://github.com/OpenPEPPOL/peppol-bis-invoice-3/blob/master/rules/examples/base-example.xml