Skip to content

Quickstart

This page builds one invoice, derives its totals, writes it as UBL and CII, validates it against the official rules, and reads it back. The seller, buyer and identifiers are synthetic.

1. Build a draft

An InvoiceDraft is an invoice without the amounts that can be derived: the line net amounts (BT-131), the document totals (BG-22) and the VAT breakdown (BG-23). Amounts, quantities, prices and rates are Decimal; a float is rejected.

import datetime
from decimal import Decimal

from euinvoice import calc, parse, profiles, to_xml
from euinvoice.model import (
    Buyer,
    BuyerPostalAddress,
    CreditTransfer,
    InvoiceDraft,
    ItemInformation,
    LineDraft,
    LineVatInformation,
    PaymentInstructions,
    PriceDetails,
    ProcessControl,
    Seller,
    SellerPostalAddress,
)

draft = InvoiceDraft(
    number="2026-0001",  # BT-1
    issue_date=datetime.date(2026, 3, 31),  # BT-2
    type_code="380",  # BT-3, UNTDID 1001 (BR-CL-01); see Concepts
    currency_code="EUR",  # BT-5
    payment_due_date=datetime.date(2026, 4, 30),  # BT-9
    process_control=ProcessControl(specification_identifier="urn:cen.eu:en16931:2017"),  # BT-24
    seller=Seller(
        name="Seller Example GmbH",  # BT-27
        vat_identifier="ATU00000000",  # BT-31
        postal_address=SellerPostalAddress(city="Wien", post_code="1010", country_code="AT"),
    ),
    buyer=Buyer(
        name="Buyer Example GmbH",  # BT-44
        vat_identifier="ATU11111111",  # BT-48
        postal_address=BuyerPostalAddress(city="Graz", post_code="8010", country_code="AT"),
    ),
    payment_instructions=PaymentInstructions(
        payment_means_type_code="58",  # BT-81
        credit_transfers=(CreditTransfer(payment_account_identifier="DE02120300000000202051"),),  # BT-84
    ),
    lines=(
        LineDraft(
            identifier="1",  # BT-126
            invoiced_quantity=Decimal("10"),  # BT-129
            invoiced_quantity_unit_code="HUR",  # BT-130, UN/ECE Rec 20/21 (BR-CL-23)
            price_details=PriceDetails(item_net_price=Decimal("95.00")),  # BT-146
            vat_information=LineVatInformation(category_code="S", rate=Decimal("20")),  # BT-151, BT-152
            item=ItemInformation(name="Consulting (hours)"),  # BT-153
        ),
        LineDraft(
            identifier="2",
            invoiced_quantity=Decimal("1"),
            invoiced_quantity_unit_code="C62",
            price_details=PriceDetails(item_net_price=Decimal("25.00")),
            vat_information=LineVatInformation(category_code="S", rate=Decimal("20")),
            item=ItemInformation(name="Hosting, March 2026"),
        ),
    ),
)

2. Derive the totals

calc.complete derives every line net amount, the document totals and one VAT breakdown per VAT category and rate, using the EN 16931 rules (BR-CO-10 … BR-CO-17) and rounding money to two decimals, half up.

>>> invoice = calc.complete(draft)
>>> [line.net_amount for line in invoice.lines]
[Decimal('950.00'), Decimal('25.00')]
>>> invoice.totals.total_without_vat, invoice.totals.total_vat, invoice.totals.amount_due
(Decimal('975.00'), Decimal('195.00'), Decimal('1170.00'))
>>> [(group.category_code, group.rate, group.taxable_amount, group.tax_amount) for group in invoice.vat_breakdown]
[('S', Decimal('20'), Decimal('975.00'), Decimal('195.00'))]
>>> calc.check(invoice)  # the CEN arithmetic and VAT category rules: no findings
()

3. Write XML

to_xml writes the invoice under a profile. Before writing, it runs the profile's pre-flight checks and calc.check, and raises PreflightError when the official rules would reject the document.

>>> ubl = to_xml(invoice, profile=profiles.EN16931, syntax="ubl")
>>> cii = to_xml(invoice, profile=profiles.EN16931, syntax="cii")
>>> ubl.decode().splitlines()[1][:60]
'<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd'
>>> parse(ubl) == parse(cii) == profiles.EN16931.prepare(invoice)  # both read back to the same model
True

4. Validate

Validation runs the official XSD and Schematron. It needs the [validate] extra and the fetched artifacts (see Validation).

>>> from euinvoice import validate
>>> validate(ubl).ok, validate(cii).ok
(True, True)