DEV Community

Giuseppe Serio
Giuseppe Serio

Posted on

Genera la tua prima fattura elettronica XML per lo SDI in TypeScript — in 10 minuti

Genera la tua prima fattura elettronica XML per lo SDI in TypeScript (in 10 minuti)

Se hai mai dovuto integrare la fatturazione elettronica italiana in un progetto Node.js, sai già quanto è scomodo: specifiche FatturaPA di 200 pagine, regole cross-field non documentate, codici errore SDI criptici, e librerie npm o abbandonate o in PHP.

Questo articolo mostra come generare un XML valido per il Sistema di Interscambio (SDI) usando fattura-elettronica-sdi-builder, una libreria TypeScript open-source che copre B2B (FPR12) e Pubblica Amministrazione (FPA12).


Installazione

npm install fattura-elettronica-sdi-builder
Enter fullscreen mode Exit fullscreen mode

Nessuna dipendenza pesante. La validazione è custom e tipizzata, zero runtime esterni.


Il flusso in tre funzioni

La libreria espone tre funzioni pubbliche che si usano sempre in sequenza:

import { applyDefaults, validate, buildXml } from 'fattura-elettronica-sdi-builder';
Enter fullscreen mode Exit fullscreen mode
Funzione Input Output
applyDefaults(input) FatturaElettronicaInput (campi deducibili opzionali) FatturaElettronica completa
validate(fattura) FatturaElettronica Result<void, ValidationError>
buildXml(fattura, options?) FatturaElettronica Result<string, BuildError>

Tutte le funzioni restituiscono un Result<T, E> — mai eccezioni non gestite:

type Result<T, E> =
  | { ok: true;  value: T }
  | { ok: false; error: E }
Enter fullscreen mode Exit fullscreen mode

Esempio completo: fattura B2B con IVA ordinaria

Genera una fattura TD01 da una Srl italiana a un cliente italiano, IVA al 22%, pagamento con bonifico.

import { applyDefaults, validate, buildXml } from 'fattura-elettronica-sdi-builder';
import type { FatturaElettronicaInput } from 'fattura-elettronica-sdi-builder';
import { writeFileSync } from 'fs';

const input: FatturaElettronicaInput = {
  FatturaElettronicaHeader: {
    DatiTrasmissione: {
      ProgressivoInvio:   '00001',
      CodiceDestinatario: 'ABC1234', // 7 caratteri per FPR12
    },
    CedentePrestatore: {
      DatiAnagrafici: {
        IdFiscaleIVA:  { IdPaese: 'IT', IdCodice: '01234567890' },
        Anagrafica:    { Denominazione: 'La Mia Azienda Srl' },
        RegimeFiscale: 'RF01',
      },
      Sede: {
        Indirizzo: 'Via Roma 1',
        CAP:       '00100',
        Comune:    'Roma',
        Provincia: 'RM',
        Nazione:   'IT',
      },
    },
    CessionarioCommittente: {
      DatiAnagrafici: {
        IdFiscaleIVA: { IdPaese: 'IT', IdCodice: '09876543210' },
        Anagrafica:   { Denominazione: 'Cliente Spa' },
      },
      Sede: {
        Indirizzo: 'Via Milano 10',
        CAP:       '20100',
        Comune:    'Milano',
        Provincia: 'MI',
        Nazione:   'IT',
      },
    },
  },
  FatturaElettronicaBody: {
    DatiGenerali: {
      DatiGeneraliDocumento: {
        TipoDocumento: 'TD01',
        Numero:        'FT-2026-001',
        // Divisa e Data sono opzionali: applyDefaults li imposta a 'EUR' e oggi
      },
    },
    DatiBeniServizi: {
      DettaglioLinee: [
        {
          NumeroLinea:    1,
          Descrizione:    'Servizio di consulenza',
          Quantita:       1,
          PrezzoUnitario: 100.00,
          PrezzoTotale:   100.00,
          AliquotaIVA:    22,
        },
      ],
      DatiRiepilogo: [
        {
          AliquotaIVA:       22,
          ImponibileImporto: 100.00,
          Imposta:           22.00, // opzionale: applyDefaults lo calcola
          EsigibilitaIVA:    'I',
        },
      ],
    },
    DatiPagamento: [
      {
        CondizioniPagamento: 'TP02',
        DettaglioPagamento: [
          {
            ModalitaPagamento:     'MP05', // bonifico
            DataScadenzaPagamento: '2026-06-18',
            ImportoPagamento:      122.00,
            IBAN:                  'IT60X0542811101000000123456',
          },
        ],
      },
    ],
  },
};

