Invoice authoring API¶
The bidirectional CIUS-RO invoice models: flat document models with computed totals, the translated EN 16931 + CIUS-RO rule set, and the flat ⇄ UBL mapping. See the authoring guide for usage.
anafpy.efactura.authoring.models ¶
Bidirectional flat models for CIUS-RO electronic invoices.
One semantic model — EN 16931's — covers both UBL document types:
:class:InvoiceDocument authors and views an Invoice or a CreditNote
(:attr:~InvoiceDocument.kind picks the render target). The same models are
produced by :func:~.read.read_invoice and consumed by :func:~.build.build_invoice
/ :func:~.build.render_invoice, mirroring the e-Transport flat models.
Validation is two-tier:
- Construction enforces what a single model can know unconditionally: field
formats and lengths (the
BR-RO-L*limits), closed code lists (BR-CL-*), decimal budgets (BR-DEC-*), and local consistency (a period needs a start or an end, a VAT category pins its rate shape, ...). Data violating these is rejected by ANAF with certainty, so failing fast is data hygiene. - :func:
~.rules.validateruns the cross-aggregate EN 16931 / CIUS-RO rule set (totals arithmetic, VAT breakdown consistency, regime-dependent identifier requirements) and returns a findings report instead of raising.
Authoring and reading are the same models but not the same contract. Refusing
to build an invalid document is data hygiene; refusing to read one ANAF already
accepted is a bug — so :func:~.read.read_invoice validates through the
:data:_DERIVED_CONTEXT marker, which turns the consistency and sign checks
into no-ops while leaving coercion, defaults and normalisation in place. The
first tier above stays fatal in both directions — code lists, format patterns,
lengths, decimal budgets all mirror rules ANAF enforces fatally, so they cannot
fire on an accepted document; when one does, the mirrors have drifted, which is
the tripwire :attr:~anafpy.efactura.models.DownloadedMessage.view documents.
Totals and the VAT breakdown are computed by default from the lines,
allowances and charges (correct by construction); explicit values may be supplied
— e.g. when reproducing an upstream document — and are then checked against the
computed ones by :func:~.rules.validate.
Contact ¶
Bases: BaseModel
A contact point (BG-6 seller / BG-9 buyer).
PostalAddress ¶
Bases: BaseModel
A postal address (BG-5/8/12/15).
CIUS-RO requires the street and city everywhere an address appears
(BR-RO-080/090/140/150/180/200); the county (CountrySubentity) is
required for Romanian addresses and must be an ISO 3166-2:RO code
(BR-RO-110), with Bucharest city names drawn from the SECTOR list
(BR-RO-100) — "sector 3" is normalised to "SECTOR3".
ElectronicAddress ¶
Bases: BaseModel
An electronic delivery address (BT-34/49) with its mandatory scheme
(BR-62/63), e.g. ElectronicAddress(address="0000000000000", scheme="0088").
PartyIdentifier ¶
Bases: BaseModel
A party identifier (BT-29/46/60); the scheme, when given, is an ISO 6523 ICD code (BR-CL-10).
Party ¶
Bases: BaseModel
A buyer (BG-7); also the base of :class:Seller (BG-4).
name is the legal name (BT-27/44) and the address is mandatory
(BR-08/10). The VAT identifier must carry its ISO 3166 country prefix
(BR-CO-09; Greece may use EL, and the rule's own list also admits
1A for Kosovo — the one non-alphabetic prefix).
Payee ¶
Bases: BaseModel
The payee (BG-10), when different from the seller (BR-17).
TaxRepresentative ¶
Bases: BaseModel
The seller's tax representative (BG-11); the VAT identifier (BT-63) and postal address (BG-12) are mandatory (BR-56/19).
Period ¶
Bases: BaseModel
An invoicing (BG-14) or line (BG-26) period: at least one bound (BR-CO-19/20), end not before start (BR-29/30).
DeliveryInformation ¶
Bases: BaseModel
Delivery details (BG-13): recipient (BT-70), location (BT-71), actual delivery date (BT-72) and address (BG-15).
CreditTransfer ¶
Bases: BaseModel
A credit-transfer account (BG-17); the account identifier is mandatory (BR-50).
PaymentCard ¶
Bases: BaseModel
Payment card information (BG-18). Card-security standards allow at most the first 6 and last 4 digits of the account number (BR-51, a warning).
DirectDebit ¶
Bases: BaseModel
Direct-debit terms (BG-19).
PaymentInstructions ¶
Bases: BaseModel
Payment instructions (BG-16); the means code (BT-81) is mandatory
(BR-49) and drawn from UNTDID 4461 — e.g. "30" credit transfer,
"58" SEPA credit transfer, "42" bank account payment.
DocumentAllowance ¶
Bases: _DocumentAllowanceChargeBase
A document-level allowance (BG-20).
DocumentCharge ¶
Bases: _DocumentAllowanceChargeBase
A document-level charge (BG-21).
LineAllowance ¶
Bases: _AllowanceChargeBase
An invoice-line allowance (BG-27).
LineCharge ¶
Bases: _AllowanceChargeBase
An invoice-line charge (BG-28).
ItemAttribute ¶
Bases: BaseModel
An item attribute (BG-32): name and value are both required (BR-54).
ItemClassification ¶
Bases: BaseModel
An item classification (BT-158); the scheme is mandatory (BR-65).
InvoiceLine ¶
Bases: BaseModel
One invoice / credit-note line (BG-25) with its item and price flattened in.
net_amount (BT-131) is computed from quantity x price and the line
allowances/charges when omitted; supply it explicitly to reproduce an
upstream document's own rounding.
effective_net_amount
property
¶
effective_net_amount: Decimal
BT-131: the explicit net amount, else quantity x unit price (per base quantity) minus line allowances plus line charges, rounded to 2.
VatBreakdownEntry ¶
Bases: BaseModel
One VAT breakdown group (BG-23), keyed by category + rate.
Amounts left None are computed from the lines and document-level
allowances/charges of the same category and rate. Exemption reasons cannot be
computed: categories E/AE/K/G/O require one (BR-E/AE/IC/G/O-10), which is the
main reason to pass explicit entries when authoring an exempt invoice. An entry
written by hand must carry it — that is data hygiene, enforced here; an entry
:meth:InvoiceDocument.compute_vat_breakdown derived has nowhere to get it, so
it is built without one and :func:~.rules.validate reports the missing reason
as a fatal BR-*-10 finding.
Totals ¶
Bases: BaseModel
The document totals (BG-22). Every member is optional: whatever is left
None is computed (see :meth:InvoiceDocument.effective_totals); whatever
is supplied is preserved and checked by :func:~.rules.validate.
No member is sign-constrained. EN 16931 and CIUS-RO put no such rule on any of them, and Romanian practice files a storno as a type-380 invoice with negative values throughout, so a negative BT-110 is ordinary inbox content. Requiring otherwise also made a read storno unrenderable — the computed totals are derived from the very amounts that make them negative.
PrecedingInvoice ¶
Bases: BaseModel
A preceding invoice reference (BG-3); the number is mandatory (BR-55). CIUS-RO requires at least one on credit notes referencing the corrected invoice in practice, and caps them at 500 (BR-RO-A500).
SupportingDocument ¶
Bases: BaseModel
An additional supporting document (BG-24); the reference is mandatory (BR-52). Embedded content requires its MIME code and filename.
Note ¶
Bases: BaseModel
An invoice note (BG-1): free text (BT-22) with an optional UNCL 4451
subject qualifier (BT-21); on the wire the two ride one cbc:Note as
#CODE#text.
InvoiceDocument ¶
Bases: BaseModel
A CIUS-RO electronic invoice or credit note, flat and bidirectional.
Author one with the business fields and let the totals and VAT breakdown be
computed; or obtain one from wire XML via :func:~.read.read_invoice /
:func:~.read.parse_invoice, with every wire amount preserved. Render with
:func:~.build.render_invoice; check the full EN 16931 + CIUS-RO rule set
with :func:~.rules.validate.
compute_vat_breakdown ¶
compute_vat_breakdown() -> list[VatBreakdownEntry]
The VAT breakdown (BG-23) computed from lines, allowances and charges:
one entry per (category, rate), tax = taxable x rate rounded to 2
(BR-CO-17), zero for the exempt-style categories. Exemption reasons are
carried over from matching explicit :attr:vat_breakdown entries.
effective_vat_breakdown ¶
effective_vat_breakdown() -> list[VatBreakdownEntry]
The breakdown to render: the computed one when no explicit entries were
given; otherwise the explicit entries with any None amounts filled
from the matching computed group.
compute_totals ¶
compute_totals() -> Totals
The document totals (BG-22) computed per BR-CO-10..16.
prepaid and rounding are never computed (they are facts, not
sums) and are read from the explicit :attr:totals;
vat_total_tax_currency (BT-111) needs an exchange rate this library
does not know, so it also stays explicit-only.
round2 ¶
round2(value: Decimal) -> Decimal
EN 16931 Annex-B rounding: two decimals, half away from zero.
vat_group_key ¶
vat_group_key(category: VatCategory, rate: Decimal | None) -> tuple[VatCategory, Decimal | None]
The (category, rate) key the VAT breakdown (BG-23) groups by.
Rates are compared numerically (19 and 19.00 are one group), and
category O is keyed without a rate at all: BR-O-05..07 say it carries none,
so an issuer's explicit 0 and an absent rate mean the same thing. Both
spellings occur in one document — a line with no cbc:Percent under a
subtotal that has one — and keying them apart splits the group in two.
anafpy.efactura.authoring.codes ¶
Hand-curated codes for the CIUS-RO invoice models.
The big closed lists (currencies, countries, units, ...) are generated into
:mod:._codelists; this module carries the small, semantically-named subsets the
CIUS-RO Schematron restricts beyond the base UNTDID/ISO lists, plus the Romanian
sub-national code lists that exist only in the RO rules.
DocumentKind ¶
Bases: StrEnum
Which UBL document a :class:~.models.InvoiceDocument renders to.
InvoiceTypeCode ¶
Bases: StrEnum
BT-3 document type codes CIUS-RO admits (BR-RO-020, UNTDID 1001 subset).
CREDIT_NOTE (381) is the only code valid on a UBL CreditNote; the other
four are valid on a UBL Invoice.
VatCategory ¶
Bases: StrEnum
BT-95/102/118/151 VAT category codes (UNCL 5305, EN16931 subset).
SPLIT_PAYMENT (B) is on the list but only valid on domestic Italian
invoices (BR-B-01) — a CIUS-RO filing using it is rejected.
VatPointDateCode ¶
Bases: StrEnum
BT-8 VAT point date codes (UNTDID 2005 restricted by BR-RO-040).
anafpy.efactura.authoring.rules ¶
The cross-aggregate EN 16931 / CIUS-RO rule set over :class:InvoiceDocument.
:func:validate hand-translates the Schematron rules that construction-time
shape checks cannot express — totals arithmetic (BR-CO-10..17), VAT breakdown
consistency (BR-S/Z/E/AE/K/G/O/L/M-01..09), regime-dependent identifier
requirements, and the RO-specific document rules — and returns a
:class:ValidationReport of findings instead of raising. Rule ids and
severities match the official CIUS-RO 1.0.9 Schematron so findings line up with
what ANAF's validare endpoint reports; numeric comparisons mirror the
Schematron's own tolerances (the per-rate sums and the tax formula accept a
strict ±1 slack, the BR-CO totals are exact).
ANAF's validator remains the authority: a clean local report is a strong signal, never a guarantee.
Severity ¶
Bases: StrEnum
Finding severity, matching the Schematron flag.
Finding ¶
Bases: BaseModel
One rule violation.
ValidationReport ¶
Bases: BaseModel
The outcome of :func:validate: all findings, worst first.
InvoiceValidationError ¶
InvoiceValidationError(report: ValidationReport)
Bases: AnafError
Raised by :func:~.build.render_invoice when the document fails
:func:validate and validation was not explicitly skipped.
validate ¶
validate(document: InvoiceDocument) -> ValidationReport
Run the translated EN 16931 + CIUS-RO rule set and report findings.
Covers the cross-aggregate rules construction cannot enforce: totals
arithmetic, VAT breakdown presence/consistency per category, VAT-regime
identifier requirements, payment-instruction coherence. Field formats,
lengths, code lists and decimal budgets were already enforced when the
models were constructed. For a document read off the wire, the
construction-time consistency checks were deliberately skipped
(:mod:.read) and are not re-reported here: a shape that arrived in an
ANAF-accepted document is a shape ANAF tolerates.
Returns:
| Name | Type | Description |
|---|---|---|
A |
ValidationReport
|
class: |
ValidationReport
|
finding remains. ANAF's server-side |
anafpy.efactura.authoring.build ¶
Compose a flat :class:~.models.InvoiceDocument into UBL wire XML.
:func:build_invoice produces the generated UBL model (Invoice or
CreditNote per the document's :attr:~.models.InvoiceDocument.kind);
:func:render_invoice serializes it to upload-ready bytes, running
:func:~.rules.validate first unless explicitly skipped.
Syntax-mapping notes (the EN 16931 UBL binding's quirks, mirrored by
:mod:.read):
- BT-21/22 ride one
cbc:Noteas#SUBJECTCODE#text. - BT-8 (VAT point date code) rides
cac:InvoicePeriod/cbc:DescriptionCode. - BT-32 (seller tax registration) is a second
cac:PartyTaxSchemewhoseTaxScheme/IDis notVAT(rendered asFC). - BT-90 (bank-assigned creditor id) is a
cac:PartyIdentificationwithschemeID="SEPA"on the payee (or the seller when there is no payee). - On a credit note, BT-9 (due date) rides
cac:PaymentMeansand BT-11 (project reference) anAdditionalDocumentReferencewith type code 50. - UBL requires
cac:CardAccount/cbc:NetworkID, which EN 16931 does not model; it is rendered asZZZand dropped on read.
render_invoice ¶
render_invoice(document: InvoiceDocument, *, skip_validation: bool = False) -> bytes
Compose and serialize a document to upload-ready UTF-8 XML bytes.
Runs :func:~.rules.validate first and raises
:class:~.rules.InvoiceValidationError on fatal findings; pass
skip_validation=True to render regardless (e.g. to reproduce a faulty
upstream document byte-for-byte for ANAF's own validator to judge).
build_invoice ¶
build_invoice(document: InvoiceDocument) -> Invoice | CreditNote
Compose the flat document into its generated UBL model.
Pure translation — no validation, no defaulting beyond what the flat model already computed (line ids, totals, VAT breakdown).
anafpy.efactura.authoring.read ¶
Read UBL wire XML back into the flat :class:~.models.InvoiceDocument.
The inverse of :mod:.build, sharing its syntax-mapping conventions (#CODE#
note prefixes, the non-VAT-schemed tax registration identifier, SEPA-schemed
creditor ids, type-130/50 document references). Every wire amount — line nets,
totals, the VAT breakdown — is read into the explicit fields, so a round-trip
preserves the source document's own arithmetic instead of recomputing it; run
:func:~.rules.validate on the result to judge that arithmetic.
The reader is lenient about shape and strict about representability. It
validates through :data:~.models._DERIVED_CONTEXT, which stands the
authoring models' construction-time checks down: what stops us filing an
invalid document has no business rejecting one ANAF already accepted, and the
inbox is full of shapes the authoring rules refuse — negative amounts on a
storno filed as a type-380 invoice, an empty <cbc:PostalZone/>, two
addresses in one BT-43, a category-O line carrying an explicit zero rate. Run
:func:~.rules.validate on the result to judge the document; the reader's job
is to hand it over whole.
Empty and whitespace-only elements are read as absent, per UBL's own semantics — and an optional aggregate left with no identifier at all (a supporting document, a preceding invoice, a credit transfer, a payment card) is dropped rather than taken to mean the whole document is unreadable.
What still raises is a missing mandatory element (ValueError) and the
checks that mirror rules ANAF itself enforces fatally — the closed BR-CL-*
code lists, the format patterns, the BR-RO-L* length limits and the
BR-DEC-* decimal budgets (a pydantic ValidationError). Those cannot fire
on a document ANAF accepted; when one does, the mirrors have drifted from
ANAF's current edition — the tripwire DownloadedMessage.view documents. That
wrapper returns None instead of raising, keeping the raw bytes and the full
UBL model as the fallback tiers.
parse_invoice ¶
parse_invoice(xml: bytes) -> InvoiceDocument
Parse e-Factura wire XML straight into an :class:InvoiceDocument.
Raises:
| Type | Description |
|---|---|
ValueError
|
when the bytes are not a parseable UBL invoice/credit note, or when a mandatory element is missing from one that is. |
ValidationError
|
when a value fails one of the ANAF-certain mirrors that stay fatal on read (code lists, format patterns, length limits, decimal budgets — see the module docstring). |
read_invoice ¶
read_invoice(doc: Invoice | CreditNote) -> InvoiceDocument
Translate a generated UBL model into the flat :class:InvoiceDocument.
Wire totals, line net amounts and the VAT breakdown land in the explicit
fields (never recomputed); :func:~.rules.validate afterwards tells whether
the source document's arithmetic holds up.