#!/usr/bin/env node /** * Inspect a PDF and report whether it carries an embedded ZUGFeRD / Factur-X * e-invoice. Pure byte inspection — no dependencies, works on any PDF. * * node scripts/inspect-zugferd.mjs path/to/invoice.pdf * * The markers below (the attachment name, the AFRelationship, the Factur-X XMP) * are stored uncompressed in the PDF, so a plain text scan is reliable. If the * required markers are missing, the file is a plain PDF — i.e. the merge never * ran for that invoice (partner flag off / iDempiere column missing), or an * upstream tool stripped the attachment. */ import { readFileSync } from 'node:fs' const path = process.argv[2] if (!path) { console.error('Usage: node scripts/inspect-zugferd.mjs ') process.exit(1) } let buf try { buf = readFileSync(path) } catch (e) { console.error(`Cannot read ${path}: ${e.message}`) process.exit(1) } // latin1 keeps every byte 1:1 so the regexes match the raw PDF tokens. const txt = buf.toString('latin1') const REQUIRED = 'required' const checks = [ ['Embedded file named factur-x.xml', /factur-x\.xml/, REQUIRED], ['Associated-Files entry (/AF)', /\/AF[\s/\[]/, REQUIRED], ['AFRelationship = Alternative', /\/AFRelationship\s*\/Alternative/, REQUIRED], ['EmbeddedFile stream', /\/EmbeddedFile\b/, REQUIRED], ['EmbeddedFiles name tree', /\/EmbeddedFiles\b/, ''], ['Factur-X XMP (fx:DocumentType)', /fx:DocumentType/, ''], ['Factur-X XMP namespace (urn:factur-x)', /urn:factur-x/, ''], ['EN16931 profile in XML/XMP', /en16931|EN 16931/i, ''], ['PDF/A id (pdfaid:part)', /pdfaid:part/, ''], ] console.log(`\nFile: ${path} (${buf.length.toLocaleString()} bytes)\n`) let missingRequired = false for (const [label, re, level] of checks) { const found = re.test(txt) if (level === REQUIRED && !found) missingRequired = true const tag = level === REQUIRED ? '' : ' (optional)' console.log(` ${found ? '✅' : '❌'} ${label}${tag}`) } console.log('') if (missingRequired) { console.log('❌ NOT an e-invoice: this is a plain PDF — no ZUGFeRD/Factur-X XML is embedded.') console.log(' → The merge did not run. Check the partner\'s IsZugPferdInvoice flag and that') console.log(' the iDempiere c_bpartner.IsZugPferdInvoice column exists. See the upload logs') console.log(' for a "[ZUGFeRD] Embedded factur-x.xml ..." line.\n') process.exit(2) } console.log('✅ This PDF carries an embedded ZUGFeRD/Factur-X e-invoice (factur-x.xml + /Alternative).') console.log(' If a recipient still rejects it as "not an e-invoice", the cause is strict') console.log(' PDF/A-3 conformance (the documented v1 limitation), not a missing attachment.\n') process.exit(0)