Come correggere l'errore di codifica UTF-8 del SAF-T in Portogallo
Perché l'AT rifiuta silenziosamente i file SAF-T XML dichiarati come UTF-8 e la correzione di una sola riga che li fa passare.
L'errore che non vedi
Carichi il tuo SAF-T XML mensile all'AT, il portale mostra "ficheiro inválido" e nient'altro. Niente numero di riga. Nessuna motivazione. Il consiglio standard è riesportare dall'ERP e reinviare. Lo fai. Continua a fallire.
Se il tuo ERP è Xero, QuickBooks, Datev o un export SAP generico, è quasi certamente lo stesso bug: il tuo file è codificato in UTF-8, ma l'AT accetta solo Windows-1252.
Perché l'AT rifiuta UTF-8 in silenzio
La specifica SAF-T (PT) dell'AT, schema attuale 1.04_01, impone Windows-1252 (CP-1252) come codifica del file. Il validatore dell'AT non restituisce un errore utile quando la codifica è sbagliata — il file viene semplicemente rifiutato all'ingresso. L'XML è altrimenti ben formato; lo schema passa i controlli. Ma la codifica a livello di byte dei caratteri accentati (ç, ã, é, ó) non corrisponde a ciò che si aspettano gli strumenti a valle dell'AT.
Gli ERP stranieri usano UTF-8 di default perché UTF-8 è lo standard moderno ovunque tranne in questa specifica pipeline fiscale portoghese. Scrivono il prologo XML così:
<?xml version="1.0" encoding="UTF-8"?>
L'AT si aspetta:
<?xml version="1.0" encoding="Windows-1252"?>
Perché modificare solo la dichiarazione non funziona
Il primo istinto è aprire il file e sostituire UTF-8 con Windows-1252 nel prologo, salvare e reinviare. Questo peggiora le cose. La dichiarazione ora dice Windows-1252 ma i byte sono ancora UTF-8. Qualunque carattere fuori dall'ASCII viene letto come spazzatura quando l'AT (o chiunque altro) legge il file con la codifica dichiarata.
La correzione che funziona davvero
Devi fare due cose, nell'ordine giusto:
- Transcodifica i byte del corpo da UTF-8 a Windows-1252. Ogni Á (0xC3 0x81 in UTF-8) diventa un singolo byte 0xC1. Ogni ç (0xC3 0xA7) diventa 0xE7. E così via.
- Riscrivi la dichiarazione di codifica per farla corrispondere:
encoding="Windows-1252".
Se salti il passo 1, ottieni spazzatura. Se salti il passo 2, l'AT continua a rifiutare.
Farlo in PHP
$xml = file_get_contents('saft.xml');
$converted = mb_convert_encoding($xml, 'Windows-1252', 'UTF-8');
$converted = preg_replace(
'/(<\?xml[^>]*encoding=)["\'][^"\']+["\']/i',
'$1"Windows-1252"',
$converted,
1,
);
file_put_contents('saft.fixed.xml', $converted);
Farlo con un clic
SAFTCheck fa esattamente questa trasformazione, in più rimuove eventuali byte BOM UTF-8 (un'altra causa di rifiuto silenzioso), convalida il file corretto contro lo schema AT 1.04_01 e ti consegna un XML pulito scaricabile. Gratis per una correzione; €7 per la correzione a pagamento con report completo e PDF.
Come confermare che la correzione abbia preso
Dopo la conversione, esegui file saft.fixed.xml in un terminale Unix — dovrebbe restituire "Non-ISO extended-ASCII text" o "ISO-8859 text", non "UTF-8 Unicode". Apri il file in un editor esadecimale: ogni carattere portoghese accentato dovrebbe essere un singolo byte nell'intervallo 0xA0–0xFF, non una sequenza di due byte con prefisso 0xC2/0xC3.
Reinvia il file all'AT. Il "ficheiro inválido" dovrebbe sparire. Se non sparisce, la codifica non era l'unico problema — ma di solito lo è.
Regole collegate che SAFTCheck intercetta nello stesso passaggio
- BOM a inizio file. L'AT rifiuta BOM UTF-8 (0xEF 0xBB 0xBF) e BOM UTF-16.
- Cifra di controllo del NIF. Algoritmo Mod-11; una cifra sbagliata e il file non è valido.
- Formato ATCUD. Obbligatorio dal 2023, deve corrispondere a
^[A-Z0-9]{8}-[1-9]\d{0,9}$. - Date dell'header. StartDate prima di EndDate, FiscalYear corrisponde all'anno di StartDate, EndDate non nel futuro.