generate_fa2_invoice
Generates a KSeF-compliant FA(2) XML invoice from structured invoice data, ready for submission to KSeF. Requires seller tax_id as a 10-digit Polish NIP.
Instructions
Generate a KSeF-compliant FA(2) XML invoice from structured invoice data.
Returns the FA(2) XML string ready for submission to KSeF. The seller's tax_id must be a Polish NIP (10 digits).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| invoice | Yes | Country-agnostic invoice document envelope. Country adapters read/write this model via BaseDocumentGenerator.generate() and BaseDocumentParser.to_invoice_document(). document_type: Country-specific code (IT: TD01–TD28, UBL: 380/381/384, DE: RE/GU…). transmission_format: Platform routing hint (IT: FPA12/FPR12, FR: B2B/B2BInt/B2C). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/mcp_ksef_pl/generator.py:163-201 (handler)FA2Generator.generate() — core handler that builds the FA(2) XML string from an InvoiceDocument, wrapping Naglowek, Podmiot1/Podmiot2, Fa sections with VAT fields, payment, invoice lines, and annotations.
async def generate(self, invoice: InvoiceDocument) -> str: try: now_utc = _dt.datetime.now(_dt.UTC).strftime("%Y-%m-%dT%H:%M:%SZ") vat_fields = _vat_summary_fields(invoice.vat_summary or []) payment = _payment_block(invoice) xml = ( f'<?xml version="1.0" encoding="UTF-8"?>\n' f'<Faktura xmlns="{_NS}"\n' f' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n' f" <Naglowek>\n" f' <KodFormularza kodSystemowy="FA (2)" wersjaSchemy="1-0E">FA</KodFormularza>\n' f" <WariantFormularza>2</WariantFormularza>\n" f" <DataWytworzenieFa>{now_utc}</DataWytworzenieFa>\n" f" <SystemInfo>{xml_escape(_SYSTEM_INFO)}</SystemInfo>\n" f" </Naglowek>\n" f" {_party_block(invoice.seller, 'Podmiot1').strip()}\n" f" {_party_block(invoice.buyer, 'Podmiot2').strip()}\n" f" <Fa>\n" f" <KodWaluty>{xml_escape(invoice.currency)}</KodWaluty>\n" f" <P_1>{xml_escape(str(invoice.date))}</P_1>\n" f" <P_2>{xml_escape(invoice.number)}</P_2>\n" f" {vat_fields}\n" f" {payment}\n" f" <Adnotacje>\n" f" <P_16>2</P_16>\n" f" <P_17>2</P_17>\n" f" <P_18>2</P_18>\n" f" <P_18A>2</P_18A>\n" f" <P_23>2</P_23>\n" f" </Adnotacje>\n" f" {_invoice_lines(invoice).strip()}\n" + (f" <StopkaFaktury>{xml_escape(invoice.note)}</StopkaFaktury>\n" if invoice.note else "") + f" </Fa>\n" f"</Faktura>\n" ) return xml except Exception as exc: raise DocumentGenerationError(f"FA(2) generation failed: {exc}") from exc - src/mcp_ksef_pl/server.py:52-59 (handler)generate_fa2_invoice — MCP tool handler registered via @mcp.tool, delegates to _fa2_generator.generate(invoice).
@mcp.tool async def generate_fa2_invoice(invoice: InvoiceDocument) -> str: """Generate a KSeF-compliant FA(2) XML invoice from structured invoice data. Returns the FA(2) XML string ready for submission to KSeF. The seller's tax_id must be a Polish NIP (10 digits). """ return await _fa2_generator.generate(invoice) - src/mcp_ksef_pl/server.py:52-59 (registration)@mcp.tool decorator registers generate_fa2_invoice as a named MCP tool with FastMCP server.
@mcp.tool async def generate_fa2_invoice(invoice: InvoiceDocument) -> str: """Generate a KSeF-compliant FA(2) XML invoice from structured invoice data. Returns the FA(2) XML string ready for submission to KSeF. The seller's tax_id must be a Polish NIP (10 digits). """ return await _fa2_generator.generate(invoice) - src/mcp_ksef_pl/generator.py:57-88 (helper)_party_block() — helper that renders Podmiot1/Podmiot2 XML blocks with tax IDs and address.
def _party_block(party: InvoiceParty, tag: str) -> str: """Render a Podmiot1 (seller) or Podmiot2 (buyer) XML block.""" name = party.name or f"{party.first_name or ''} {party.last_name or ''}".strip() nip = party.tax_id.identifier if party.tax_id.country_code.upper() == "PL" else "" id_block = f"<NIP>{xml_escape(nip)}</NIP>\n" if nip else "" if party.alt_tax_id: id_block += f"<KodUE>{xml_escape(party.tax_id.country_code.upper())}</KodUE>\n" id_block += f"<NrVatUE>{xml_escape(party.alt_tax_id.identifier)}</NrVatUE>\n" id_block += f"<Nazwa>{xml_escape(name)}</Nazwa>\n" addr_block = "" if party.address: a = party.address addr_block = ( f"<Adres>\n" f" <KodKraju>{xml_escape(a.country_code.upper())}</KodKraju>\n" f" <AdresL1>{xml_escape(a.street or '')}</AdresL1>\n" + (f" <KodPocztowy>{xml_escape(a.postal_code)}</KodPocztowy>\n" if a.postal_code else "") + f" <Miejscowosc>{xml_escape(a.city or '')}</Miejscowosc>\n" + (f" <Wojewodztwo>{xml_escape(a.province)}</Wojewodztwo>\n" if a.province else "") + f"</Adres>\n" ) return ( f"<{tag}>\n" f" <DaneIdentyfikacyjne>\n" f" {id_block.strip()}\n" f" </DaneIdentyfikacyjne>\n" f" {addr_block.strip()}\n" f"</{tag}>\n" ) - src/mcp_ksef_pl/generator.py:120-149 (helper)_invoice_lines(), _payment_block(), _vat_summary_fields() — helpers that build FA(2) invoice line items, payment info, and VAT summary field blocks.
def _invoice_lines(invoice: InvoiceDocument) -> str: rows = [] for line in invoice.lines: rate_str = str(int(line.vat_rate)) if line.vat_rate == int(line.vat_rate) else str(line.vat_rate) rows.append( f" <FaWiersz>\n" f" <NrWierszaFa>{line.line_number}</NrWierszaFa>\n" f" <P_7>{xml_escape(line.description)}</P_7>\n" f" <P_8A>{xml_escape(line.unit_of_measure or 'szt')}</P_8A>\n" f" <P_8B>{format_amount(line.quantity)}</P_8B>\n" f" <P_9A>{_d(line.unit_price)}</P_9A>\n" f" <P_11>{_d(line.total_price)}</P_11>\n" f" <P_12>{xml_escape(rate_str)}</P_12>\n" + (f" <P_12_XII>{xml_escape(line.vat_exemption_code)}</P_12_XII>\n" if line.vat_exemption_code else "") + f" </FaWiersz>\n" ) return "<FaWiersze>\n" + "".join(rows) + "</FaWiersze>\n" def _payment_block(invoice: InvoiceDocument) -> str: if not invoice.payment: return "" p = invoice.payment parts = [] if p.due_date: parts.append(f"<P_6>{xml_escape(str(p.due_date))}</P_6>") if p.iban: parts.append(f"<RachunekBankowy><NrRB>{xml_escape(p.iban)}</NrRB></RachunekBankowy>") return "\n".join(parts)