es__generate_facturae_xml
Generate Facturae 3.2.2 XML invoices for Spanish B2G electronic invoicing to the FACe portal. The document is unsigned and must be signed before submission.
Instructions
Genera una factura XML conforme a Facturae 3.2.2 para envío B2G al portal FACe. El documento generado está sin firmar; use es__sign_facturae_xades para firmarlo.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| invoice | Yes | InvoiceDocument con seller, buyer, vat_summary y lines. | |
| schema_version | No | Versión del esquema Facturae (por defecto: '3.2.2'). | 3.2.2 |
Implementation Reference
- Handler function for the es__generate_facturae_xml tool. Parses invoice data, builds Facturae 3.2.2 XML via build_facturae_xml(), and returns the XML string.
async def handle_es_generate_facturae_xml( arguments: dict[str, Any], ) -> list[types.TextContent]: try: invoice_data = arguments.get("invoice") if not invoice_data: return err("invoice is required", "MISSING_PARAM") invoice = parse_invoice(invoice_data) xml_bytes = build_facturae_xml(invoice) logger.info("Facturae 3.2.2 XML generated for invoice %s", invoice.number) return ok({ "xml": xml_bytes.decode("utf-8"), "schema_version": "3.2.2", "invoice_number": invoice.number, "next_step": "Use es__sign_facturae_xades to apply XAdES-EPES signature before submitting to FACe.", }) except EInvoicingError as exc: return err(str(exc)) except Exception as exc: logger.exception("es__generate_facturae_xml failed") return err(str(exc)) - Tool definition/schema for es__generate_facturae_xml, including name, description, and inputSchema (invoice object required, optional schema_version).
TOOL_ES_GENERATE_FACTURAE_XML = types.Tool( name="es__generate_facturae_xml", description=( "Genera una factura XML conforme a Facturae 3.2.2 para envío B2G al portal FACe. " "El documento generado está sin firmar; use es__sign_facturae_xades para firmarlo." ), inputSchema={ "type": "object", "properties": { "invoice": { "type": "object", "description": "InvoiceDocument con seller, buyer, vat_summary y lines.", }, "schema_version": { "type": "string", "description": "Versión del esquema Facturae (por defecto: '3.2.2').", "default": "3.2.2", }, }, "required": ["invoice"], }, ) - mcp_facturacion_electronica_es/server.py:108-137 (registration)Registration of the tool name es__generate_facturae_xml to its handler in the _TOOL_HANDLERS dict on the server.
_TOOL_HANDLERS: dict[str, Any] = { # VERI*FACTU "es__generate_verifactu_record": handle_es_generate_verifactu_record, "es__validate_verifactu_record": handle_es_validate_verifactu_record, "es__submit_verifactu_to_aeat": handle_es_submit_verifactu_to_aeat, "es__generate_qr_verifactu": handle_es_generate_qr_verifactu, "es__cancel_verifactu_record": handle_es_cancel_verifactu_record, # Facturae / FACe "es__generate_facturae_xml": handle_es_generate_facturae_xml, "es__sign_facturae_xades": handle_es_sign_facturae_xades, "es__submit_to_face": handle_es_submit_to_face, "es__get_face_invoice_status": handle_es_get_face_invoice_status, "es__validate_facturae_schema": handle_es_validate_facturae_schema, # SII "es__build_sii_invoice_record": handle_es_build_sii_invoice_record, "es__submit_sii_batch": handle_es_submit_sii_batch, "es__query_sii_status": handle_es_query_sii_status, "es__generate_sii_correction": handle_es_generate_sii_correction, # TicketBAI "es__generate_ticketbai_xml": handle_es_generate_ticketbai_xml, "es__submit_ticketbai": handle_es_submit_ticketbai, "es__validate_ticketbai_schema": handle_es_validate_ticketbai_schema, # Crea y Crece / B2B "es__generate_b2b_einvoice_es": handle_es_generate_b2b_einvoice_es, "es__check_b2b_mandate_applicability": handle_es_check_b2b_mandate_applicability, # Utilities "es__detect_regional_regime": handle_es_detect_regional_regime, "es__get_compliance_status": handle_es_get_compliance_status, "es__parse_aeat_response": handle_es_parse_aeat_response, } - mcp_facturacion_electronica_es/server.py:77-106 (registration)Registration of TOOL_ES_GENERATE_FACTURAE_XML in the _ALL_TOOLS list on the server.
_ALL_TOOLS: list[types.Tool] = [ # VERI*FACTU TOOL_ES_GENERATE_VERIFACTU_RECORD, TOOL_ES_VALIDATE_VERIFACTU_RECORD, TOOL_ES_SUBMIT_VERIFACTU_TO_AEAT, TOOL_ES_GENERATE_QR_VERIFACTU, TOOL_ES_CANCEL_VERIFACTU_RECORD, # Facturae / FACe TOOL_ES_GENERATE_FACTURAE_XML, TOOL_ES_SIGN_FACTURAE_XADES, TOOL_ES_SUBMIT_TO_FACE, TOOL_ES_GET_FACE_INVOICE_STATUS, TOOL_ES_VALIDATE_FACTURAE_SCHEMA, # SII TOOL_ES_BUILD_SII_INVOICE_RECORD, TOOL_ES_SUBMIT_SII_BATCH, TOOL_ES_QUERY_SII_STATUS, TOOL_ES_GENERATE_SII_CORRECTION, # TicketBAI TOOL_ES_GENERATE_TICKETBAI_XML, TOOL_ES_SUBMIT_TICKETBAI, TOOL_ES_VALIDATE_TICKETBAI_SCHEMA, # Crea y Crece / B2B TOOL_ES_GENERATE_B2B_EINVOICE_ES, TOOL_ES_CHECK_B2B_MANDATE_APPLICABILITY, # Utilities TOOL_ES_DETECT_REGIONAL_REGIME, TOOL_ES_GET_COMPLIANCE_STATUS, TOOL_ES_PARSE_AEAT_RESPONSE, ] - Core XML builder helper function build_facturae_xml() that constructs the Facturae 3.2.2 XML tree from an InvoiceDocument.
def build_facturae_xml(invoice: InvoiceDocument) -> bytes: """Build a Facturae 3.2.2 XML document from an InvoiceDocument. Args: invoice: Validated core InvoiceDocument. Returns: UTF-8 encoded Facturae 3.2.2 XML bytes (unsigned). """ nsmap = { None: _FACTURAE_NS, "ds": _DS_NS, "xades": _XADES_NS, } root = etree.Element("Facturae", nsmap=nsmap) # FileHeader fh = _sub(root, "FileHeader") _sub(fh, "SchemaVersion", "3.2.2") _sub(fh, "Modality", "I") # I = individual invoice _sub(fh, "InvoiceIssuerType", "EU") # EU = private seller # Compute totals tax_base = sum((v.taxable_base for v in invoice.vat_summary), Decimal("0")) tax_total = sum((v.vat_amount for v in invoice.vat_summary), Decimal("0")) grand_total = tax_base + tax_total batch = _sub(fh, "Batch") _sub(batch, "BatchIdentifier", invoice.number) _sub(batch, "InvoicesCount", "1") ta = _sub(batch, "TotalInvoicesAmount") _sub(ta, "TotalAmount", fmt_amount(grand_total)) oa = _sub(batch, "TotalOutstandingAmount") _sub(oa, "TotalAmount", fmt_amount(grand_total)) ea = _sub(batch, "TotalExecutableAmount") _sub(ea, "TotalAmount", fmt_amount(grand_total)) _sub(batch, "InvoiceCurrencyCode", invoice.currency.upper()) # Parties parties = _sub(root, "Parties") seller_elem = _sub(parties, "SellerParty") _build_tax_id_block(seller_elem, invoice.seller) _build_legal_block(seller_elem, invoice.seller) buyer_elem = _sub(parties, "BuyerParty") _build_tax_id_block(buyer_elem, invoice.buyer) _build_legal_block(buyer_elem, invoice.buyer) # Invoices invoices_elem = _sub(root, "Invoices") inv = _sub(invoices_elem, "Invoice") ih = _sub(inv, "InvoiceHeader") _sub(ih, "InvoiceNumber", invoice.number) _sub(ih, "InvoiceDocumentType", "FC") # FC = complete invoice _sub(ih, "InvoiceClass", "OO") # OO = original iid = _sub(inv, "InvoiceIssueData") _sub(iid, "IssueDate", invoice.date) _sub(iid, "InvoiceCurrencyCode", invoice.currency.upper()) _sub(iid, "TaxCurrencyCode", invoice.currency.upper()) _sub(iid, "LanguageName", "es") # TaxesOutputs taxes = _sub(inv, "TaxesOutputs") for vat in invoice.vat_summary: tax = _sub(taxes, "Tax") _sub(tax, "TaxTypeCode", "01") # 01 = IVA _sub(tax, "TaxRate", fmt_amount(vat.vat_rate)) tb = _sub(tax, "TaxableBase") _sub(tb, "TotalAmount", fmt_amount(vat.taxable_base)) ta_elem = _sub(tax, "TaxAmount") _sub(ta_elem, "TotalAmount", fmt_amount(vat.vat_amount)) # InvoiceTotals totals = _sub(inv, "InvoiceTotals") _sub(totals, "TotalGrossAmount", fmt_amount(tax_base)) _sub(totals, "TotalGeneralDiscounts", "0.00") _sub(totals, "TotalGeneralSurcharges", "0.00") _sub(totals, "TotalGrossAmountBeforeTaxes", fmt_amount(tax_base)) _sub(totals, "TotalTaxOutputs", fmt_amount(tax_total)) _sub(totals, "TotalTaxesWithheld", "0.00") _sub(totals, "InvoiceTotal", fmt_amount(grand_total)) _sub(totals, "TotalOutstandingAmount", fmt_amount(grand_total)) _sub(totals, "TotalExecutableAmount", fmt_amount(grand_total)) # Items if invoice.lines: items = _sub(inv, "Items") for line in invoice.lines: il = _sub(items, "InvoiceLine") _sub(il, "ItemDescription", line.description[:2500]) if line.quantity is not None: _sub(il, "Quantity", fmt_amount(line.quantity)) _sub(il, "UnitPriceWithoutTax", fmt_amount(line.unit_price)) _sub(il, "TotalCost", fmt_amount(line.total_price)) _sub(il, "GrossAmount", fmt_amount(line.total_price)) line_taxes = _sub(il, "TaxesOutputs") lt = _sub(line_taxes, "Tax") _sub(lt, "TaxTypeCode", "01") _sub(lt, "TaxRate", fmt_amount(line.vat_rate)) ltb = _sub(lt, "TaxableBase") _sub(ltb, "TotalAmount", fmt_amount(line.total_price)) lta = _sub(lt, "TaxAmount") _sub(lta, "TotalAmount", fmt_amount(line.total_price * line.vat_rate / 100)) # PaymentDetails (if provided) if invoice.payment: pd = _sub(inv, "PaymentDetails") installment = _sub(pd, "Installment") _sub(installment, "InstallmentDueDate", invoice.payment.due_date or invoice.date) _sub(installment, "InstallmentAmount", fmt_amount(invoice.payment.amount)) _sub(installment, "PaymentMeans", invoice.payment.payment_method_code or "01") if invoice.payment.iban: _sub(installment, "AccountToBeCredited") # AccountToBeCredited needs full bank details; simplified here # [NEED: add IBAN and BIC fields per Facturae 3.2.2 schema] return etree.tostring(root, xml_declaration=True, encoding="UTF-8", pretty_print=True)