generate_invoice_be
Generates a valid UBL 2.1 Belgian e-invoice XML from structured data, applying correct customizationID and profileID for Peppol BIS 3.0 or PINT-BE profiles. Output ready for Peppol or Mercurius.
Instructions
Generate a valid UBL 2.1 Belgian e-invoice XML document from structured data.
Applies the correct customizationID and profileID for the selected Belgian Peppol profile. The output XML is ready for submission to the Peppol network or the Mercurius platform.
Returns a dict with:
xml: the generated UBL 2.1 XML stringcustomization_id: the UBL customizationID applied (BT-24)profile_id: the UBL profileID applied (BT-23)
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| invoice_data | Yes | Invoice fields matching the BEInvoice schema | |
| profile | No | Target profile: 'peppol-bis-3' (default) or 'pint-be' | peppol-bis-3 |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- The core handler for generate_invoice_be: validates invoice_data via BEInvoice model, generates UBL XML via self.generate(), and returns the xml string with customization_id and profile_id.
async def generate_invoice_be( self, invoice_data: Annotated[dict[str, Any], "Invoice fields matching the BEInvoice schema"], profile: Annotated[ ProfileLiteral, "Target profile: 'peppol-bis-3' (default) or 'pint-be'", ] = "peppol-bis-3", ) -> dict[str, object]: """Generate a valid UBL 2.1 Belgian e-invoice XML document from structured data. Applies the correct customizationID and profileID for the selected Belgian Peppol profile. The output XML is ready for submission to the Peppol network or the Mercurius platform. Returns a dict with: - ``xml``: the generated UBL 2.1 XML string - ``customization_id``: the UBL customizationID applied (BT-24) - ``profile_id``: the UBL profileID applied (BT-23) """ try: invoice = BEInvoice.model_validate({**invoice_data, "profile": profile}) xml_string = self.generate(invoice) except DocumentGenerationError: raise except Exception as exc: raise DocumentGenerationError(str(exc)) from exc return { "xml": xml_string, "customization_id": CUSTOMIZATION_IDS[profile], "profile_id": PROFILE_IDS[profile], } - src/mcp_einvoicing_be/server.py:20-24 (registration)Registration: _register_be_tools() calls mcp.tool()(_generator.generate_invoice_be) to expose the tool on the FastMCP server.
def _register_be_tools(mcp: Any) -> None: """Register all Belgian e-invoicing tools onto the shared FastMCP instance.""" mcp.tool()(_validator.validate_invoice_be) mcp.tool()(_validator.validate_pint_be) mcp.tool()(_generator.generate_invoice_be) - BEInvoice Pydantic model validated in generate_invoice_be. Defines profile, document_type, seller, buyer, lines, payment fields.
class BEInvoice(InvoiceDocument): # type: ignore[misc] """Belgian e-invoice. Extends ``InvoiceDocument`` with Belgium-specific fields: PINT-BE profile selection, Belgian party types, and Belgian payment terms. """ document_type: Literal["380", "381", "383"] = Field( default="380", description="UNTDID 1001 code: 380=Invoice, 381=Credit note, 383=Debit note", ) profile: Literal["peppol-bis-3", "pint-be"] = Field( default="peppol-bis-3", description="Belgian Peppol profile to apply", ) seller: Supplier buyer: Customer lines: list[BEInvoiceLine] = Field(..., min_length=1) payment: BEPaymentTerms | None = Field(default=None) order_reference: str | None = Field( default=None, description="Purchase order reference (BT-13)" ) # noqa: E501 contract_reference: str | None = Field(default=None, description="Contract reference (BT-12)") payment_means_code: str = Field( default="30", description="UNTDID 4461 payment means code (30=credit transfer)", ) @field_validator("currency", check_fields=False) @classmethod def uppercase_currency(cls, v: str) -> str: return v.upper() # Re-export the core validation result — no Belgium-specific fields needed. ValidationResult = DocumentValidationResult - render_ubl_invoice: serializes BEInvoice to UBL 2.1 XML string used by BEDocumentGenerator.generate().
def render_ubl_invoice( invoice: BEInvoice, customization_id: str, profile_id: str, namespaces: dict[str, str], ) -> str: """Serialize a ``BEInvoice`` to a UBL 2.1 Invoice XML string.""" root = Element(_q(_UBL_INVOICE_NS, "Invoice")) _el(root, _q(_CBC, "CustomizationID"), customization_id) _el(root, _q(_CBC, "ProfileID"), profile_id) _el(root, _q(_CBC, "ID"), invoice.number) _el(root, _q(_CBC, "IssueDate"), invoice.date) _el(root, _q(_CBC, "InvoiceTypeCode"), invoice.document_type) _el(root, _q(_CBC, "DocumentCurrencyCode"), invoice.currency) _el_opt(root, _q(_CBC, "Note"), getattr(invoice, "note", None)) if invoice.order_reference: order_ref = SubElement(root, _q(_CAC, "OrderReference")) _el(order_ref, _q(_CBC, "ID"), invoice.order_reference) if invoice.contract_reference: contract_ref = SubElement(root, _q(_CAC, "ContractDocumentReference")) _el(contract_ref, _q(_CBC, "ID"), invoice.contract_reference) _render_party(root, "AccountingSupplierParty", invoice.seller) _render_party(root, "AccountingCustomerParty", invoice.buyer) _render_payment_means(root, invoice) if invoice.payment and invoice.payment.due_date: pt = SubElement(root, _q(_CAC, "PaymentTerms")) _el(pt, _q(_CBC, "Note"), f"Due: {invoice.payment.due_date}") _render_tax_total(root, invoice) _render_legal_monetary_total(root, invoice) for idx, line in enumerate(invoice.lines, start=1): line_el = SubElement(root, _q(_CAC, "InvoiceLine")) _el(line_el, _q(_CBC, "ID"), str(idx)) qty_el = _el(line_el, _q(_CBC, "InvoicedQuantity"), format_quantity(line.quantity or 0)) qty_el.set("unitCode", line.unit_code) ext_el = _el( line_el, _q(_CBC, "LineExtensionAmount"), format_amount((line.quantity or 0) * line.unit_price), ) ext_el.set("currencyID", invoice.currency) item_el = SubElement(line_el, _q(_CAC, "Item")) _el(item_el, _q(_CBC, "Description"), line.description) _el_opt( # noqa: E501 item_el, _q(_CBC, "SellersItemIdentification"), getattr(line, "buyer_item_id", None) ) cls_tax = SubElement(item_el, _q(_CAC, "ClassifiedTaxCategory")) _el(cls_tax, _q(_CBC, "ID"), line.vat_category.value) _el(cls_tax, _q(_CBC, "Percent"), str(line.vat_rate)) ts = SubElement(cls_tax, _q(_CAC, "TaxScheme")) _el(ts, _q(_CBC, "ID"), "VAT") price_el = SubElement(line_el, _q(_CAC, "Price")) price_amt = _el(price_el, _q(_CBC, "PriceAmount"), format_amount(line.unit_price)) price_amt.set("currencyID", invoice.currency) return tostring(root, encoding="unicode", xml_declaration=False) - CUSTOMIZATION_IDS and PROFILE_IDS constants used by generate_invoice_be to set BT-24 and BT-23 values.
"""Peppol BIS Billing 3.0 constants for Belgium.""" # UBL customizationID values (BT-24) CUSTOMIZATION_IDS: dict[str, str] = { "peppol-bis-3": ("urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0"), "pint-be": ( "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0" "#conformant#urn:fdc:www.nbb.be:2020:pintbe" ), } # UBL profileID values (BT-23) PROFILE_IDS: dict[str, str] = { "peppol-bis-3": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0", "pint-be": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0", }