Bon allora per dare una vaga idea
la fattura è generata in una struttura che si chiama tstringlist, che è... una lista di stringhe, cui si accede essenzialmente con
add per aggiungere gli elementi e
[] per accedervi come fosse un array.
Dentro i_dataset si attinge al database, mappando i relativi campi entro quelli della fattura elettronica
function xg_generaFatturaElettronica(i_dataset:TDataSet;i_cartella:string;i_allegato:string;
i_trasmittentecf:string;
i_trasmittentepiva:string;
(...) tantl altri bei parametri di input
var
(...) varie variabili stringa
anno,mese,giorno:word;
fattura : TStringList; //// qui materialmente viene costruita la fattura
fatturadata:tdate;
fatturanumero:integer;
fatturaanno:integer;
numerolinea:Integer;
Funzioncine di supporto: trasforma i float nel formato della agenzia, con arrotondamento euro
function g_moneyafel(i_valore:double):string;
begin
Result:=stringreplace(format('%.02f',[EuroArrotonda(i_valore)]),',','.',[rfreplaceall]);
end;
Questa funziona "trasforma" tutti i caratteri in formato compatibile XML
function g_testoafel(i_stringa:string):string;
var
i:Integer;
begin
Result:=i_stringa;
if i_stringa='' then Exit;
for i:=Low(arrayconversione) to High(arrayconversione) do
Result:=StringReplace(result,arrayconversione[i],'&#'+inttostr(i)+';',[rfreplaceall]);
Result:=StringReplace(result,'<','<',[rfreplaceall]);
Result:=StringReplace(result,'&','&',[rfreplaceall]);
Result:=StringReplace(result,'"','"',[rfreplaceall]);
Result:=StringReplace(result,'''',''',[rfreplaceall]);
Result:=StringReplace(result,#10,' ',[rfreplaceall]);
Result:=StringReplace(result,#13,' ',[rfreplaceall]);
end;
Qui parte il resto
var
zrighe:TZQuery;
nomefile:string;
marchiofile:string;
nomepdf:string;
begin
Result:='';
numerolinea:=1;
(...) controlli vari che tolgo
fatturadata:=i_dataset.fieldbyname('datadocumento').asdatetime;
fatturanumero:=i_dataset.fieldbyname('progressivo').asinteger;
DecodeDate(fatturadata,anno,mese,giorno);
fatturaanno:=anno;
if i_dataset.fieldbyname('felpecdestinatario').isnull
AND i_dataset.fieldbyname('felcodice').isnull then
begin
logganow('Errore 71734 felpecdestinatario e felcodice vuoti');
exit;
end;
nota: c'è una funzione per generare il numero di serie aggiunto al nome file, ed è questa
function Dec_To_Base(nBase,nDec_Value, Lead_Zeros: int64): string;
var
Base_PChar: PChar;
Base_String: string;
To_Del, Modulus, DivNo: int64;
temp_string: string;
i:integer;
nLen, Len_Base: int64;
begin
{initialise..}
BASE_string := '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; //base62
To_Del := 0;
Modulus := 0;
DivNo := nDec_Value;
result := '';
if (nBase > 62) then
nBase := 62; {max = Base62}
SetLength(Base_String, nBase);
Base_PChar := PChar(Base_String);
while DivNo > 0 do
begin
Modulus := DivNo mod nBase;
result := Base_PChar[Modulus] + result;
DivNo := DivNo div nBase;
end;
if (Length(result) = 0) then
result := '0';
if (Length(result) < Lead_Zeros) then
for i := 1 to (Lead_Zeros - Length(result)) do
result := '0' + result;
end;
function g_codificanumerofatturaelettronica(i_tipodocumento:string;i_anno:Integer;i_numero:Integer):string;
var
tipo:char;
anno:Char;
numero:string;
begin
Result:='';
if Length(i_tipodocumento)>1 then
Exit;
tipo:=i_tipodocumento[1];
if i_anno<2018 then
Exit;
if i_anno>(2018+62) then
Exit;
if i_numero<0 then
Exit;
if i_numero>238327 then
Exit;
anno:=Dec_To_Base(62,i_anno-2018,0)[1];
numero:=Dec_To_Base(62,i_numero,3);
Result:=tipo+anno+numero;
if length(result)<>5 then
result:='';
end;
Essenzialmente "impacchetta" tipodocumento, anno e numero fattura in 5 caratteri,
arrivando al 2080 con circa 230.000 documento\anno massimo
E' importante segnalare "documento" perchè ci sono le fatture, le fatture immediate eccetera, ma soprattutto le NOTE DI CREDITO (e di debito), più le varianti con firma digitale XAdES eccetera.
Nell'esempio è una fattura "semplice" (codice 'a')
marchiofile:=g_codificanumerofatturaelettronica('a',fatturaanno,fatturanumero);
riceventepec:='';
riceventecodicedestinatario:='000000';
if i_dataset.fieldbyname('felpecdestinatario').asstring<>'' then
begin
riceventepec:=i_dataset.fieldbyname('felpecdestinatario').asstring;
riceventecodicedestinatario:='0000000';
end;
(...) inizializzazioni varie
/// sceglie se usare il campo DITTA oppure cognome+nome
if (i_dataset.fieldbyname('ditta').AsString='')
AND
((i_dataset.fieldbyname('felcognome').AsString='') or (i_dataset.FieldByName('felnome').Asstring='')) then
begin
loggaNow('errore 12684: ditta / cognome+nome vuoto');
Exit;
end;
if i_dataset.fieldbyname('ditta').AsString<>'' then
begin
riceventedenominazione:=i_dataset.fieldbyname('ditta').AsString;
end
else
begin
riceventenome:=i_dataset.fieldbyname('felnome').asstring;
riceventecognome:=i_dataset.fieldbyname('felcognome').asstring;
riceventedenominazione:='';
end;
if i_dataset.fieldbyname('codicefiscale').AsString='' then
begin
loggaNow('Errore 1735: per le fatture elettroniche il codicefiscale ci vuole sempre');
Exit;
end;
(...) check vari
(...)check su modalità MP05 (bonifico) senza beneficiario, check su IBAN eccetera
/// prende le righe del documento, cioè fa il join esplicito tra testata e righe. normalmente prenderebbe anche i totali (per i casi con IVA diversa nello stesso documento eccetera. è sempre un esempio)
zrighe:=TZQuery.create(nil);
zrighe.connection:=datdati.zconn;
ZXSQL(zrighe,'select * from ridocumenti where idlinkrighe="'+i_dataset.fieldbyname('idlink').asstring+'"');
/// qui inizia a generare la fattura vera e propria dentro la variabile "fattura"
Nulla ti vieterebbe di scrivere, brutalmente, dentro un file di testo, una riga alla volta.
Per Delphi la cosa "bella" è che, alla fine, con una singola istruzione si scrive in un colpo solo tutto quanto.
Nota: gli offset che vedi (0,4,8...) sono le spaziature-tab identiche a quelle dell'esempio della fattura Agenzia.
Configurando opportunamente i parametri salta fuori un file uguale binariamente all'esempio
fattura:=tstringlist.create;
g_fatturaScriviHeader(fattura);
g_fatturaScriviRiga(0,fattura,'<FatturaElettronicaHeader>');
g_fatturaScriviRiga(4,fattura,'<DatiTrasmissione>');
g_fatturaScriviRiga(8,fattura,'<IdTrasmittente>');
g_fatturaScriviTag(12,fattura,'IdPaese','IT');
g_fatturaScriviTag(12,fattura,'IdCodice',i_trasmittentecf);
g_fatturaScriviRiga(8,fattura,'</IdTrasmittente>');
g_fatturaScriviTag(8,fattura,'ProgressivoInvio',marchiofile);
g_fatturaScriviTag(8,fattura,'FormatoTrasmissione','FPR12'); /// attenzione sempre hardcoded PRivati
g_fatturaScriviTag(8,fattura,'CodiceDestinatario',riceventecodicedestinatario);
g_fatturaScriviRiga(8,fattura,'<ContattiTrasmittente>');
g_fatturaScriviTag(12,fattura,'Telefono',i_trasmittentetelefono);
g_fatturaScriviTag(12,fattura,'Email',i_trasmittenteemail);
g_fatturaScriviRiga(8,fattura,'</ContattiTrasmittente>');
g_fatturaScriviTag(8,fattura,'PECDestinatario',riceventepec);
g_fatturaScriviRiga(4,fattura,'</DatiTrasmissione>');
Compila essenzialmente le righe di testo talvolta usando i parametri in ingresso della funzione, altre dai dati presi dal database (cioè dal documento).
La discrasia è riferita al fatto di poter inviare in modo disgiunto (il commercialista invia i documenti del cliente)
g_fatturaScriviRiga(4,fattura,'<CedentePrestatore>');
g_fatturaScriviRiga(8,fattura,'<DatiAnagrafici>');
g_fatturaScriviRiga(12,fattura,'<IdFiscaleIVA>');
g_fatturaScriviTag(16,fattura,'IdPaese','IT');
g_fatturaScriviTag(16,fattura,'IdCodice',i_trasmittentepiva);
g_fatturaScriviRiga(12,fattura,'</IdFiscaleIVA>');
g_fatturaScriviTag(12,fattura,'CodiceFiscale',i_trasmittentecf);
g_fatturaScriviRiga(12,fattura,'<Anagrafica>');
if i_trasmittentedenominazione<>'' then
g_fatturaScriviTag(16,fattura,'Denominazione',i_trasmittentedenominazione)
else
begin
g_fatturaScriviTag(16,fattura,'Nome',i_trasmittentenome);
g_fatturaScriviTag(16,fattura,'Cognome',i_trasmittentecognome);
end;
g_fatturaScriviRiga(12,fattura,'</Anagrafica>');
g_fatturaScriviTag(12,fattura,'RegimeFiscale',i_trasmittenteregimefiscale);
g_fatturaScriviRiga(8,fattura,'</DatiAnagrafici>');
g_fatturaScriviRiga(8,fattura,'<Sede>');
g_fatturaScriviTag(12,fattura,'Indirizzo',i_trasmittenteindirizzo);
g_fatturaScriviTag(12,fattura,'CAP',i_trasmittentecap);
g_fatturaScriviTag(12,fattura,'Comune',i_trasmittentecomune);
g_fatturaScriviTag(12,fattura,'Provincia',i_trasmittenteprovincia);
g_fatturaScriviTag(12,fattura,'Nazione','IT');
g_fatturaScriviRiga(8,fattura,'</Sede>');
g_fatturaScriviRiga(4,fattura,'</CedentePrestatore>');
g_fatturaScriviRiga(4,fattura,'<CessionarioCommittente>');
g_fatturaScriviRiga(8,fattura,'<DatiAnagrafici>');
if riceventepiva<>'' then
begin
g_fatturaScriviRiga(12,fattura,'<IdFiscaleIVA>');
g_fatturaScriviTag(16,fattura,'IdPaese','IT');
g_fatturaScriviTag(16,fattura,'IdCodice',riceventepiva);
g_fatturaScriviRiga(12,fattura,'</IdFiscaleIVA>');
end;
g_fatturaScriviTag(12,fattura,'CodiceFiscale',riceventecf);
C'è la solita manfrina tra ditta/denominazione e nome+cognome (eventualmente titolo)
g_fatturaScriviRiga(12,fattura,'<Anagrafica>');
if riceventedenominazione<>'' then
g_fatturaScriviTag(16,fattura,'Denominazione',riceventedenominazione)
else
begin
g_fatturaScriviTag(16,fattura,'Nome',riceventenome);
g_fatturaScriviTag(16,fattura,'Cognome',riceventecognome);
end;
g_fatturaScriviRiga(12,fattura,'</Anagrafica>');
g_fatturaScriviRiga(8,fattura,'</DatiAnagrafici>');
g_fatturaScriviRiga(8,fattura,'<Sede>');
g_fatturaScriviTag(12,fattura,'Indirizzo',riceventeindirizzo);
g_fatturaScriviTag(12,fattura,'CAP',riceventecap);
g_fatturaScriviTag(12,fattura,'Comune',riceventecomune);
g_fatturaScriviTag(12,fattura,'Provincia',riceventeprovincia);
g_fatturaScriviTag(12,fattura,'Nazione','IT');
g_fatturaScriviRiga(8,fattura,'</Sede>');
g_fatturaScriviRiga(4,fattura,'</CessionarioCommittente>');
g_fatturaScriviRiga(0,fattura,'</FatturaElettronicaHeader>');
Qui, grosso modo, la prima parte della fattura è preparata.
Ora va predisposta la porzione dove ci sono gli importi
g_fatturaScriviRiga(0,fattura,'<FatturaElettronicaBody>');
g_fatturaScriviRiga(4,fattura,'<DatiGenerali>');
g_fatturaScriviRiga(8,fattura,'<DatiGeneraliDocumento>');
g_fatturaScriviTag(12,fattura,'TipoDocumento',i_fatturatipodocumento);
g_fatturaScriviTag(12,fattura,'Divisa','EUR');
g_fatturaScriviTag(12,fattura,'Data',datetodatamysql(fatturadata));
g_fatturaScriviTag(12,fattura,'Numero',inttostr(fatturanumero));
Certi blocchi sono opzionali, ad esempio se c'è ritenuta oppure no eccetera
if prendiCampoBool(i_dataset,'flagritenuta') then
begin
g_fatturaScriviRiga(12,fattura,'<DatiRitenuta>');
g_fatturaScriviTag(16,fattura,'TipoRitenuta','RT01');
g_fatturaScriviTag(16,fattura,'ImportoRitenuta',g_moneyafel(i_dataset.fieldbyname('ritenuta').asfloat));
g_fatturaScriviTag(16,fattura,'AliquotaRitenuta',g_moneyafel(i_dataset.fieldbyname('perritenuta').asinteger));
g_fatturaScriviTag(16,fattura,'CausalePagamento','A');
g_fatturaScriviRiga(12,fattura,'</DatiRitenuta>');
end;
if prendiCampoBool(i_dataset,'flagritenuta') then
begin
g_fatturaScriviRiga(12,fattura,'<DatiCassaPrevidenziale>');
g_fatturaScriviTag(16,fattura,'TipoCassa','TC01');
g_fatturaScriviTag(16,fattura,'AlCassa',g_moneyafel(i_dataset.fieldbyname('percassa').asinteger));
g_fatturaScriviTag(16,fattura,'ImportoContributoCassa',g_moneyafel(i_dataset.fieldbyname('cp').asfloat));
g_fatturaScriviTag(16,fattura,'ImponibileCassa',g_moneyafel(i_dataset.fieldbyname('cassa').asfloat));
g_fatturaScriviTag(16,fattura,'AliquotaIVA',g_moneyafel(i_dataset.fieldbyname('periva').asinteger));
g_fatturaScriviRiga(12,fattura,'</DatiCassaPrevidenziale>');
end;
g_fatturaScriviTag(12,fattura,'ImportoTotaleDocumento',g_moneyafel(i_dataset.fieldbyname('totaledocumento').asfloat));
if i_dataset.fieldbyname('testo').asstring<>'' then
g_fatturaScriviTag(12,fattura,'Causale',g_testoafel(i_dataset.fieldbyname('testo').asstring));
g_fatturaScriviRiga(8,fattura,'</DatiGeneraliDocumento>');
g_fatturaScriviRiga(4,fattura,'</DatiGenerali>');
Chiaramente questa parte (i $$$) dipende fortemente dal tipo di documento, qui ho preso una versione semplificate.
Ce ne sono caterve (di possibilità)
In questo caso semplicemente cicla per ogni riga (del documento sul db)
g_fatturaScriviRiga(4,fattura,'<DatiBeniServizi>');
if isDataSetOK(zrighe) then
begin
with zrighe do
begin
First;
while not Eof do
begin
g_fatturaScriviRiga(8,fattura,'<DettaglioLinee>');
g_fatturaScriviTag(12,fattura,'NumeroLinea',inttostr(numerolinea));
g_fatturaScriviTag(12,fattura,'Descrizione',g_testoafel(zrighe.fieldbyname('descrizione').asstring));
g_fatturaScriviTag(12,fattura,'Quantita',g_moneyafel(zrighe.fieldbyname('quantita').asfloat));
if zrighe.fieldbyname('umm').asstring<>'' then
g_fatturaScriviTag(12,fattura,'UnitaMisura',g_testoafel(zrighe.fieldbyname('umm').asstring));
if not zrighe.fieldbyname('data').isnull then
begin
g_fatturaScriviTag(12,fattura,'DataInizioPeriodo',datetodatamysql(zrighe.fieldbyname('data').asdatetime));
g_fatturaScriviTag(12,fattura,'DataFinePeriodo',datetodatamysql(zrighe.fieldbyname('data').Asdatetime));
end;
g_fatturaScriviTag(12,fattura,'PrezzoUnitario',g_moneyafel(zrighe.fieldbyname('imponibile').asfloat));
g_fatturaScriviTag(12,fattura,'PrezzoTotale',g_moneyafel(zrighe.fieldbyname('totaleimponibile').asfloat));
g_fatturaScriviTag(12,fattura,'AliquotaIVA',g_moneyafel(zrighe.fieldbyname('perivadiversa').asinteger));
g_fatturaScriviRiga(8,fattura,'</DettaglioLinee>');
inc(numerolinea);
Next;
end;
end;
end
else //nessuna linea = 1 linea
begin
g_fatturaScriviRiga(8,fattura,'<DettaglioLinee>');
g_fatturaScriviTag(12,fattura,'NumeroLinea','1');
g_fatturaScriviTag(12,fattura,'Descrizione',g_testoafel(i_dataset.fieldbyname('testo').asstring));
g_fatturaScriviTag(12,fattura,'Quantita','1.00');
g_fatturaScriviTag(12,fattura,'PrezzoUnitario',g_moneyafel(i_dataset.fieldbyname('totaleimponibile').asfloat));
g_fatturaScriviTag(12,fattura,'PrezzoTotale',g_moneyafel(i_dataset.fieldbyname('totaleimponibile').asfloat));
g_fatturaScriviTag(12,fattura,'AliquotaIVA',g_moneyafel(i_dataset.fieldbyname('periva').asinteger));
g_fatturaScriviRiga(8,fattura,'</DettaglioLinee>');
end;
Sopra c'è una particolarità: deve sempre esistere ALMENO una riga di dettaglio.
Quando non c'è (e nel mio gestionale potrebbe non esserci) crea una singola riga di dettaglio di quantità "1" e importo pari al totale.
Si continua a seconda dei casi: c'è l'IVA?
if prendiCampoBool(i_dataset,'flagiva') then
begin
g_fatturaScriviRiga(8,fattura,'<DatiRiepilogo>');
g_fatturaScriviTag(12,fattura,'AliquotaIVA',g_moneyafel(i_dataset.fieldbyname('periva').asinteger));
g_fatturaScriviTag(12,fattura,'ImponibileImporto',g_moneyafel(i_dataset.fieldbyname('totaleimponibile').asfloat));
g_fatturaScriviTag(12,fattura,'Imposta',g_moneyafel(i_dataset.fieldbyname('iva').asfloat));
g_fatturaScriviTag(12,fattura,'EsigibilitaIVA','I');
g_fatturaScriviRiga(8,fattura,'</DatiRiepilogo>');
end;
g_fatturaScriviRiga(4,fattura,'</DatiBeniServizi>');
(...)
E tante altre belle cose che puoi vedere nel file XLS (della documentazione)
Verso la fine la BASILARE modalità di pagamento
g_fatturaScriviRiga(4,fattura,'<DatiPagamento>');
g_fatturaScrivitag(8,fattura,'CondizioniPagamento','TP02');
if not i_dataset.fieldbyname('felmodalitapagamento').isnull then
begin
g_fatturaScriviRiga(8,fattura,'<DettaglioPagamento>');
g_fatturaScriviTag(12,fattura,'Beneficiario',g_testoafel(i_dataset.fieldbyname('felbeneficiario').asstring));
g_fatturaScriviTag(12,fattura,'ModalitaPagamento',g_testoafel(i_dataset.fieldbyname('felmodalitapagamento').asstring));
g_fatturaScriviTag(12,fattura,'ImportoPagamento',g_moneyafel(i_dataset.fieldbyname('totaledacorrispondere').asfloat));
if i_dataset.fieldbyname('felmodalitapagamento').asstring='MP05' then
begin
g_fatturaScriviTag(12,fattura,'IstitutoFinanziario',g_testoafel(i_dataset.fieldbyname('felistitutofinanziario').asstring));
g_fatturaScriviTag(12,fattura,'IBAN',g_testoafel(i_dataset.fieldbyname('feliban').asstring));
end;
g_fatturaScriviRiga(8,fattura,'</DettaglioPagamento>');
end;
g_fatturaScriviRiga(4,fattura,'</DatiPagamento>');
g_fatturaScriviRiga(0,fattura,'</FatturaElettronicaBody>');
g_fatturaScriviRiga(0,fattura,'</p:FatturaElettronica>');
Bon, è fatta, va solo scritto il "pacco"
nomefile:=i_cartella+'IT'+uppercase(i_trasmittentecf)+'_'+marchiofile+'.xml';
SpacchettaDLL(i_cartella+'fatturaordinaria_v1.2.1.xsl','fel');
fattura.SaveToFile(nomefile);
nel mio caso si crea il nome del file marchiato (cioè con la codifica base62 di anno e numero).
Si "appiccica" anche l'XSL (per generare la versione PDF)
nomepdf:=i_cartella+'IT'+uppercase(i_trasmittentecf)+'_'+marchiofile+'_'+format('%.04d_%.04d',[i_dataset.fieldbyname('anno').asinteger,i_dataset.fieldbyname('progressivo').asinteger])+'.pdf';
g_xmltopdf(nomefile,i_cartella+'fatturaordinaria_v1.2.1.xsl',nomepdf);
E' una mia funzione che da XML (e XSL) crea il PDF
Il quale può essere "appiccicato" mime64 nello XML o spedito a parte eccetera
if FileExists(nomefile) then
result:=nomefile;
fattura.Free;
end;