Skip to content

Invoice authoring

anafpy.efactura.authoring composes complete CIUS-RO invoices and credit notes from business fields — no invoicing software and no UBL knowledge required. The models are bidirectional: the same InvoiceDocument authors a filing and views a parsed one, with byte-stable round-trips.

If your invoicing software already exports UBL XML, strongly prefer the pass-through path: its document is authoritative, anafpy never re-composes it, and — the part that outlives the filing — ANAF's SPV is not invoice storage: it purges filed messages after ~60 days, and an invoicing system is what keeps your durable record. Authoring exists for everyone else — a freelancer, an agent drafting an invoice in a chat, a script issuing a handful of invoices a month — with one obligation attached: download and keep the signed ZIP of every filing, because your copy is the one that lasts.

Composing an invoice

import datetime as dt
from decimal import Decimal

from anafpy.efactura.authoring import (
    InvoiceDocument, InvoiceLine, Party, PostalAddress, Seller,
)

address = PostalAddress(
    street="Str. Exemplu 1", city="Cluj-Napoca", county="RO-CJ", country="RO",
)
invoice = InvoiceDocument(
    number="INV-2026-0042",
    issue_date=dt.date(2026, 7, 8),
    due_date=dt.date(2026, 8, 7),
    currency="RON",
    seller=Seller(name="Furnizor SRL", vat_id="RO12345678", address=address),
    buyer=Party(name="Client SRL", vat_id="RO87654321", address=address),
    lines=[
        InvoiceLine(
            name="Servicii de consultanta",
            quantity=Decimal("10"),
            unit="H87",  # UN/ECE Rec 20/21: H87 = piece, KGM = kilogram
            unit_price=Decimal("150.00"),
            vat_category="S",
            vat_rate=Decimal("19"),
        ),
    ],
)

That is a complete, fileable document: the totals and the VAT breakdown are computed from the lines, document-level allowances and charges (grouped by VAT category and rate, EN 16931 rounding). Supply explicit values only to reproduce an upstream document's own arithmetic — they are preserved on render and cross-checked by validate().

One semantic model covers both document types: pass kind="credit_note" (with a preceding_invoices reference to the corrected invoice) and the same fields render a UBL CreditNote instead, type code 381 and all.

The full EN 16931 surface is available when needed: payee and tax representative, delivery details, payment instructions (credit transfers, card, direct debit), document- and line-level allowances/charges, item identifiers/classifications/attributes, attachments, periods, and every reference term (contract, order, despatch, ...).

Two-tier validation

Construction enforces what a single field or model can know unconditionally — formats, the CIUS-RO length caps, the closed code lists (currencies, countries, units, payment means, VATEX, ...), decimal budgets, per-category VAT rate shapes, Romanian county/sector rules. Invalid data fails fast with a pointed message; ANAF would reject it with certainty anyway.

The cross-cutting rules run on demand:

from anafpy.efactura.authoring import validate

report = validate(invoice)
for finding in report.findings:
    print(finding.rule, finding.message)   # e.g. BR-CO-25, BR-S-08, BR-RO-120

validate() is a hand-translated port of the official EN 16931 + CIUS-RO Schematron — totals arithmetic, VAT-regime identifier requirements, breakdown consistency — reporting findings with the official rule ids and mirroring the Schematron's own numeric tolerances. It is fast local feedback, not a verdict: ANAF's server-side validare stays authoritative (PublicClient.validate_invoice).

Rendering and filing

from anafpy.efactura.authoring import render_invoice

xml = render_invoice(invoice)          # upload-ready UTF-8 bytes
# or file directly:
result = await efactura.upload_invoice(invoice, cif="12345678")

Both run validate() first and raise InvoiceValidationError (carrying the report) on fatal findings; pass skip_validation=True to let ANAF be the only judge.

Reading wire XML back

from anafpy.efactura.authoring import parse_invoice, read_invoice

document = parse_invoice(xml_bytes)        # from bytes
document = read_invoice(message.document)  # from a DownloadedMessage's UBL

The reader is strict and full-fidelity: every wire amount lands in the explicit fields (never recomputed), so render_invoice(parse_invoice(xml)) == xml holds and validate() can judge an upstream document's arithmetic. This is the natural starting point for drafting a credit note from a received invoice — and it is exactly what DownloadedMessage.view returns for a downloaded message, wrapped to yield None instead of raising when the content is not a representable invoice.