Leggere dettaglio linee da fatture elettroniche con xmltodict

In questo articolo avevamo visto come esportare in csv i dati di intestazione di una fattura elettronica per poi poterla leggere con Excel.

Il programma utilizzava la libreria eTree per scorrere i valori per poi scriverli su un file di testo csv.

Il problema si pone sulle righe di dettaglio, che naturalmente possono essere più di una.

Qui il problema principale è come accedervi ma anche come successivamente scriverle nel file in uscita.

Utilizzando una libreria diversa, xmltodict possiamo accedere direttamente agli oggetti dell’xml in vari formati o utilizzando degli xpath.

Nel nostro caso ho scelto di scorrere il dictionary creato e individuare il tipo di valore ottenuto dalle singole interrogazioni.

Siccome gli oggetti del dictionary sono raggruppati in liste o liste di dictionary, parlando per esempio dei valori DettagliLinee, della fatturazione elettronica.

Possiamo avere un dictionary con all’interno i campi di dettaglio di una sola riga oppure una lista di dictionary nel caso di fatture con più righe.

L’idea è quindi di ricreare una struttura Python che contenga ogni fattura in un oggetto lista, dove ogni valore di DettaglioLinee sia contenuto in un dictionary facilmente accessibile.

 

#!/usr/bin/env python3

import os, glob
import xmltodict

DatiFatture = []
StrutturaCampi = []

# Creiamo una struttura dei campi che ci interessa esportare seguendo la struttura xml, se i campi sono composti da più righe,
# come nel caso degli articoli, prendiamo il primo campo 'padre' necessario (DettaglioLinee) il programma si occuperà di prendere
# tutti i sottocampi contenuti.

StrutturaCampi = [['FatturaElettronicaHeader', 'DatiTrasmissione', 'ProgressivoInvio'],
                  ['FatturaElettronicaHeader', 'DatiTrasmissione', 'CodiceDestinatario'],
                  ['FatturaElettronicaHeader', 'CedentePrestatore', 'DatiAnagrafici', 'Anagrafica', 'Denominazione'],
                  ['FatturaElettronicaHeader', 'CessionarioCommittente', 'Sede', 'Indirizzo'],
                  ['FatturaElettronicaBody', 'DatiPagamento', 'CondizioniPagamento'],
                  ['FatturaElettronicaBody', 'DatiBeniServizi', 'DettaglioLinee']]

# Qui indichiamo la certella con i file XML da elaborare
path = "C:\\Users\\user\\Downloads"

# Leggiamo tutti i file
for filename in glob.glob(os.path.join(path, "*.xml")):
    Fattura = []

    with open(filename) as fd:
        doc = xmltodict.parse(fd.read())
        # Prendiamo la chiave radice principale in automatico (es.: p:FatturaElettronica or ns1:FatturaElettronica)
        root = list(doc.keys())[0]

        # Facciamo il parse della struttura campi
        for fieldpath in StrutturaCampi:
            fieldname = ''

            # Prepariamo la stringa da interrogare successivamente con il metodo eval
            string_2_eval = 'doc["' + root + '"]'
            row_dict = {}

            for field in fieldpath:
                # memorizziamo il nome dell'ultimo campo della lista (Es.ProgressivoInvio):
                fieldname = fieldpath[-1]
                # accodiamo il percorso per andare a recuperare il valore nell'xml
                string_2_eval += '["' + field.replace('\n', '') + '"]'

            # Verifichiamo se il valore esiste e che non dia errori
            try:
                value = eval(string_2_eval)
            except:
                value = False

            # Verifichiamo che tipo di dato abbiamo:
            # se è un dictionary vuol dire che è una fattura con una sola riga di dettaglio
            if isinstance(value, dict) and value:
                print("E' UN DICTIONARY !")
                for k, v in value.items():
                    row_dict[k] = v
                Fattura.append(('Righe fattura', row_dict))

            # se è una lista vuol dire che è una fattura con una più righe di dettaglio
            elif isinstance(value, list) and value:
                print("E' UNA LISTA !")
                for l in value:
                    for k, v in l.items():
                        row_dict[k] = v
                    Fattura.append(('Righe fattura', row_dict))
                    row_dict = {}
            # se è un valore vuol dire che è un valore singolo
            elif not isinstance(value, dict) and value:
                print("VALORE NORMALE")
                Fattura.append((fieldname, value))
            # se è un valore ma non contiene dati aggiungiamo comunque il campo vuoto per non perdere la formattazione delle colonne nel file in uscita
            elif not isinstance(value, dict) and not value:
                Fattura.append((fieldname, ''))

        # Fine lettura campi fattura
        # A questo punto in Fattura[] abbiamo la lista di tutti i campi che ci interessano, dove ogni valore di 'Righe Fattura' conterrà
        # il dictionary con i campi della riga.

        # Volendo possiamo accodare a un'altra lista che conterrà tutte le fatture in formato lista.
        DatiFatture.append(Fattura)


print('Dati fatture')
print('------------')
print(DatiFatture)

OUTPUT SPIEGATO:

Prima fattura
[
[('ProgressivoInvio', '00002'), ('CodiceDestinatario', 'XXXXXX'), ('Denominazione', 'Roberto Zanardo'), ('Indirizzo', 'Via dei Tigli,23'), ('CondizioniPagamento', 'TP02'), 

Questa è una tuple che contiene come primo dato il nome del campo(Riga fattura) e come secondo dato il dictionary con tutte le chiavi/valori della riga.
('Riga fattura', {'NumeroLinea': '1', 'Descrizione': 'CONSULENZA/SVILUPPO SOFTWARE ODOO', 'Quantita': '1.00', 'PrezzoUnitario': '1600.00', 'PrezzoTotale': '1600.00', 'AliquotaIVA': '1.00', 'Natura': 'N2'}), ('Riga fattura', {'NumeroLinea': '2', 'Descrizione': 'cassa previdenziale 4', 'Quantita': '1.00', 'PrezzoUnitario': '64.00', 'PrezzoTotale': '64.00', 'AliquotaIVA': '2.00', 'Natura': 'N2'})

]
Fine prima fattura
,

Seconda fattura
[

('ProgressivoInvio', '00002'), ('CodiceDestinatario', 'SUXXXN'), ('Denominazione', 'Roberto Zanardo'), ('Indirizzo', 'Via Alpi 34'), ('CondizioniPagamento', 'TP02'), 

Questa è la tuple della prima riga che contiene come primo dato il nome del campo(Riga fattura) e come secondo dato il dictionary con tutte le chiavi/valori della riga.
('Riga fattura', {'NumeroLinea': '1', 'Descrizione': 'ORE CONSULENZA E SVILUPPO SOFTWARE', 'Quantita': '1.00', 'PrezzoUnitario': '1200.00', 'PrezzoTotale': '1200.00', 'AliquotaIVA': '0.00', 'Natura': 'N2'}),

Questa è la tuple della prima seconda che contiene come primo dato il nome del campo(Riga fattura) e come secondo dato il dictionary con tutte le chiavi/valori della riga.

('Riga fattura', {'NumeroLinea': '2', 'Descrizione': 'Spese', 'Quantita': '1.00', 'PrezzoUnitario': '48.00', 'PrezzoTotale': '48.00', 'AliquotaIVA': '0.00', 'Natura': 'N2'})],

]
Fine seconda fattura

Da questo punto sarà quindi necessario rileggere la struttura creata per disporla nel file di output come preferiamo.