Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More
Lino and Peppol (eInvoicing)¶
Lino supports receiving and sending digital invoices via the Peppol network as required by companies in Belgium from January 2026.
This document explain a few general things about Peppol in our words. Implementation details are in ibanity : Peppol access via Ibanity.
Note
Code snippets in this document (lines starting with >>>
) get tested
as part of our development workflow. The following
initialization snippet tells you which demo project is being used.
>>> from lino_book.projects.cosi1.startup import *
Generating Peppol XML files¶
Lino has two database models that represent a Peppol document:
VatProductInvoice
and
VatAccountInvoice
. The former is
usually used for sales and the latter for purchase.
>>> for m in rt.models_by_base(jinja.XMLMaker):
... if m.xml_file_template:
... print(full_model_name(m), m.xml_file_template, m.xml_validator_file)
...
trading.VatProductInvoice vat/peppol-ubl.xml .../lino_xl/lib/vat/XSD/PEPPOL-EN16931-UBL.sch
vat.VatAccountInvoice vat/peppol-ubl.xml .../lino_xl/lib/vat/XSD/PEPPOL-EN16931-UBL.sch
In order to create the XML of a Peppol document, Lino simply parses the
vat/peppol-ubl.xml
template.
Lino currrently does not validate Peppol documents. See Schematron validation.
You can manually validate the generated XML file for example on ecosio.org.
Peppol code lists¶
There is a register of OpenPeppol eDEC Code Lists
Electronic Address Scheme¶
One of the challenges of Peppol was how to identify your business partners across national borders. Peppol suggests a standard for international identification of legal persons.
Peppol does this in two steps: first you specify one of the recognized official registries of identification codes. Peppol knows a few hundred of them. These are called “Electronic Address Schemes” or . Let’s call them EAS.
- EAS¶
Abbreviation for “Electronic Address Scheme”. A recognized official registry of identification codes.
Some documents call them “Participant Identifier Schemes”: example)
Some examples of EAS codes:
9925 : Belgium VAT number
0208 : Belgium Company number
9931 : Estonian VAT number
It seems that EAS 9925 is a subset of EAS 0208: every party subject to VAT in Belgium also as a company number, which is the same as its VAT number. Hence normal companies should be in both registers, while companies like insurances and some non-profit organizations (who are not subject to VAT and hence have no VAT number) will be only in EAS 0208.
The commondata.peppolcodes
module defines a dict COUNTRY2SCHEME
,
which maps country codes to the EAS number of the VAT office of that
country. Lino uses this when generating a Peppol XML file for outbound
documents.
>>> from commondata.peppolcodes import COUNTRY2SCHEME
>>> peppol_countries = set(COUNTRY2SCHEME.keys())
The list contains some countries that are not part of the European Union
(according to lino_xl.lib.vat.eu_country_codes
):
>>> sorted(peppol_countries - dd.plugins.vat.eu_country_codes)
['AD', 'AL', 'BA', 'CH', 'GB', 'LI', 'MC', 'ME', 'MK', 'NO', 'RS', 'SM', 'TR', 'VA', 'international']
TODO: Denmark is currently not in our list because it doesn’t have a registry scheme that ends with “VAT”.
>>> dd.plugins.vat.eu_country_codes - peppol_countries
{'DK'}
The parties of an invoice¶
Seller : the partner who sells. Also called supplier, provider. See cac:AccountingSupplierParty
Buyer : the partner who buys. Also called invoicee, recipient, customer. See cac:AccountingCustomerParty
Payee : the partner who receives the payment. Shall be used when the Payee is different from the Seller. We currently don’t use this element
<cac:PayeeParty>
.Tax representative :
<cac:TaxRepresentativeParty>
: not mandatory and we don’t know what it is used for.
Both the seller and the buyer of an invoice contain a single mandatory element cac:Party, which contains a mandatory element cbc:EndpointID, which identifies the party’s electronic address.
This EndpointID
element contains the identification number as a textual
value, and must have an attribute schemeID
, which refers to the EAS
code to use when looking up the identification number.
The VAT category¶
The <cac:ClassifiedTaxCategory>
element contains “a group of business terms
providing information about the VAT applicable for the goods and services
invoiced on the invoice or the invoice line.”
<cbc:ID>
: The VAT category code for the invoiced item.<cbc:Percent>
: The applied VAT rate. A number, potentially with decimal positions<cac:TaxScheme>
: a mysterious but mandatory element. According to our references it must contain exactly one child element<cbc:ID>VAT</cbc:ID>
, where the word “VAT” seems to mean that the seller is identified using their VAT identifier. We don’t know whether it may contain other values.
A good introduction into why we have all these categories and rates is here: VAT Rates in Europe 2024
- VAT category¶
An alphabetic code defined as part of Peppol in the UNCL5305 code list
This list specifies the allowed VAT categories:
AE
Vat Reverse Charge
VAT is levied from the buyer.
E
Exempt from Tax
Taxes are not applicable.
S
Standard rate
The standard rate is applicable.
Z
Zero rated goods
The goods are at a zero rate.
H
Higher rate
A higher rate of duty or tax or fee is applicable.
AA
Lower rate
Tax rate is lower than standard rate.
Two functions are used in the vat/peppol-ubl.xml
template:
VatItemBase.get_peppol_vat_category()
returns the VAT category of a voucher item by interpreting the VAT regime and VAT class. This is used for the<cac:ClassifiedTaxCategory>
element.linox_xl.lib.VatVoucher.get_vat_subtotals()
returns an iterator of 4-tuples (categ, rate, total_base, total_vat). These are used for the<cac:TaxSubtotal>
elements of the voucher.
>>> dd.plugins.vat.declaration_plugin
'lino_xl.lib.bevat'
>>> rows = []
>>> coll = collections.OrderedDict()
>>> for obj in trading.InvoiceItem.objects.all():
... k = (obj.vat_class, obj.voucher.vat_regime, obj.get_peppol_vat_category())
... if k not in coll:
... coll[k] = obj
>>> for k, obj in coll.items():
... vat_class, vat_regime, pc = k
... rows.append([vat_class.name, vat_regime.name, pc, obj])
>>> headers = ["Class", "Regime", "Cat.", "Line"]
>>> print(rstgen.table(headers, rows))
========== ========== ====== ===============
Class Regime Cat. Line
---------- ---------- ------ ---------------
services subject S SLS 1/2014#1
services intracom AE SLS 2/2014#1
reduced subject AA SLS 6/2014#1
exempt subject Z SLS 6/2014#2
reduced intracom AE SLS 11/2014#2
exempt intracom AE SLS 12/2014#1
services normal S SLS 14/2014#1
reduced normal AA SLS 17/2014#1
exempt normal Z SLS 17/2014#2
========== ========== ====== ===============
>>> obj = trading.VatProductInvoice.objects.get(pk=111)
>>> print(f"{obj} {obj.total_base} {obj.total_vat} {obj.total_incl}")
SLS 6/2014 1110.16 203.87 1314.03
>>> round(1110.16 + 203.87, 2)
1314.03
>>> headers = ['Cat', "Rate", "Base", "VAT"]
>>> rows = []
>>> for cat, rate, total_base, total_vat in obj.get_vat_subtotals():
... rows.append([cat, rate, total_base, total_vat])
>>> print(rstgen.table(headers, rows))
===== ====== ======== ========
Cat Rate Base VAT
----- ------ -------- --------
AA 0.12 299.00 35.88
Z 0 11.20 0.00
S 0.21 799.96 167.99
===== ====== ======== ========
>>> round(299 + 11.20 + 799.96, 2)
1110.16
>>> round(35.88 + 0 + 167.99, 2)
203.87
Some error messages and how we fix them¶
Error message: A buyer reference or purchase order reference MUST be provided.
The specs about cbc:BuyerReference say indeed that “An invoice must have buyer reference or purchase order reference (BT-13).”
🡒 When
your_ref
is empty, Lino writes “not specified”.
Amounts¶
Vocabulary:
Line net amount: Invoiced quantity * Unit Gross Price
Allowances : discount or similar amount to subtract from the net amount
Charges : some fee, tax (other than VAT) or similar amount to add to the net amount
Line extension amount : Net amount + Charges - Allowances.
An invoice must have exactly one cac:LegalMonetaryTotal element, which provides the monetary totals for the invoice. It can have the following children (each child at most once):
cbc:LineExtensionAmount: Sum of all invoice line amounts in the invoice, net of tax and settlement discounts, but inclusive of any applicable rounding amount.
cbc:TaxExclusiveAmount: total amount of the invoice without VAT.
cbc:TaxInclusiveAmount: total amount of the invoice with VAT.
cbc:ChargeTotalAmount: Sum of all charges in the invoice.
cbc:PrepaidAmount: Sum of amounts that have been paid in advance.
cbc:PayableRoundingAmount Amount to be added to the invoice total to round the amount to be paid.
‘cbc:PayableAmount <https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/cac-LegalMonetaryTotal/cbc-PayableAmount/>`__: outstanding amount that is requested to be paid
Amounts must be rounded to maximum 2 decimals.
Each amount element has a mandatory attribute currencyID
.
Sources of information¶
ec.europa.eu (European Commission):
peppol.eu (Copyright OpenPeppol AISBL):
OpenPeppol eDEC Specifications lists the specifications of the Peppol eDelivery network.
peppol.org (Copyright OpenPeppol AISBL):
other:
https://github.com/OpenPEPPOL/peppol-bis-invoice-3/blob/master/rules/examples/base-example.xml
2020-06-01 The European Commission updates the Electronic Address Scheme (EAS) code lists
My own blog entries:
Peppol jargon¶
Here are some Peppol-related terms and abbreviations and what they mean.
- Peppol¶
An international standard for exchanging structured electronic invoices and other business documents.
- Peppol document¶
A business document that can be exchanged via the Peppol network. Right now this means an invoice or credit note, either sales or purchase.
See also Generating Peppol XML files.
- Peppol network¶
The network of organizations who use the Peppol standard for doing their business communication.
- Access Point¶
An organization that provides connection to the Peppol network. Abbreviated AP.
- UBL¶
Universal Business Language
- BIS¶
Business Interoperability Specifications
- SML¶
Service Metadata Locator
https://docs.peppol.eu/edelivery/sml/PEPPOL-EDN-Service-Metadata-Locator-1.2.0-2021-05-13.pdf
- SMP¶
Service Metadata Publishing
- AS4¶
The only transport profile still in use.
“The AS4 technical specification [AS4] defines a secure and reliable messaging protocol. It can be used for message exchange in Business-to-Business (B2B), Administration-to-Administration (A2A), Administration-to-Business (A2B) and Business-to-Administration (B2A) contexts. AS4 messages can carry any number of payloads. Payloads may be structured or unstructured documents or data.” (ec.europa.eu)
- Hermes¶
A platform provided by the Belgian government where SMEs can sign in and communicate with the Peppol network. They can manually enter outbound documents (sales invoices and credit notes) to be sent to their customers, and they can view their inbound documents.