// 1. Deduce i valori opzionali
const fattura = applyDefaults(input);

// 2. Valida
const validation = validate(fattura);
if (!validation.ok) {
  validation.error.fields.forEach(({ code, field, message }) => {
    console.error(`[${code}] ${field}: ${message}`);
  });
  process.exit(1);
}

// 3. Genera XML
const build = buildXml(fattura, { prettyPrint: true });
if (!build.ok) {
  console.error(build.error.message);
  process.exit(1);
}

// Naming convention SDI: {IdPaese}{IdCodice}_{Formato}_{Progressivo}.xml
writeFileSync('IT01234567890_FPR12_00001.xml', build.value, 'utf-8');
console.log('Fattura generata.');
Enter fullscreen mode Exit fullscreen mode

Nota: IdTrasmittente e FormatoTrasmissione sono omessi — applyDefaults li deduce automaticamente da CedentePrestatore.


I default che applyDefaults gestisce per te

Uno dei problemi più fastidiosi della FatturaPA è la quantità di campi che si possono dedurre ma che comunque vanno valorizzati nell'XML. applyDefaults li gestisce:

Campo omesso Valore dedotto
IdTrasmittente Copiato da CedentePrestatore.DatiAnagrafici.IdFiscaleIVA
FormatoTrasmissione "FPR12"
CodiceDestinatario "XXXXXXX" se cliente estero
Divisa "EUR"
Data Data odierna YYYY-MM-DD
DatiRiepilogo.Imposta Calcolato da ImponibileImporto × AliquotaIVA / 100

Gestione degli errori SDI

La validazione restituisce errori tipizzati con i codici errore SDI ufficiali. Esempio: se CodiceDestinatario ha lunghezza sbagliata per il formato scelto, o se AliquotaIVA = 0 senza Natura, o se PrezzoTotale non corrisponde al calcolo atteso (SDI 00423):

const validation = validate(fattura);

if (!validation.ok) {
  validation.error.fields.forEach(({ code, field, message }) => {
    console.error(`[${code}] ${field}: ${message}`);
    // Esempio: [MISSING_NATURA] DatiBeniServizi.DettaglioLinee[0].Natura: ...
    // Esempio: [INVALID_VALUE]  DatiTrasmissione.CodiceDestinatario: ...
  });
}
Enter fullscreen mode Exit fullscreen mode

I codici errore sono tipizzati (ErrorCode) — puoi fare switch su di essi senza magic string.


Tre casi d'uso in 30 righe

Fattura PA (FPA12)

DatiTrasmissione: {
  FormatoTrasmissione: 'FPA12',
  CodiceDestinatario:  'ABCDE1', // 6 caratteri: codice IPA
  ProgressivoInvio:    '00001',
},
Enter fullscreen mode Exit fullscreen mode

Cliente estero

// CodiceDestinatario deve essere 'XXXXXXX' se cliente non IT
DatiTrasmissione: {
  CodiceDestinatario: 'XXXXXXX',
},
CessionarioCommittente: {
  DatiAnagrafici: {
    IdFiscaleIVA: { IdPaese: 'DE', IdCodice: 'DE123456789' },
    Anagrafica:   { Denominazione: 'German GmbH' },
  },
  Sede: { Indirizzo: 'Hauptstrasse 1', CAP: '10115', Comune: 'Berlin', Nazione: 'DE' },
},
Enter fullscreen mode Exit fullscreen mode

Operazione esente IVA (es. prestazione medica)

DettaglioLinee: [{
  NumeroLinea:    1,
  Descrizione:    'Prestazione medica',
  PrezzoUnitario: 200.00,
  PrezzoTotale:   200.00,
  AliquotaIVA:    0,
  Natura:         'N4', // obbligatorio quando AliquotaIVA = 0
}],
DatiRiepilogo: [{
  AliquotaIVA:          0,
  Natura:               'N4',
  ImponibileImporto:    200.00,
  Imposta:              0,
  RiferimentoNormativo: 'art. 10 DPR 633/72',
}],
Enter fullscreen mode Exit fullscreen mode

Tipi di documento supportati

La libreria copre tutti i TipoDocumento da TD01 a TD29, incluse autofatture (TD16–TD23), fatture differite (TD24/TD25), parcelle con ritenuta (TD06), e fatture semplificate (TD07–TD09). Per ognuno vengono applicate le regole cross-field specifiche delle specifiche SDI v1.7.1.


Link

Se lo stai usando o valutando, apri una issue o scrivi a gserio95@gmail.com. Qualsiasi feedback è utile.

Top comments (0)