generate_fattura_xml
Assemble a complete FatturaPA v1.6.1 XML document from all prepared data blocks, returning the XML string and filename for immediate validation.
Instructions
Assemble a complete FatturaPA v1.6.1 XML document from all prepared blocks.
Use this as step 10 in the invoice generation workflow — the final assembly step. All required blocks must come from their respective builder/validator tools; pass the full dict returned by each tool (the function unwraps the top-level key).
Required: dati_trasmissione, cedente_prestatore, cessionario_committente, dati_generali, dettaglio_linee (list), dati_riepilogo (list from compute_totali()). Optional: dati_pagamento, allegati (list), dati_ritenuta.
Does NOT validate against the XSD schema — call validate_fattura_xsd() (step 11) on the returned 'xml' string immediately after to confirm conformance.
On success returns {'xml': str, 'filename': str, 'formato_trasmissione': str, 'length_bytes': int}. On unexpected error returns {'error': ''}.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| dati_trasmissione | Yes | DatiTrasmissione block from build_transmission_header(). Must contain IdTrasmittente, ProgressivoInvio, FormatoTrasmissione, and CodiceDestinatario. | |
| cedente_prestatore | Yes | CedentePrestatore block from validate_cedente_prestatore(). Contains seller's tax ID, name, address, and fiscal regime. | |
| cessionario_committente | Yes | CessionarioCommittente block from validate_cessionario(). Contains buyer's tax ID, name, and address. | |
| dati_generali | Yes | DatiGenerali block from build_dati_generali(). Contains document type, date, number, and currency. | |
| dettaglio_linee | Yes | List of DettaglioLinee dicts from add_linea_dettaglio(). Each entry must have NumeroLinea, Descrizione, PrezzoUnitario, PrezzoTotale, and AliquotaIVA. | |
| dati_riepilogo | Yes | List of DatiRiepilogo dicts from compute_totali(). Contains VAT summary grouped by AliquotaIVA. | |
| dati_pagamento | No | DatiPagamento block from build_dati_pagamento(). Optional. | |
| allegati | No | List of Allegati dicts from add_allegato(). Optional. | |
| dati_ritenuta | No | DatiRitenuta block from check_ritenuta_acconto(). Required for professional invoices with withholding tax (ritenuta d'acconto). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- tools/global_tools.py:89-411 (handler)The main handler function that generates a complete FatturaPA v1.6.1 XML document from all prepared data blocks (DatiTrasmissione, CedentePrestatore, CessionarioCommittente, DatiGenerali, DettaglioLinee, DatiRiepilogo, plus optional DatiPagamento, Allegati, DatiRitenuta). Returns {xml, filename, formato_trasmissione, length_bytes}.
@mcp.tool() def generate_fattura_xml( dati_trasmissione: Annotated[ dict, Field( description=( "DatiTrasmissione block from build_transmission_header(). " "Must contain IdTrasmittente, ProgressivoInvio, FormatoTrasmissione, " "and CodiceDestinatario." ) ), ], cedente_prestatore: Annotated[ dict, Field( description=( "CedentePrestatore block from validate_cedente_prestatore(). " "Contains seller's tax ID, name, address, and fiscal regime." ) ), ], cessionario_committente: Annotated[ dict, Field( description=( "CessionarioCommittente block from validate_cessionario(). " "Contains buyer's tax ID, name, and address." ) ), ], dati_generali: Annotated[ dict, Field( description=( "DatiGenerali block from build_dati_generali(). " "Contains document type, date, number, and currency." ) ), ], dettaglio_linee: Annotated[ list, Field( description=( "List of DettaglioLinee dicts from add_linea_dettaglio(). " "Each entry must have NumeroLinea, Descrizione, PrezzoUnitario, " "PrezzoTotale, and AliquotaIVA." ) ), ], dati_riepilogo: Annotated[ list, Field( description=( "List of DatiRiepilogo dicts from compute_totali(). " "Contains VAT summary grouped by AliquotaIVA." ) ), ], dati_pagamento: Annotated[ Optional[dict], Field( default=None, description="DatiPagamento block from build_dati_pagamento(). Optional.", ), ] = None, allegati: Annotated[ Optional[list], Field( default=None, description="List of Allegati dicts from add_allegato(). Optional.", ), ] = None, dati_ritenuta: Annotated[ Optional[dict], Field( default=None, description=( "DatiRitenuta block from check_ritenuta_acconto(). " "Required for professional invoices with withholding tax (ritenuta d'acconto)." ), ), ] = None, ) -> dict: """Assemble a complete FatturaPA v1.6.1 XML document from all prepared blocks. Use this as step 10 in the invoice generation workflow — the final assembly step. All required blocks must come from their respective builder/validator tools; pass the full dict returned by each tool (the function unwraps the top-level key). Required: dati_trasmissione, cedente_prestatore, cessionario_committente, dati_generali, dettaglio_linee (list), dati_riepilogo (list from compute_totali()). Optional: dati_pagamento, allegati (list), dati_ritenuta. Does NOT validate against the XSD schema — call validate_fattura_xsd() (step 11) on the returned 'xml' string immediately after to confirm conformance. On success returns {'xml': str, 'filename': str, 'formato_trasmissione': str, 'length_bytes': int}. On unexpected error returns {'error': '<reason>'}. """ try: dt = dati_trasmissione.get("DatiTrasmissione", dati_trasmissione) cp = cedente_prestatore.get("CedentePrestatore", cedente_prestatore) cc = cessionario_committente.get("CessionarioCommittente", cessionario_committente) dg = dati_generali.get("DatiGenerali", dati_generali) formato = dt.get("FormatoTrasmissione", "FPR12") id_trasmittente = dt.get("IdTrasmittente", {}) id_paese_tx = id_trasmittente.get("IdPaese", "IT") id_codice_tx = id_trasmittente.get("IdCodice", "") progressivo = dt.get("ProgressivoInvio", "00001") codice_dest = dt.get("CodiceDestinatario", "0000000") pec_dest = dt.get("PECDestinatario", "") cp_dati = cp.get("DatiAnagrafici", {}) cp_id = cp_dati.get("IdFiscaleIVA", {}) cp_anagrafica = cp_dati.get("Anagrafica", {}) cp_regime = cp_dati.get("RegimeFiscale", "RF01") cp_sede = cp.get("Sede", {}) cc_dati = cc.get("DatiAnagrafici", {}) cc_id = cc_dati.get("IdFiscaleIVA", {}) cc_cf = cc_dati.get("CodiceFiscale", "") cc_anagrafica = cc_dati.get("Anagrafica", {}) cc_sede = cc.get("Sede", {}) dg_doc = dg.get("DatiGeneraliDocumento", dg) tipo_doc = dg_doc.get("TipoDocumento", "TD01") divisa = dg_doc.get("Divisa", "EUR") data_doc = dg_doc.get("Data", "") numero_doc = dg_doc.get("Numero", "") causale = dg_doc.get("Causale", "") def _seller_name(anagrafica: dict) -> str: if "Denominazione" in anagrafica: return f"<Denominazione>{anagrafica['Denominazione']}</Denominazione>" return ( f"<Nome>{anagrafica.get('Nome', '')}</Nome>" f"<Cognome>{anagrafica.get('Cognome', '')}</Cognome>" ) def _buyer_id(cc_id: dict, cc_cf: str) -> str: parts = [] if cc_id: parts.append( f"<IdFiscaleIVA>" f"<IdPaese>{cc_id.get('IdPaese', 'IT')}</IdPaese>" f"<IdCodice>{cc_id.get('IdCodice', '')}</IdCodice>" f"</IdFiscaleIVA>" ) if cc_cf: parts.append(f"<CodiceFiscale>{cc_cf}</CodiceFiscale>") return "".join(parts) def _linee_xml(linee: list) -> str: parts = [] for linea in linee: ld = linea.get("DettaglioLinee", linea) qta = f"<Quantita>{ld['Quantita']}</Quantita>" if "Quantita" in ld else "" um = f"<UnitaMisura>{ld['UnitaMisura']}</UnitaMisura>" if "UnitaMisura" in ld else "" nat = f"<Natura>{ld['Natura']}</Natura>" if "Natura" in ld else "" rit = f"<Ritenuta>{ld['Ritenuta']}</Ritenuta>" if "Ritenuta" in ld else "" parts.append( f"<DettaglioLinee>" f"<NumeroLinea>{ld['NumeroLinea']}</NumeroLinea>" f"<Descrizione>{ld['Descrizione']}</Descrizione>" f"{qta}{um}" f"<PrezzoUnitario>{ld['PrezzoUnitario']}</PrezzoUnitario>" f"<PrezzoTotale>{ld['PrezzoTotale']}</PrezzoTotale>" f"<AliquotaIVA>{ld['AliquotaIVA']}</AliquotaIVA>" f"{nat}{rit}" f"</DettaglioLinee>" ) return "".join(parts) def _riepilogo_xml(riepilogo: list) -> str: parts = [] for r in riepilogo: nat = f"<Natura>{r['Natura']}</Natura>" if "Natura" in r else "" parts.append( f"<DatiRiepilogo>" f"<AliquotaIVA>{r['AliquotaIVA']}</AliquotaIVA>" f"{nat}" f"<Imponibile>{r['Imponibile']}</Imponibile>" f"<Imposta>{r['Imposta']}</Imposta>" f"<EsigibilitaIVA>{r.get('EsigibilitaIVA', 'I')}</EsigibilitaIVA>" f"</DatiRiepilogo>" ) return "".join(parts) def _pagamento_xml(pagamento: Optional[dict]) -> str: if not pagamento: return "" p = pagamento.get("DatiPagamento", pagamento) dp = p.get("DettaglioPagamento", {}) scad = f"<DataScadenzaPagamento>{dp['DataScadenzaPagamento']}</DataScadenzaPagamento>" if "DataScadenzaPagamento" in dp else "" iban = f"<IBAN>{dp['IBAN']}</IBAN>" if "IBAN" in dp else "" banca = f"<IstitutoFinanziario>{dp['IstitutoFinanziario']}</IstitutoFinanziario>" if "IstitutoFinanziario" in dp else "" return ( f"<DatiPagamento>" f"<CondizioniPagamento>{p['CondizioniPagamento']}</CondizioniPagamento>" f"<DettaglioPagamento>" f"<ModalitaPagamento>{dp['ModalitaPagamento']}</ModalitaPagamento>" f"{scad}" f"<ImportoPagamento>{dp['ImportoPagamento']}</ImportoPagamento>" f"{iban}{banca}" f"</DettaglioPagamento>" f"</DatiPagamento>" ) def _allegati_xml(allegati_list: Optional[list]) -> str: if not allegati_list: return "" parts = [] for a in allegati_list: entry = a.get("Allegati", a) fmt = f"<FormatoAllegato>{entry['FormatoAllegato']}</FormatoAllegato>" if "FormatoAllegato" in entry else "" desc = f"<DescrizioneAllegato>{entry['DescrizioneAllegato']}</DescrizioneAllegato>" if "DescrizioneAllegato" in entry else "" parts.append( f"<Allegati>" f"<NomeAllegato>{entry['NomeAllegato']}</NomeAllegato>" f"{fmt}{desc}" f"<Attachment>{entry['Attachment']}</Attachment>" f"</Allegati>" ) return "".join(parts) def _ritenuta_xml(ritenuta: Optional[dict]) -> str: if not ritenuta: return "" r = ritenuta.get("DatiRitenuta", ritenuta) return ( f"<DatiRitenuta>" f"<TipoRitenuta>{r['TipoRitenuta']}</TipoRitenuta>" f"<ImportoRitenuta>{r['ImportoRitenuta']}</ImportoRitenuta>" f"<AliquotaRitenuta>{r['AliquotaRitenuta']}</AliquotaRitenuta>" f"<CausalePagamento>{r['CausalePagamento']}</CausalePagamento>" f"</DatiRitenuta>" ) pec_xml = f"<PECDestinatario>{pec_dest}</PECDestinatario>" if pec_dest else "" causale_xml = f"<Causale>{causale}</Causale>" if causale else "" xml = ( f'<?xml version="1.0" encoding="UTF-8"?>' f'<p:FatturaElettronica versione="{formato}" ' f'xmlns:p="{FATTURA_NS}" ' f'xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ' f'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' f"<FatturaElettronicaHeader>" f"<DatiTrasmissione>" f"<IdTrasmittente>" f"<IdPaese>{id_paese_tx}</IdPaese>" f"<IdCodice>{id_codice_tx}</IdCodice>" f"</IdTrasmittente>" f"<ProgressivoInvio>{progressivo}</ProgressivoInvio>" f"<FormatoTrasmissione>{formato}</FormatoTrasmissione>" f"<CodiceDestinatario>{codice_dest}</CodiceDestinatario>" f"{pec_xml}" f"</DatiTrasmissione>" f"<CedentePrestatore>" f"<DatiAnagrafici>" f"<IdFiscaleIVA>" f"<IdPaese>{cp_id.get('IdPaese', 'IT')}</IdPaese>" f"<IdCodice>{cp_id.get('IdCodice', '')}</IdCodice>" f"</IdFiscaleIVA>" f"<Anagrafica>{_seller_name(cp_anagrafica)}</Anagrafica>" f"<RegimeFiscale>{cp_regime}</RegimeFiscale>" f"</DatiAnagrafici>" f"<Sede>" f"<Indirizzo>{cp_sede.get('Indirizzo', '')}</Indirizzo>" f"<CAP>{cp_sede.get('CAP', '')}</CAP>" f"<Comune>{cp_sede.get('Comune', '')}</Comune>" f"<Nazione>{cp_sede.get('Nazione', 'IT')}</Nazione>" f"</Sede>" f"</CedentePrestatore>" f"<CessionarioCommittente>" f"<DatiAnagrafici>" f"{_buyer_id(cc_id, cc_cf)}" f"<Anagrafica>{_seller_name(cc_anagrafica)}</Anagrafica>" f"</DatiAnagrafici>" f"<Sede>" f"<Indirizzo>{cc_sede.get('Indirizzo', '')}</Indirizzo>" f"<CAP>{cc_sede.get('CAP', '')}</CAP>" f"<Comune>{cc_sede.get('Comune', '')}</Comune>" f"<Nazione>{cc_sede.get('Nazione', 'IT')}</Nazione>" f"</Sede>" f"</CessionarioCommittente>" f"</FatturaElettronicaHeader>" f"<FatturaElettronicaBody>" f"<DatiGenerali>" f"<DatiGeneraliDocumento>" f"<TipoDocumento>{tipo_doc}</TipoDocumento>" f"<Divisa>{divisa}</Divisa>" f"<Data>{data_doc}</Data>" f"<Numero>{numero_doc}</Numero>" f"{causale_xml}" f"{_ritenuta_xml(dati_ritenuta)}" f"</DatiGeneraliDocumento>" f"</DatiGenerali>" f"<DatiBeniServizi>" f"{_linee_xml(dettaglio_linee)}" f"{_riepilogo_xml(dati_riepilogo)}" f"</DatiBeniServizi>" f"{_pagamento_xml(dati_pagamento)}" f"{_allegati_xml(allegati)}" f"</FatturaElettronicaBody>" f"</p:FatturaElettronica>" ) # Generate SDI filename from seller Partita IVA piva = cp_id.get("IdCodice", "00000000000") filename = f"IT{piva}_{progressivo}.xml" return { "xml": xml, "filename": filename, "formato_trasmissione": formato, "length_bytes": len(xml.encode("utf-8")), } except Exception as exc: logger.exception("Error generating FatturaPA XML") return {"error": f"XML generation failed: {exc}"} - tools/global_tools.py:90-170 (schema)Input parameters definition with Pydantic Field descriptions showing required (dati_trasmissione, cedente_prestatore, cessionario_committente, dati_generali, dettaglio_linee, dati_riepilogo) and optional (dati_pagamento, allegati, dati_ritenuta) parameters.
def generate_fattura_xml( dati_trasmissione: Annotated[ dict, Field( description=( "DatiTrasmissione block from build_transmission_header(). " "Must contain IdTrasmittente, ProgressivoInvio, FormatoTrasmissione, " "and CodiceDestinatario." ) ), ], cedente_prestatore: Annotated[ dict, Field( description=( "CedentePrestatore block from validate_cedente_prestatore(). " "Contains seller's tax ID, name, address, and fiscal regime." ) ), ], cessionario_committente: Annotated[ dict, Field( description=( "CessionarioCommittente block from validate_cessionario(). " "Contains buyer's tax ID, name, and address." ) ), ], dati_generali: Annotated[ dict, Field( description=( "DatiGenerali block from build_dati_generali(). " "Contains document type, date, number, and currency." ) ), ], dettaglio_linee: Annotated[ list, Field( description=( "List of DettaglioLinee dicts from add_linea_dettaglio(). " "Each entry must have NumeroLinea, Descrizione, PrezzoUnitario, " "PrezzoTotale, and AliquotaIVA." ) ), ], dati_riepilogo: Annotated[ list, Field( description=( "List of DatiRiepilogo dicts from compute_totali(). " "Contains VAT summary grouped by AliquotaIVA." ) ), ], dati_pagamento: Annotated[ Optional[dict], Field( default=None, description="DatiPagamento block from build_dati_pagamento(). Optional.", ), ] = None, allegati: Annotated[ Optional[list], Field( default=None, description="List of Allegati dicts from add_allegato(). Optional.", ), ] = None, dati_ritenuta: Annotated[ Optional[dict], Field( default=None, description=( "DatiRitenuta block from check_ritenuta_acconto(). " "Required for professional invoices with withholding tax (ritenuta d'acconto)." ), ), ] = None, - tools/global_tools.py:86-412 (registration)Registration of generate_fattura_xml via @mcp.tool() decorator inside register_global_tools(mcp: FastMCP). The function register_global_tools is called from server.py line 85.
def register_global_tools(mcp: FastMCP) -> None: """Register the 7 global FatturaPA tools on the FastMCP instance.""" @mcp.tool() def generate_fattura_xml( dati_trasmissione: Annotated[ dict, Field( description=( "DatiTrasmissione block from build_transmission_header(). " "Must contain IdTrasmittente, ProgressivoInvio, FormatoTrasmissione, " "and CodiceDestinatario." ) ), ], cedente_prestatore: Annotated[ dict, Field( description=( "CedentePrestatore block from validate_cedente_prestatore(). " "Contains seller's tax ID, name, address, and fiscal regime." ) ), ], cessionario_committente: Annotated[ dict, Field( description=( "CessionarioCommittente block from validate_cessionario(). " "Contains buyer's tax ID, name, and address." ) ), ], dati_generali: Annotated[ dict, Field( description=( "DatiGenerali block from build_dati_generali(). " "Contains document type, date, number, and currency." ) ), ], dettaglio_linee: Annotated[ list, Field( description=( "List of DettaglioLinee dicts from add_linea_dettaglio(). " "Each entry must have NumeroLinea, Descrizione, PrezzoUnitario, " "PrezzoTotale, and AliquotaIVA." ) ), ], dati_riepilogo: Annotated[ list, Field( description=( "List of DatiRiepilogo dicts from compute_totali(). " "Contains VAT summary grouped by AliquotaIVA." ) ), ], dati_pagamento: Annotated[ Optional[dict], Field( default=None, description="DatiPagamento block from build_dati_pagamento(). Optional.", ), ] = None, allegati: Annotated[ Optional[list], Field( default=None, description="List of Allegati dicts from add_allegato(). Optional.", ), ] = None, dati_ritenuta: Annotated[ Optional[dict], Field( default=None, description=( "DatiRitenuta block from check_ritenuta_acconto(). " "Required for professional invoices with withholding tax (ritenuta d'acconto)." ), ), ] = None, ) -> dict: """Assemble a complete FatturaPA v1.6.1 XML document from all prepared blocks. Use this as step 10 in the invoice generation workflow — the final assembly step. All required blocks must come from their respective builder/validator tools; pass the full dict returned by each tool (the function unwraps the top-level key). Required: dati_trasmissione, cedente_prestatore, cessionario_committente, dati_generali, dettaglio_linee (list), dati_riepilogo (list from compute_totali()). Optional: dati_pagamento, allegati (list), dati_ritenuta. Does NOT validate against the XSD schema — call validate_fattura_xsd() (step 11) on the returned 'xml' string immediately after to confirm conformance. On success returns {'xml': str, 'filename': str, 'formato_trasmissione': str, 'length_bytes': int}. On unexpected error returns {'error': '<reason>'}. """ try: dt = dati_trasmissione.get("DatiTrasmissione", dati_trasmissione) cp = cedente_prestatore.get("CedentePrestatore", cedente_prestatore) cc = cessionario_committente.get("CessionarioCommittente", cessionario_committente) dg = dati_generali.get("DatiGenerali", dati_generali) formato = dt.get("FormatoTrasmissione", "FPR12") id_trasmittente = dt.get("IdTrasmittente", {}) id_paese_tx = id_trasmittente.get("IdPaese", "IT") id_codice_tx = id_trasmittente.get("IdCodice", "") progressivo = dt.get("ProgressivoInvio", "00001") codice_dest = dt.get("CodiceDestinatario", "0000000") pec_dest = dt.get("PECDestinatario", "") cp_dati = cp.get("DatiAnagrafici", {}) cp_id = cp_dati.get("IdFiscaleIVA", {}) cp_anagrafica = cp_dati.get("Anagrafica", {}) cp_regime = cp_dati.get("RegimeFiscale", "RF01") cp_sede = cp.get("Sede", {}) cc_dati = cc.get("DatiAnagrafici", {}) cc_id = cc_dati.get("IdFiscaleIVA", {}) cc_cf = cc_dati.get("CodiceFiscale", "") cc_anagrafica = cc_dati.get("Anagrafica", {}) cc_sede = cc.get("Sede", {}) dg_doc = dg.get("DatiGeneraliDocumento", dg) tipo_doc = dg_doc.get("TipoDocumento", "TD01") divisa = dg_doc.get("Divisa", "EUR") data_doc = dg_doc.get("Data", "") numero_doc = dg_doc.get("Numero", "") causale = dg_doc.get("Causale", "") def _seller_name(anagrafica: dict) -> str: if "Denominazione" in anagrafica: return f"<Denominazione>{anagrafica['Denominazione']}</Denominazione>" return ( f"<Nome>{anagrafica.get('Nome', '')}</Nome>" f"<Cognome>{anagrafica.get('Cognome', '')}</Cognome>" ) def _buyer_id(cc_id: dict, cc_cf: str) -> str: parts = [] if cc_id: parts.append( f"<IdFiscaleIVA>" f"<IdPaese>{cc_id.get('IdPaese', 'IT')}</IdPaese>" f"<IdCodice>{cc_id.get('IdCodice', '')}</IdCodice>" f"</IdFiscaleIVA>" ) if cc_cf: parts.append(f"<CodiceFiscale>{cc_cf}</CodiceFiscale>") return "".join(parts) def _linee_xml(linee: list) -> str: parts = [] for linea in linee: ld = linea.get("DettaglioLinee", linea) qta = f"<Quantita>{ld['Quantita']}</Quantita>" if "Quantita" in ld else "" um = f"<UnitaMisura>{ld['UnitaMisura']}</UnitaMisura>" if "UnitaMisura" in ld else "" nat = f"<Natura>{ld['Natura']}</Natura>" if "Natura" in ld else "" rit = f"<Ritenuta>{ld['Ritenuta']}</Ritenuta>" if "Ritenuta" in ld else "" parts.append( f"<DettaglioLinee>" f"<NumeroLinea>{ld['NumeroLinea']}</NumeroLinea>" f"<Descrizione>{ld['Descrizione']}</Descrizione>" f"{qta}{um}" f"<PrezzoUnitario>{ld['PrezzoUnitario']}</PrezzoUnitario>" f"<PrezzoTotale>{ld['PrezzoTotale']}</PrezzoTotale>" f"<AliquotaIVA>{ld['AliquotaIVA']}</AliquotaIVA>" f"{nat}{rit}" f"</DettaglioLinee>" ) return "".join(parts) def _riepilogo_xml(riepilogo: list) -> str: parts = [] for r in riepilogo: nat = f"<Natura>{r['Natura']}</Natura>" if "Natura" in r else "" parts.append( f"<DatiRiepilogo>" f"<AliquotaIVA>{r['AliquotaIVA']}</AliquotaIVA>" f"{nat}" f"<Imponibile>{r['Imponibile']}</Imponibile>" f"<Imposta>{r['Imposta']}</Imposta>" f"<EsigibilitaIVA>{r.get('EsigibilitaIVA', 'I')}</EsigibilitaIVA>" f"</DatiRiepilogo>" ) return "".join(parts) def _pagamento_xml(pagamento: Optional[dict]) -> str: if not pagamento: return "" p = pagamento.get("DatiPagamento", pagamento) dp = p.get("DettaglioPagamento", {}) scad = f"<DataScadenzaPagamento>{dp['DataScadenzaPagamento']}</DataScadenzaPagamento>" if "DataScadenzaPagamento" in dp else "" iban = f"<IBAN>{dp['IBAN']}</IBAN>" if "IBAN" in dp else "" banca = f"<IstitutoFinanziario>{dp['IstitutoFinanziario']}</IstitutoFinanziario>" if "IstitutoFinanziario" in dp else "" return ( f"<DatiPagamento>" f"<CondizioniPagamento>{p['CondizioniPagamento']}</CondizioniPagamento>" f"<DettaglioPagamento>" f"<ModalitaPagamento>{dp['ModalitaPagamento']}</ModalitaPagamento>" f"{scad}" f"<ImportoPagamento>{dp['ImportoPagamento']}</ImportoPagamento>" f"{iban}{banca}" f"</DettaglioPagamento>" f"</DatiPagamento>" ) def _allegati_xml(allegati_list: Optional[list]) -> str: if not allegati_list: return "" parts = [] for a in allegati_list: entry = a.get("Allegati", a) fmt = f"<FormatoAllegato>{entry['FormatoAllegato']}</FormatoAllegato>" if "FormatoAllegato" in entry else "" desc = f"<DescrizioneAllegato>{entry['DescrizioneAllegato']}</DescrizioneAllegato>" if "DescrizioneAllegato" in entry else "" parts.append( f"<Allegati>" f"<NomeAllegato>{entry['NomeAllegato']}</NomeAllegato>" f"{fmt}{desc}" f"<Attachment>{entry['Attachment']}</Attachment>" f"</Allegati>" ) return "".join(parts) def _ritenuta_xml(ritenuta: Optional[dict]) -> str: if not ritenuta: return "" r = ritenuta.get("DatiRitenuta", ritenuta) return ( f"<DatiRitenuta>" f"<TipoRitenuta>{r['TipoRitenuta']}</TipoRitenuta>" f"<ImportoRitenuta>{r['ImportoRitenuta']}</ImportoRitenuta>" f"<AliquotaRitenuta>{r['AliquotaRitenuta']}</AliquotaRitenuta>" f"<CausalePagamento>{r['CausalePagamento']}</CausalePagamento>" f"</DatiRitenuta>" ) pec_xml = f"<PECDestinatario>{pec_dest}</PECDestinatario>" if pec_dest else "" causale_xml = f"<Causale>{causale}</Causale>" if causale else "" xml = ( f'<?xml version="1.0" encoding="UTF-8"?>' f'<p:FatturaElettronica versione="{formato}" ' f'xmlns:p="{FATTURA_NS}" ' f'xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ' f'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' f"<FatturaElettronicaHeader>" f"<DatiTrasmissione>" f"<IdTrasmittente>" f"<IdPaese>{id_paese_tx}</IdPaese>" f"<IdCodice>{id_codice_tx}</IdCodice>" f"</IdTrasmittente>" f"<ProgressivoInvio>{progressivo}</ProgressivoInvio>" f"<FormatoTrasmissione>{formato}</FormatoTrasmissione>" f"<CodiceDestinatario>{codice_dest}</CodiceDestinatario>" f"{pec_xml}" f"</DatiTrasmissione>" f"<CedentePrestatore>" f"<DatiAnagrafici>" f"<IdFiscaleIVA>" f"<IdPaese>{cp_id.get('IdPaese', 'IT')}</IdPaese>" f"<IdCodice>{cp_id.get('IdCodice', '')}</IdCodice>" f"</IdFiscaleIVA>" f"<Anagrafica>{_seller_name(cp_anagrafica)}</Anagrafica>" f"<RegimeFiscale>{cp_regime}</RegimeFiscale>" f"</DatiAnagrafici>" f"<Sede>" f"<Indirizzo>{cp_sede.get('Indirizzo', '')}</Indirizzo>" f"<CAP>{cp_sede.get('CAP', '')}</CAP>" f"<Comune>{cp_sede.get('Comune', '')}</Comune>" f"<Nazione>{cp_sede.get('Nazione', 'IT')}</Nazione>" f"</Sede>" f"</CedentePrestatore>" f"<CessionarioCommittente>" f"<DatiAnagrafici>" f"{_buyer_id(cc_id, cc_cf)}" f"<Anagrafica>{_seller_name(cc_anagrafica)}</Anagrafica>" f"</DatiAnagrafici>" f"<Sede>" f"<Indirizzo>{cc_sede.get('Indirizzo', '')}</Indirizzo>" f"<CAP>{cc_sede.get('CAP', '')}</CAP>" f"<Comune>{cc_sede.get('Comune', '')}</Comune>" f"<Nazione>{cc_sede.get('Nazione', 'IT')}</Nazione>" f"</Sede>" f"</CessionarioCommittente>" f"</FatturaElettronicaHeader>" f"<FatturaElettronicaBody>" f"<DatiGenerali>" f"<DatiGeneraliDocumento>" f"<TipoDocumento>{tipo_doc}</TipoDocumento>" f"<Divisa>{divisa}</Divisa>" f"<Data>{data_doc}</Data>" f"<Numero>{numero_doc}</Numero>" f"{causale_xml}" f"{_ritenuta_xml(dati_ritenuta)}" f"</DatiGeneraliDocumento>" f"</DatiGenerali>" f"<DatiBeniServizi>" f"{_linee_xml(dettaglio_linee)}" f"{_riepilogo_xml(dati_riepilogo)}" f"</DatiBeniServizi>" f"{_pagamento_xml(dati_pagamento)}" f"{_allegati_xml(allegati)}" f"</FatturaElettronicaBody>" f"</p:FatturaElettronica>" ) # Generate SDI filename from seller Partita IVA piva = cp_id.get("IdCodice", "00000000000") filename = f"IT{piva}_{progressivo}.xml" return { "xml": xml, "filename": filename, "formato_trasmissione": formato, "length_bytes": len(xml.encode("utf-8")), } except Exception as exc: logger.exception("Error generating FatturaPA XML") return {"error": f"XML generation failed: {exc}"} - tools/global_tools.py:221-326 (helper)Internal helper functions used by generate_fattura_xml: _seller_name (formats seller/buyer name as Denomination or Nome+Cognome), _buyer_id (formats buyer fiscal ID with optional CodiceFiscale), _linee_xml (serializes line items), _riepilogo_xml (serializes VAT summary), _pagamento_xml (serializes payment details), _allegati_xml (serializes attachments), _ritenuta_xml (serializes withholding tax).
def _seller_name(anagrafica: dict) -> str: if "Denominazione" in anagrafica: return f"<Denominazione>{anagrafica['Denominazione']}</Denominazione>" return ( f"<Nome>{anagrafica.get('Nome', '')}</Nome>" f"<Cognome>{anagrafica.get('Cognome', '')}</Cognome>" ) def _buyer_id(cc_id: dict, cc_cf: str) -> str: parts = [] if cc_id: parts.append( f"<IdFiscaleIVA>" f"<IdPaese>{cc_id.get('IdPaese', 'IT')}</IdPaese>" f"<IdCodice>{cc_id.get('IdCodice', '')}</IdCodice>" f"</IdFiscaleIVA>" ) if cc_cf: parts.append(f"<CodiceFiscale>{cc_cf}</CodiceFiscale>") return "".join(parts) def _linee_xml(linee: list) -> str: parts = [] for linea in linee: ld = linea.get("DettaglioLinee", linea) qta = f"<Quantita>{ld['Quantita']}</Quantita>" if "Quantita" in ld else "" um = f"<UnitaMisura>{ld['UnitaMisura']}</UnitaMisura>" if "UnitaMisura" in ld else "" nat = f"<Natura>{ld['Natura']}</Natura>" if "Natura" in ld else "" rit = f"<Ritenuta>{ld['Ritenuta']}</Ritenuta>" if "Ritenuta" in ld else "" parts.append( f"<DettaglioLinee>" f"<NumeroLinea>{ld['NumeroLinea']}</NumeroLinea>" f"<Descrizione>{ld['Descrizione']}</Descrizione>" f"{qta}{um}" f"<PrezzoUnitario>{ld['PrezzoUnitario']}</PrezzoUnitario>" f"<PrezzoTotale>{ld['PrezzoTotale']}</PrezzoTotale>" f"<AliquotaIVA>{ld['AliquotaIVA']}</AliquotaIVA>" f"{nat}{rit}" f"</DettaglioLinee>" ) return "".join(parts) def _riepilogo_xml(riepilogo: list) -> str: parts = [] for r in riepilogo: nat = f"<Natura>{r['Natura']}</Natura>" if "Natura" in r else "" parts.append( f"<DatiRiepilogo>" f"<AliquotaIVA>{r['AliquotaIVA']}</AliquotaIVA>" f"{nat}" f"<Imponibile>{r['Imponibile']}</Imponibile>" f"<Imposta>{r['Imposta']}</Imposta>" f"<EsigibilitaIVA>{r.get('EsigibilitaIVA', 'I')}</EsigibilitaIVA>" f"</DatiRiepilogo>" ) return "".join(parts) def _pagamento_xml(pagamento: Optional[dict]) -> str: if not pagamento: return "" p = pagamento.get("DatiPagamento", pagamento) dp = p.get("DettaglioPagamento", {}) scad = f"<DataScadenzaPagamento>{dp['DataScadenzaPagamento']}</DataScadenzaPagamento>" if "DataScadenzaPagamento" in dp else "" iban = f"<IBAN>{dp['IBAN']}</IBAN>" if "IBAN" in dp else "" banca = f"<IstitutoFinanziario>{dp['IstitutoFinanziario']}</IstitutoFinanziario>" if "IstitutoFinanziario" in dp else "" return ( f"<DatiPagamento>" f"<CondizioniPagamento>{p['CondizioniPagamento']}</CondizioniPagamento>" f"<DettaglioPagamento>" f"<ModalitaPagamento>{dp['ModalitaPagamento']}</ModalitaPagamento>" f"{scad}" f"<ImportoPagamento>{dp['ImportoPagamento']}</ImportoPagamento>" f"{iban}{banca}" f"</DettaglioPagamento>" f"</DatiPagamento>" ) def _allegati_xml(allegati_list: Optional[list]) -> str: if not allegati_list: return "" parts = [] for a in allegati_list: entry = a.get("Allegati", a) fmt = f"<FormatoAllegato>{entry['FormatoAllegato']}</FormatoAllegato>" if "FormatoAllegato" in entry else "" desc = f"<DescrizioneAllegato>{entry['DescrizioneAllegato']}</DescrizioneAllegato>" if "DescrizioneAllegato" in entry else "" parts.append( f"<Allegati>" f"<NomeAllegato>{entry['NomeAllegato']}</NomeAllegato>" f"{fmt}{desc}" f"<Attachment>{entry['Attachment']}</Attachment>" f"</Allegati>" ) return "".join(parts) def _ritenuta_xml(ritenuta: Optional[dict]) -> str: if not ritenuta: return "" r = ritenuta.get("DatiRitenuta", ritenuta) return ( f"<DatiRitenuta>" f"<TipoRitenuta>{r['TipoRitenuta']}</TipoRitenuta>" f"<ImportoRitenuta>{r['ImportoRitenuta']}</ImportoRitenuta>" f"<AliquotaRitenuta>{r['AliquotaRitenuta']}</AliquotaRitenuta>" f"<CausalePagamento>{r['CausalePagamento']}</CausalePagamento>" f"</DatiRitenuta>" )