@jasy/cli 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/assets/jasy.txt +23 -0
- package/assets/validation/README.md +21 -0
- package/assets/validation/en16931-cii.sef.json.gz +0 -0
- package/assets/validation/en16931-ubl.sef.json.gz +0 -0
- package/assets/validation/xrechnung-cii.sef.json.gz +0 -0
- package/assets/validation/xrechnung-ubl.sef.json.gz +0 -0
- package/dist/commands/export.d.ts +1 -0
- package/dist/commands/export.js +63 -0
- package/dist/commands/read.d.ts +1 -0
- package/dist/commands/read.js +73 -0
- package/dist/commands/validate.d.ts +1 -0
- package/dist/commands/validate.js +102 -0
- package/dist/commands/verapdf.d.ts +1 -0
- package/dist/commands/verapdf.js +96 -0
- package/dist/core/detect.d.ts +11 -0
- package/dist/core/detect.js +33 -0
- package/dist/core/export.d.ts +10 -0
- package/dist/core/export.js +150 -0
- package/dist/core/extract.d.ts +2 -0
- package/dist/core/extract.js +80 -0
- package/dist/core/parse.d.ts +7 -0
- package/dist/core/parse.js +395 -0
- package/dist/core/pdfa.d.ts +12 -0
- package/dist/core/pdfa.js +47 -0
- package/dist/core/read.d.ts +14 -0
- package/dist/core/read.js +25 -0
- package/dist/core/validate.d.ts +18 -0
- package/dist/core/validate.js +65 -0
- package/dist/core/verapdf.d.ts +26 -0
- package/dist/core/verapdf.js +161 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +73 -0
- package/dist/tui/app.d.ts +1 -0
- package/dist/tui/app.js +356 -0
- package/dist/tui/file-open.d.ts +10 -0
- package/dist/tui/file-open.js +140 -0
- package/package.json +41 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { deflateRawSync } from "node:zlib";
|
|
2
|
+
const money = (n) => n.toFixed(2);
|
|
3
|
+
const addressLine = (a) => [a.line1, [a.postCode, a.city].filter(Boolean).join(" "), a.country].filter(Boolean).join(", ");
|
|
4
|
+
/** A flat totals summary, the four amounts a reader cares about. */
|
|
5
|
+
function summary(inv, t) {
|
|
6
|
+
return {
|
|
7
|
+
net: t.taxBasisTotal, // BT-109
|
|
8
|
+
vat: t.taxTotal, // BT-110
|
|
9
|
+
gross: t.grandTotal, // BT-112
|
|
10
|
+
due: t.duePayable, // BT-115
|
|
11
|
+
currency: inv.currency,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Full invoice model + a computed totals block, pretty-printed. */
|
|
15
|
+
export function exportJson(inv, t) {
|
|
16
|
+
return JSON.stringify(Object.assign(Object.assign({}, inv), { totals: summary(inv, t) }), null, 2);
|
|
17
|
+
}
|
|
18
|
+
/** A plain-text receipt - no ANSI, safe to pipe or save. */
|
|
19
|
+
export function exportText(inv, t) {
|
|
20
|
+
const L = [];
|
|
21
|
+
L.push(`Invoice ${inv.number}`);
|
|
22
|
+
L.push(`Date ${inv.issueDate}${inv.dueDate ? ` due ${inv.dueDate}` : ""}`);
|
|
23
|
+
if (inv.buyerReference)
|
|
24
|
+
L.push(`Ref ${inv.buyerReference}`);
|
|
25
|
+
L.push("");
|
|
26
|
+
L.push(`From ${inv.seller.name}`);
|
|
27
|
+
L.push(` ${addressLine(inv.seller.address)}`);
|
|
28
|
+
if (inv.seller.vatId)
|
|
29
|
+
L.push(` VAT ${inv.seller.vatId}`);
|
|
30
|
+
L.push(`To ${inv.buyer.name}`);
|
|
31
|
+
L.push(` ${addressLine(inv.buyer.address)}`);
|
|
32
|
+
L.push("");
|
|
33
|
+
L.push(`${"Qty".padEnd(8)}${"Unit".padEnd(6)}${"Item".padEnd(34)}${"Net".padStart(12)}`);
|
|
34
|
+
inv.lines.forEach((l, i) => {
|
|
35
|
+
L.push(`${String(l.quantity).padEnd(8)}${l.unit.padEnd(6)}${l.name.slice(0, 33).padEnd(34)}${money(t.lineNets[i]).padStart(12)}`);
|
|
36
|
+
});
|
|
37
|
+
L.push("─".repeat(60));
|
|
38
|
+
const s = summary(inv, t);
|
|
39
|
+
L.push(`${"Net".padStart(48)}${money(s.net).padStart(12)}`);
|
|
40
|
+
L.push(`${"VAT".padStart(48)}${money(s.vat).padStart(12)}`);
|
|
41
|
+
L.push(`${`Total ${s.currency}`.padStart(48)}${money(s.gross).padStart(12)}`);
|
|
42
|
+
return L.join("\n") + "\n";
|
|
43
|
+
}
|
|
44
|
+
// ── minimal ZIP container (for the .xlsx); each part deflated via zlib ──────────────────────────────
|
|
45
|
+
function crc32(buf) {
|
|
46
|
+
let c = ~0;
|
|
47
|
+
for (let i = 0; i < buf.length; i++) {
|
|
48
|
+
c ^= buf[i];
|
|
49
|
+
for (let k = 0; k < 8; k++)
|
|
50
|
+
c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
|
|
51
|
+
}
|
|
52
|
+
return ~c >>> 0;
|
|
53
|
+
}
|
|
54
|
+
function zip(files) {
|
|
55
|
+
const locals = [];
|
|
56
|
+
const central = [];
|
|
57
|
+
let offset = 0;
|
|
58
|
+
for (const f of files) {
|
|
59
|
+
const name = Buffer.from(f.name, "utf-8");
|
|
60
|
+
const crc = crc32(f.data); // CRC is always over the *uncompressed* data
|
|
61
|
+
const raw = f.data.length;
|
|
62
|
+
const body = deflateRawSync(f.data); // ZIP method 8 = raw DEFLATE
|
|
63
|
+
const comp = body.length;
|
|
64
|
+
const local = Buffer.alloc(30);
|
|
65
|
+
local.writeUInt32LE(0x04034b50, 0);
|
|
66
|
+
local.writeUInt16LE(20, 4); // version needed
|
|
67
|
+
local.writeUInt16LE(8, 8); // method 8 = deflate
|
|
68
|
+
local.writeUInt32LE(crc, 14);
|
|
69
|
+
local.writeUInt32LE(comp, 18); // compressed size
|
|
70
|
+
local.writeUInt32LE(raw, 22); // uncompressed size
|
|
71
|
+
local.writeUInt16LE(name.length, 26);
|
|
72
|
+
locals.push(local, name, body);
|
|
73
|
+
const cen = Buffer.alloc(46);
|
|
74
|
+
cen.writeUInt32LE(0x02014b50, 0);
|
|
75
|
+
cen.writeUInt16LE(20, 4); // version made by
|
|
76
|
+
cen.writeUInt16LE(20, 6); // version needed
|
|
77
|
+
cen.writeUInt16LE(8, 10); // method 8 = deflate
|
|
78
|
+
cen.writeUInt32LE(crc, 16);
|
|
79
|
+
cen.writeUInt32LE(comp, 20);
|
|
80
|
+
cen.writeUInt32LE(raw, 24);
|
|
81
|
+
cen.writeUInt16LE(name.length, 28);
|
|
82
|
+
cen.writeUInt32LE(offset, 42); // local header offset
|
|
83
|
+
central.push(cen, name);
|
|
84
|
+
offset += local.length + name.length + comp;
|
|
85
|
+
}
|
|
86
|
+
const cd = Buffer.concat(central);
|
|
87
|
+
const eocd = Buffer.alloc(22);
|
|
88
|
+
eocd.writeUInt32LE(0x06054b50, 0);
|
|
89
|
+
eocd.writeUInt16LE(files.length, 8);
|
|
90
|
+
eocd.writeUInt16LE(files.length, 10);
|
|
91
|
+
eocd.writeUInt32LE(cd.length, 12);
|
|
92
|
+
eocd.writeUInt32LE(offset, 16);
|
|
93
|
+
return Buffer.concat([...locals, cd, eocd]);
|
|
94
|
+
}
|
|
95
|
+
/** Escape `& < >` and drop chars illegal in XML 1.0 (everything < 0x20 except tab/LF/CR), one pass. */
|
|
96
|
+
const xmlEsc = (s) => {
|
|
97
|
+
let out = "";
|
|
98
|
+
for (let i = 0; i < s.length; i++) {
|
|
99
|
+
const c = s.charCodeAt(i);
|
|
100
|
+
if (c < 0x20 && c !== 0x09 && c !== 0x0a && c !== 0x0d)
|
|
101
|
+
continue;
|
|
102
|
+
const ch = s[i];
|
|
103
|
+
out += ch === "&" ? "&" : ch === "<" ? "<" : ch === ">" ? ">" : ch;
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
};
|
|
107
|
+
/** A worksheet cell: inline string or bare number. */
|
|
108
|
+
const cell = (ref, v) => typeof v === "number"
|
|
109
|
+
? `<c r="${ref}"><v>${v}</v></c>`
|
|
110
|
+
: `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${xmlEsc(v)}</t></is></c>`;
|
|
111
|
+
const row = (r, cells) => `<row r="${r}">${cells.map((v, i) => cell(`${String.fromCharCode(65 + i)}${r}`, v)).join("")}</row>`;
|
|
112
|
+
/** A minimal single-sheet .xlsx with the invoice header, line items and totals. */
|
|
113
|
+
export function exportXlsx(inv, t) {
|
|
114
|
+
const s = summary(inv, t);
|
|
115
|
+
const rows = [];
|
|
116
|
+
let r = 1;
|
|
117
|
+
rows.push(row(r++, ["Invoice", inv.number]));
|
|
118
|
+
rows.push(row(r++, ["Date", inv.issueDate]));
|
|
119
|
+
if (inv.dueDate)
|
|
120
|
+
rows.push(row(r++, ["Due", inv.dueDate]));
|
|
121
|
+
rows.push(row(r++, ["From", inv.seller.name]));
|
|
122
|
+
rows.push(row(r++, ["To", inv.buyer.name]));
|
|
123
|
+
r++; // blank
|
|
124
|
+
rows.push(row(r++, ["Qty", "Unit", "Item", "Net"]));
|
|
125
|
+
inv.lines.forEach((l, i) => rows.push(row(r++, [l.quantity, l.unit, l.name, t.lineNets[i]])));
|
|
126
|
+
r++; // blank
|
|
127
|
+
rows.push(row(r++, ["", "", "Net", s.net]));
|
|
128
|
+
rows.push(row(r++, ["", "", "VAT", s.vat]));
|
|
129
|
+
rows.push(row(r++, ["", "", `Total ${s.currency}`, s.gross]));
|
|
130
|
+
const sheet = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${rows.join("")}</sheetData></worksheet>`;
|
|
131
|
+
const workbook = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="Invoice" sheetId="1" r:id="rId1"/></sheets></workbook>`;
|
|
132
|
+
const wbRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/></Relationships>`;
|
|
133
|
+
const rootRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`;
|
|
134
|
+
const contentTypes = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/></Types>`;
|
|
135
|
+
return zip([
|
|
136
|
+
{ name: "[Content_Types].xml", data: Buffer.from(contentTypes) },
|
|
137
|
+
{ name: "_rels/.rels", data: Buffer.from(rootRels) },
|
|
138
|
+
{ name: "xl/workbook.xml", data: Buffer.from(workbook) },
|
|
139
|
+
{ name: "xl/_rels/workbook.xml.rels", data: Buffer.from(wbRels) },
|
|
140
|
+
{ name: "xl/worksheets/sheet1.xml", data: Buffer.from(sheet) },
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
/** Export to the requested format - string for json/txt, Buffer for the binary xlsx. */
|
|
144
|
+
export function exportInvoice(inv, t, format) {
|
|
145
|
+
if (format === "json")
|
|
146
|
+
return exportJson(inv, t);
|
|
147
|
+
if (format === "txt")
|
|
148
|
+
return exportText(inv, t);
|
|
149
|
+
return exportXlsx(inv, t);
|
|
150
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { inflateSync } from "node:zlib";
|
|
2
|
+
// PDF → embedded XML. Pulls the e-invoice XML (factur-x.xml) out of a ZUGFeRD / XRechnung PDF/A-3.
|
|
3
|
+
// Minimal by design: we do NOT parse the whole PDF (no pdf.js). We resolve the /EmbeddedFile stream
|
|
4
|
+
// object, read its bytes, and FlateDecode-inflate them when filtered. jasy *wrote* the PDF - here we
|
|
5
|
+
// read the embedded part back out. The foundation the whole CLI hangs off (read → parse → validate).
|
|
6
|
+
/** The embedded XML of a ZUGFeRD / XRechnung PDF, as a UTF-8 string. Throws if the PDF carries none. */
|
|
7
|
+
export function extractEmbeddedXml(pdf) {
|
|
8
|
+
const buf = Buffer.from(pdf);
|
|
9
|
+
const s = buf.toString("latin1"); // 1 byte per char → string index == byte offset
|
|
10
|
+
const objNum = findEmbeddedFileObject(s);
|
|
11
|
+
if (objNum === null) {
|
|
12
|
+
throw new Error("No embedded XML found - is this a ZUGFeRD / Factur-X PDF?");
|
|
13
|
+
}
|
|
14
|
+
const obj = locateObject(s, objNum);
|
|
15
|
+
if (!obj)
|
|
16
|
+
throw new Error(`Embedded-file object ${objNum} not found in the PDF.`);
|
|
17
|
+
const bytes = readStream(buf, s, obj.dataStart, obj.dict);
|
|
18
|
+
const flated = /\/Filter\s*(?:\[\s*)?\/FlateDecode/.test(obj.dict);
|
|
19
|
+
return (flated ? inflateSync(bytes) : bytes).toString("utf-8");
|
|
20
|
+
}
|
|
21
|
+
// The e-invoice XML's EmbeddedFile object number. A PDF may embed SEVERAL files (e.g. a tool also
|
|
22
|
+
// attaches its own gobl.json / source) - so we collect every Filespec and pick the invoice XML by name
|
|
23
|
+
// (factur-x.xml / zugferd-invoice.xml / xrechnung.xml / order-x.xml), then any .xml, then the first.
|
|
24
|
+
function findEmbeddedFileObject(s) {
|
|
25
|
+
var _a, _b;
|
|
26
|
+
const specs = [];
|
|
27
|
+
const efRe = /\/EF\s*<<[^>]*?\/(?:UF|F)\s+(\d+)\s+0\s+R/g;
|
|
28
|
+
for (let m = efRe.exec(s); m; m = efRe.exec(s)) {
|
|
29
|
+
specs.push({ obj: Number(m[1]), name: filespecName(s, m.index) });
|
|
30
|
+
}
|
|
31
|
+
if (specs.length) {
|
|
32
|
+
const invoiceXml = /(factur-x|zugferd-invoice|xrechnung|order-x|cii)\.xml$/i;
|
|
33
|
+
const pick = (_b = (_a = specs.find((sp) => invoiceXml.test(sp.name))) !== null && _a !== void 0 ? _a : specs.find((sp) => /\.xml$/i.test(sp.name))) !== null && _b !== void 0 ? _b : specs[0];
|
|
34
|
+
return pick.obj;
|
|
35
|
+
}
|
|
36
|
+
const t = s.search(/\/Type\s*\/EmbeddedFile/);
|
|
37
|
+
if (t >= 0) {
|
|
38
|
+
// the enclosing "N 0 obj" is the last object definition before the /Type
|
|
39
|
+
const m = s.slice(0, t).match(/(\d+)\s+0\s+obj(?![\s\S]*\d+\s+0\s+obj)/);
|
|
40
|
+
if (m)
|
|
41
|
+
return Number(m[1]);
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
/** The /F or /UF filename of the Filespec that owns the /EF at `idx` (look just before it, else after). */
|
|
46
|
+
function filespecName(s, idx) {
|
|
47
|
+
const before = s.slice(Math.max(0, idx - 400), idx).match(/\/(?:UF|F)\s*\(([^)]*)\)[^(]*$/);
|
|
48
|
+
if (before)
|
|
49
|
+
return before[1];
|
|
50
|
+
const after = s.slice(idx, idx + 400).match(/\/(?:UF|F)\s*\(([^)]*)\)/);
|
|
51
|
+
return after ? after[1] : "";
|
|
52
|
+
}
|
|
53
|
+
function locateObject(s, objNum) {
|
|
54
|
+
const m = new RegExp(`(?:^|[^0-9])${objNum}\\s+0\\s+obj`).exec(s);
|
|
55
|
+
if (!m)
|
|
56
|
+
return null;
|
|
57
|
+
const streamKw = s.indexOf("stream", m.index + m[0].length);
|
|
58
|
+
if (streamKw < 0)
|
|
59
|
+
return null;
|
|
60
|
+
const dict = s.slice(m.index + m[0].length, streamKw);
|
|
61
|
+
// "stream" is followed by CRLF or LF before the data begins
|
|
62
|
+
let dataStart = streamKw + "stream".length;
|
|
63
|
+
if (s[dataStart] === "\r")
|
|
64
|
+
dataStart++;
|
|
65
|
+
if (s[dataStart] === "\n")
|
|
66
|
+
dataStart++;
|
|
67
|
+
return { dict, dataStart };
|
|
68
|
+
}
|
|
69
|
+
// Stream bytes: the direct /Length when present (precise), else everything up to "endstream".
|
|
70
|
+
function readStream(buf, s, dataStart, dict) {
|
|
71
|
+
const len = dict.match(/\/Length\s+(\d+)(?!\s+\d+\s+R)/); // direct length, not an indirect ref
|
|
72
|
+
if (len)
|
|
73
|
+
return buf.subarray(dataStart, dataStart + Number(len[1]));
|
|
74
|
+
let end = s.indexOf("endstream", dataStart);
|
|
75
|
+
if (s[end - 1] === "\n")
|
|
76
|
+
end--;
|
|
77
|
+
if (s[end - 1] === "\r")
|
|
78
|
+
end--;
|
|
79
|
+
return buf.subarray(dataStart, end);
|
|
80
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Invoice } from "@jasy/zugferd";
|
|
2
|
+
/** Parse a UN/CEFACT CII invoice (EN16931 / ZUGFeRD / XRechnung-CII) into the Invoice model. */
|
|
3
|
+
export declare function parseCII(xml: string): Invoice;
|
|
4
|
+
/** Parse an OASIS UBL invoice (EN16931 / PEPPOL / XRechnung-UBL) into the Invoice model. */
|
|
5
|
+
export declare function parseUBL(xml: string): Invoice;
|
|
6
|
+
/** Parse e-invoice XML into the Invoice model, picking CII/UBL from what it is. */
|
|
7
|
+
export declare function parseInvoice(xml: string): Invoice;
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { detectInvoice } from "./detect.js";
|
|
2
|
+
// XML → Invoice. Hand-rolled, scope-based extraction of the known EN16931 tags (we emit them in
|
|
3
|
+
// @jasy/zugferd, so we know every path). No XML-parser dependency. CII first; UBL plugs in next.
|
|
4
|
+
// Round-trip safe: parsing an invoice we generated and re-emitting reproduces the same XML.
|
|
5
|
+
const unesc = (s) => s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
6
|
+
/** Inner content of the first `<tag …>…</tag>` (CII tags don't self-nest, so non-greedy is exact). */
|
|
7
|
+
function inner(xml, tag) {
|
|
8
|
+
var _a;
|
|
9
|
+
if (xml === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
return (_a = new RegExp(`<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)</${tag}>`).exec(xml)) === null || _a === void 0 ? void 0 : _a[1];
|
|
12
|
+
}
|
|
13
|
+
/** Inner content of every `<tag …>…</tag>`. */
|
|
14
|
+
function innerAll(xml, tag) {
|
|
15
|
+
if (xml === undefined)
|
|
16
|
+
return [];
|
|
17
|
+
const re = new RegExp(`<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)</${tag}>`, "g");
|
|
18
|
+
const out = [];
|
|
19
|
+
for (let m = re.exec(xml); m; m = re.exec(xml))
|
|
20
|
+
out.push(m[1]);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
/** Text value of the first leaf `<tag …>value</tag>`, unescaped. */
|
|
24
|
+
function val(xml, tag) {
|
|
25
|
+
const c = inner(xml, tag);
|
|
26
|
+
return c === undefined ? undefined : unesc(c);
|
|
27
|
+
}
|
|
28
|
+
/** An attribute on the first `<tag … name="X" …>`. */
|
|
29
|
+
function attr(xml, tag, name) {
|
|
30
|
+
var _a;
|
|
31
|
+
if (xml === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
return (_a = new RegExp(`<${tag}\\s[^>]*\\b${name}="([^"]*)"`).exec(xml)) === null || _a === void 0 ? void 0 : _a[1];
|
|
34
|
+
}
|
|
35
|
+
const num = (s) => (s === undefined ? 0 : parseFloat(s));
|
|
36
|
+
/** `format="102"` date `20260620` → `2026-06-20`. */
|
|
37
|
+
function date(scope) {
|
|
38
|
+
const d = val(scope, "udt:DateTimeString");
|
|
39
|
+
return d && d.length === 8 ? `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}` : undefined;
|
|
40
|
+
}
|
|
41
|
+
function parseAddress(s) {
|
|
42
|
+
var _a;
|
|
43
|
+
return {
|
|
44
|
+
postCode: val(s, "ram:PostcodeCode"),
|
|
45
|
+
line1: val(s, "ram:LineOne"),
|
|
46
|
+
line2: val(s, "ram:LineTwo"),
|
|
47
|
+
line3: val(s, "ram:LineThree"),
|
|
48
|
+
city: val(s, "ram:CityName"),
|
|
49
|
+
subdivision: val(s, "ram:CountrySubDivisionName"),
|
|
50
|
+
country: (_a = val(s, "ram:CountryID")) !== null && _a !== void 0 ? _a : "",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function parseContact(s) {
|
|
54
|
+
if (!s)
|
|
55
|
+
return undefined;
|
|
56
|
+
const c = {
|
|
57
|
+
name: val(s, "ram:PersonName"),
|
|
58
|
+
phone: val(inner(s, "ram:TelephoneUniversalCommunication"), "ram:CompleteNumber"),
|
|
59
|
+
email: val(inner(s, "ram:EmailURIUniversalCommunication"), "ram:URIID"),
|
|
60
|
+
};
|
|
61
|
+
return c.name || c.phone || c.email ? c : undefined;
|
|
62
|
+
}
|
|
63
|
+
/** `<ram:ID schemeID="VA">…</ram:ID>` inside a party's tax registrations (VA = VAT id, FC = tax no.). */
|
|
64
|
+
function taxReg(s, scheme) {
|
|
65
|
+
const m = new RegExp(`<ram:ID schemeID="${scheme}">([^<]*)</ram:ID>`).exec(s);
|
|
66
|
+
return m ? unesc(m[1]) : undefined;
|
|
67
|
+
}
|
|
68
|
+
function parseSeller(s) {
|
|
69
|
+
var _a, _b;
|
|
70
|
+
const org = inner(s, "ram:SpecifiedLegalOrganization");
|
|
71
|
+
return {
|
|
72
|
+
name: (_a = val(s, "ram:Name")) !== null && _a !== void 0 ? _a : "",
|
|
73
|
+
tradingName: val(org, "ram:TradingBusinessName"),
|
|
74
|
+
legalRegistrationId: val(org, "ram:ID"),
|
|
75
|
+
vatId: taxReg(s, "VA"),
|
|
76
|
+
taxNumber: taxReg(s, "FC"),
|
|
77
|
+
electronicAddress: val(inner(s, "ram:URIUniversalCommunication"), "ram:URIID"),
|
|
78
|
+
address: parseAddress((_b = inner(s, "ram:PostalTradeAddress")) !== null && _b !== void 0 ? _b : ""),
|
|
79
|
+
contact: parseContact(inner(s, "ram:DefinedTradeContact")),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function parseBuyer(s) {
|
|
83
|
+
var _a, _b;
|
|
84
|
+
const org = inner(s, "ram:SpecifiedLegalOrganization");
|
|
85
|
+
return {
|
|
86
|
+
name: (_a = val(s, "ram:Name")) !== null && _a !== void 0 ? _a : "",
|
|
87
|
+
tradingName: val(org, "ram:TradingBusinessName"),
|
|
88
|
+
legalRegistrationId: val(org, "ram:ID"),
|
|
89
|
+
vatId: taxReg(s, "VA"),
|
|
90
|
+
electronicAddress: val(inner(s, "ram:URIUniversalCommunication"), "ram:URIID"),
|
|
91
|
+
address: parseAddress((_b = inner(s, "ram:PostalTradeAddress")) !== null && _b !== void 0 ? _b : ""),
|
|
92
|
+
contact: parseContact(inner(s, "ram:DefinedTradeContact")),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function parseLine(s, index) {
|
|
96
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
97
|
+
const doc = (_a = inner(s, "ram:AssociatedDocumentLineDocument")) !== null && _a !== void 0 ? _a : "";
|
|
98
|
+
const product = (_b = inner(s, "ram:SpecifiedTradeProduct")) !== null && _b !== void 0 ? _b : "";
|
|
99
|
+
const price = (_c = inner(s, "ram:NetPriceProductTradePrice")) !== null && _c !== void 0 ? _c : "";
|
|
100
|
+
const del = (_d = inner(s, "ram:SpecifiedLineTradeDelivery")) !== null && _d !== void 0 ? _d : "";
|
|
101
|
+
const tax = (_e = inner(inner(s, "ram:SpecifiedLineTradeSettlement"), "ram:ApplicableTradeTax")) !== null && _e !== void 0 ? _e : "";
|
|
102
|
+
const id = val(doc, "ram:LineID");
|
|
103
|
+
const note = inner(doc, "ram:IncludedNote");
|
|
104
|
+
const basis = inner(price, "ram:BasisQuantity");
|
|
105
|
+
return {
|
|
106
|
+
id: id !== undefined && id !== String(index + 1) ? id : undefined, // omit the auto-number
|
|
107
|
+
name: (_f = val(product, "ram:Name")) !== null && _f !== void 0 ? _f : "",
|
|
108
|
+
description: val(product, "ram:Description"),
|
|
109
|
+
sellerItemId: val(product, "ram:SellerAssignedID"),
|
|
110
|
+
buyerItemId: val(product, "ram:BuyerAssignedID"),
|
|
111
|
+
standardItemId: val(product, "ram:GlobalID"),
|
|
112
|
+
quantity: num(val(del, "ram:BilledQuantity")),
|
|
113
|
+
unit: (_g = attr(del, "ram:BilledQuantity", "unitCode")) !== null && _g !== void 0 ? _g : "",
|
|
114
|
+
netUnitPrice: num(val(price, "ram:ChargeAmount")),
|
|
115
|
+
priceBaseQuantity: basis ? num(val(price, "ram:BasisQuantity")) : undefined,
|
|
116
|
+
vat: {
|
|
117
|
+
category: ((_h = val(tax, "ram:CategoryCode")) !== null && _h !== void 0 ? _h : "S"),
|
|
118
|
+
ratePercent: num(val(tax, "ram:RateApplicablePercent")),
|
|
119
|
+
},
|
|
120
|
+
note: note ? val(note, "ram:Content") : undefined,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** A document-level allowance (discount) or charge (surcharge), BG-20 / BG-21. */
|
|
124
|
+
function parseAllowanceCii(ac) {
|
|
125
|
+
var _a;
|
|
126
|
+
const cat = inner(ac, "ram:CategoryTradeTax");
|
|
127
|
+
return {
|
|
128
|
+
isCharge: val(inner(ac, "ram:ChargeIndicator"), "udt:Indicator") === "true",
|
|
129
|
+
amount: num(val(ac, "ram:ActualAmount")),
|
|
130
|
+
reason: val(ac, "ram:Reason"),
|
|
131
|
+
reasonCode: val(ac, "ram:ReasonCode"),
|
|
132
|
+
vat: {
|
|
133
|
+
category: ((_a = val(cat, "ram:CategoryCode")) !== null && _a !== void 0 ? _a : "S"),
|
|
134
|
+
ratePercent: num(val(cat, "ram:RateApplicablePercent")),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** VAT exemption reasons (BT-120/121) keyed by category, read off the BG-23 breakdown groups. */
|
|
139
|
+
function exemptionsCii(set) {
|
|
140
|
+
const out = {};
|
|
141
|
+
for (const g of innerAll(set, "ram:ApplicableTradeTax")) {
|
|
142
|
+
const cat = val(g, "ram:CategoryCode");
|
|
143
|
+
const text = val(g, "ram:ExemptionReason");
|
|
144
|
+
const code = val(g, "ram:ExemptionReasonCode");
|
|
145
|
+
if (cat && (text || code))
|
|
146
|
+
out[cat] = { text, code };
|
|
147
|
+
}
|
|
148
|
+
return Object.keys(out).length ? out : undefined;
|
|
149
|
+
}
|
|
150
|
+
/** Parse a UN/CEFACT CII invoice (EN16931 / ZUGFeRD / XRechnung-CII) into the Invoice model. */
|
|
151
|
+
export function parseCII(xml) {
|
|
152
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
153
|
+
const header = (_a = inner(xml, "rsm:ExchangedDocument")) !== null && _a !== void 0 ? _a : "";
|
|
154
|
+
const tx = (_b = inner(xml, "rsm:SupplyChainTradeTransaction")) !== null && _b !== void 0 ? _b : "";
|
|
155
|
+
const agr = (_c = inner(tx, "ram:ApplicableHeaderTradeAgreement")) !== null && _c !== void 0 ? _c : "";
|
|
156
|
+
const del = (_d = inner(tx, "ram:ApplicableHeaderTradeDelivery")) !== null && _d !== void 0 ? _d : "";
|
|
157
|
+
const set = (_e = inner(tx, "ram:ApplicableHeaderTradeSettlement")) !== null && _e !== void 0 ? _e : "";
|
|
158
|
+
const type = num(val(header, "ram:TypeCode"));
|
|
159
|
+
const notes = innerAll(header, "ram:IncludedNote")
|
|
160
|
+
.map((n) => val(n, "ram:Content"))
|
|
161
|
+
.filter((n) => !!n);
|
|
162
|
+
const shipTo = inner(del, "ram:ShipToTradeParty");
|
|
163
|
+
const deliveryDate = date(inner(del, "ram:ActualDeliverySupplyChainEvent"));
|
|
164
|
+
const delivery = shipTo || deliveryDate
|
|
165
|
+
? {
|
|
166
|
+
date: deliveryDate,
|
|
167
|
+
recipientName: val(shipTo, "ram:Name"),
|
|
168
|
+
address: shipTo && inner(shipTo, "ram:PostalTradeAddress")
|
|
169
|
+
? parseAddress(inner(shipTo, "ram:PostalTradeAddress"))
|
|
170
|
+
: undefined,
|
|
171
|
+
}
|
|
172
|
+
: undefined;
|
|
173
|
+
const pm = inner(set, "ram:SpecifiedTradeSettlementPaymentMeans");
|
|
174
|
+
const acct = inner(pm, "ram:PayeePartyCreditorFinancialAccount");
|
|
175
|
+
const inst = inner(pm, "ram:PayeeSpecifiedCreditorFinancialInstitution");
|
|
176
|
+
const terms = inner(set, "ram:SpecifiedTradePaymentTerms");
|
|
177
|
+
const payment = pm || terms
|
|
178
|
+
? {
|
|
179
|
+
meansCode: val(pm, "ram:TypeCode"),
|
|
180
|
+
meansText: val(pm, "ram:Information"),
|
|
181
|
+
iban: val(acct, "ram:IBANID"),
|
|
182
|
+
accountName: val(acct, "ram:AccountName"),
|
|
183
|
+
bic: val(inst, "ram:BICID"),
|
|
184
|
+
terms: val(terms, "ram:Description"),
|
|
185
|
+
}
|
|
186
|
+
: undefined;
|
|
187
|
+
const totals = inner(set, "ram:SpecifiedTradeSettlementHeaderMonetarySummation");
|
|
188
|
+
const paid = val(totals, "ram:TotalPrepaidAmount");
|
|
189
|
+
const allowancesCharges = innerAll(set, "ram:SpecifiedTradeAllowanceCharge").map(parseAllowanceCii);
|
|
190
|
+
return {
|
|
191
|
+
number: (_f = val(header, "ram:ID")) !== null && _f !== void 0 ? _f : "",
|
|
192
|
+
issueDate: (_g = date(inner(header, "ram:IssueDateTime"))) !== null && _g !== void 0 ? _g : "",
|
|
193
|
+
type: type && type !== 380 ? type : undefined,
|
|
194
|
+
currency: (_h = val(set, "ram:InvoiceCurrencyCode")) !== null && _h !== void 0 ? _h : "",
|
|
195
|
+
dueDate: date(inner(terms, "ram:DueDateDateTime")),
|
|
196
|
+
buyerReference: val(agr, "ram:BuyerReference"),
|
|
197
|
+
purchaseOrderRef: val(inner(agr, "ram:BuyerOrderReferencedDocument"), "ram:IssuerAssignedID"),
|
|
198
|
+
contractRef: val(inner(agr, "ram:ContractReferencedDocument"), "ram:IssuerAssignedID"),
|
|
199
|
+
notes: notes.length ? notes : undefined,
|
|
200
|
+
seller: parseSeller((_j = inner(agr, "ram:SellerTradeParty")) !== null && _j !== void 0 ? _j : ""),
|
|
201
|
+
buyer: parseBuyer((_k = inner(agr, "ram:BuyerTradeParty")) !== null && _k !== void 0 ? _k : ""),
|
|
202
|
+
delivery,
|
|
203
|
+
payeeName: val(inner(set, "ram:PayeeTradeParty"), "ram:Name"),
|
|
204
|
+
lines: innerAll(tx, "ram:IncludedSupplyChainTradeLineItem").map(parseLine),
|
|
205
|
+
allowancesCharges: allowancesCharges.length ? allowancesCharges : undefined,
|
|
206
|
+
vatExemptionReasons: exemptionsCii(set),
|
|
207
|
+
payment,
|
|
208
|
+
paidAmount: paid ? num(paid) : undefined,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
// ── UBL (OASIS Invoice-2) - the second EN16931 syntax (PEPPOL; XRechnung accepts it too) ────────────
|
|
212
|
+
function parseAddressUbl(s) {
|
|
213
|
+
var _a;
|
|
214
|
+
return {
|
|
215
|
+
line1: val(s, "cbc:StreetName"),
|
|
216
|
+
line2: val(s, "cbc:AdditionalStreetName"),
|
|
217
|
+
line3: val(inner(s, "cac:AddressLine"), "cbc:Line"),
|
|
218
|
+
city: val(s, "cbc:CityName"),
|
|
219
|
+
postCode: val(s, "cbc:PostalZone"),
|
|
220
|
+
subdivision: val(s, "cbc:CountrySubentity"),
|
|
221
|
+
country: (_a = val(inner(s, "cac:Country"), "cbc:IdentificationCode")) !== null && _a !== void 0 ? _a : "",
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function parseContactUbl(s) {
|
|
225
|
+
if (!s)
|
|
226
|
+
return undefined;
|
|
227
|
+
const c = {
|
|
228
|
+
name: val(s, "cbc:Name"),
|
|
229
|
+
phone: val(s, "cbc:Telephone"),
|
|
230
|
+
email: val(s, "cbc:ElectronicMail"),
|
|
231
|
+
};
|
|
232
|
+
return c.name || c.phone || c.email ? c : undefined;
|
|
233
|
+
}
|
|
234
|
+
/** A party's tax id by scheme: the cbc:CompanyID whose cac:TaxScheme is "VAT" (BT-31) or "FC" (BT-32). */
|
|
235
|
+
function ublTaxId(party, scheme) {
|
|
236
|
+
for (const pts of innerAll(party, "cac:PartyTaxScheme")) {
|
|
237
|
+
if (val(inner(pts, "cac:TaxScheme"), "cbc:ID") === scheme)
|
|
238
|
+
return val(pts, "cbc:CompanyID");
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
function parsePartyUbl(scope) {
|
|
243
|
+
var _a, _b, _c;
|
|
244
|
+
const party = (_a = inner(scope, "cac:Party")) !== null && _a !== void 0 ? _a : "";
|
|
245
|
+
const legal = inner(party, "cac:PartyLegalEntity");
|
|
246
|
+
return {
|
|
247
|
+
party,
|
|
248
|
+
name: (_b = val(legal, "cbc:RegistrationName")) !== null && _b !== void 0 ? _b : "",
|
|
249
|
+
tradingName: val(inner(party, "cac:PartyName"), "cbc:Name"),
|
|
250
|
+
legalRegistrationId: val(legal, "cbc:CompanyID"),
|
|
251
|
+
vatId: ublTaxId(party, "VAT"),
|
|
252
|
+
electronicAddress: val(party, "cbc:EndpointID"),
|
|
253
|
+
address: parseAddressUbl((_c = inner(party, "cac:PostalAddress")) !== null && _c !== void 0 ? _c : ""),
|
|
254
|
+
contact: parseContactUbl(inner(party, "cac:Contact")),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function parseLineUbl(s, index) {
|
|
258
|
+
var _a, _b, _c, _d, _e, _f;
|
|
259
|
+
const item = (_a = inner(s, "cac:Item")) !== null && _a !== void 0 ? _a : "";
|
|
260
|
+
const price = (_b = inner(s, "cac:Price")) !== null && _b !== void 0 ? _b : "";
|
|
261
|
+
const tax = (_c = inner(item, "cac:ClassifiedTaxCategory")) !== null && _c !== void 0 ? _c : "";
|
|
262
|
+
const id = val(s, "cbc:ID");
|
|
263
|
+
const base = val(price, "cbc:BaseQuantity");
|
|
264
|
+
return {
|
|
265
|
+
id: id !== undefined && id !== String(index + 1) ? id : undefined, // omit the auto-number
|
|
266
|
+
name: (_d = val(item, "cbc:Name")) !== null && _d !== void 0 ? _d : "",
|
|
267
|
+
description: val(item, "cbc:Description"),
|
|
268
|
+
sellerItemId: val(inner(item, "cac:SellersItemIdentification"), "cbc:ID"),
|
|
269
|
+
buyerItemId: val(inner(item, "cac:BuyersItemIdentification"), "cbc:ID"),
|
|
270
|
+
standardItemId: val(inner(item, "cac:StandardItemIdentification"), "cbc:ID"),
|
|
271
|
+
quantity: num(val(s, "cbc:InvoicedQuantity")),
|
|
272
|
+
unit: (_e = attr(s, "cbc:InvoicedQuantity", "unitCode")) !== null && _e !== void 0 ? _e : "",
|
|
273
|
+
netUnitPrice: num(val(price, "cbc:PriceAmount")),
|
|
274
|
+
priceBaseQuantity: base !== undefined ? num(base) : undefined,
|
|
275
|
+
vat: {
|
|
276
|
+
category: ((_f = val(tax, "cbc:ID")) !== null && _f !== void 0 ? _f : "S"),
|
|
277
|
+
ratePercent: num(val(tax, "cbc:Percent")),
|
|
278
|
+
},
|
|
279
|
+
note: val(s, "cbc:Note"),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function parseAllowanceUbl(ac) {
|
|
283
|
+
var _a;
|
|
284
|
+
const cat = inner(ac, "cac:TaxCategory");
|
|
285
|
+
return {
|
|
286
|
+
isCharge: val(ac, "cbc:ChargeIndicator") === "true",
|
|
287
|
+
amount: num(val(ac, "cbc:Amount")),
|
|
288
|
+
reason: val(ac, "cbc:AllowanceChargeReason"),
|
|
289
|
+
reasonCode: val(ac, "cbc:AllowanceChargeReasonCode"),
|
|
290
|
+
vat: {
|
|
291
|
+
category: ((_a = val(cat, "cbc:ID")) !== null && _a !== void 0 ? _a : "S"),
|
|
292
|
+
ratePercent: num(val(cat, "cbc:Percent")),
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function exemptionsUbl(xml) {
|
|
297
|
+
const out = {};
|
|
298
|
+
for (const sub of innerAll(inner(xml, "cac:TaxTotal"), "cac:TaxSubtotal")) {
|
|
299
|
+
const tc = inner(sub, "cac:TaxCategory");
|
|
300
|
+
const cat = val(tc, "cbc:ID");
|
|
301
|
+
const text = val(tc, "cbc:TaxExemptionReason");
|
|
302
|
+
const code = val(tc, "cbc:TaxExemptionReasonCode");
|
|
303
|
+
if (cat && (text || code))
|
|
304
|
+
out[cat] = { text, code };
|
|
305
|
+
}
|
|
306
|
+
return Object.keys(out).length ? out : undefined;
|
|
307
|
+
}
|
|
308
|
+
/** Parse an OASIS UBL invoice (EN16931 / PEPPOL / XRechnung-UBL) into the Invoice model. */
|
|
309
|
+
export function parseUBL(xml) {
|
|
310
|
+
var _a, _b, _c, _d, _e;
|
|
311
|
+
// UBL has no head wrapper; the header fields are direct children before the supplier party
|
|
312
|
+
const cut = xml.indexOf("<cac:AccountingSupplierParty");
|
|
313
|
+
const head = cut >= 0 ? xml.slice(0, cut) : xml;
|
|
314
|
+
const seller = parsePartyUbl((_a = inner(xml, "cac:AccountingSupplierParty")) !== null && _a !== void 0 ? _a : "");
|
|
315
|
+
const buyer = parsePartyUbl((_b = inner(xml, "cac:AccountingCustomerParty")) !== null && _b !== void 0 ? _b : "");
|
|
316
|
+
const type = num(val(head, "cbc:InvoiceTypeCode"));
|
|
317
|
+
const notes = innerAll(head, "cbc:Note")
|
|
318
|
+
.map(unesc)
|
|
319
|
+
.filter((n) => n.length > 0);
|
|
320
|
+
const del = inner(xml, "cac:Delivery");
|
|
321
|
+
const dLoc = inner(del, "cac:DeliveryLocation");
|
|
322
|
+
const deliveryDate = val(del, "cbc:ActualDeliveryDate"); // UBL dates are plain ISO, no 102 format
|
|
323
|
+
const dParty = inner(del, "cac:DeliveryParty");
|
|
324
|
+
const delivery = del && (deliveryDate || dParty || dLoc)
|
|
325
|
+
? {
|
|
326
|
+
date: deliveryDate,
|
|
327
|
+
recipientName: val(inner(dParty, "cac:PartyName"), "cbc:Name"),
|
|
328
|
+
address: inner(dLoc, "cac:PostalAddress")
|
|
329
|
+
? parseAddressUbl(inner(dLoc, "cac:PostalAddress"))
|
|
330
|
+
: undefined,
|
|
331
|
+
}
|
|
332
|
+
: undefined;
|
|
333
|
+
const pm = inner(xml, "cac:PaymentMeans");
|
|
334
|
+
const acct = inner(pm, "cac:PayeeFinancialAccount");
|
|
335
|
+
const terms = inner(xml, "cac:PaymentTerms");
|
|
336
|
+
const payment = pm || terms
|
|
337
|
+
? {
|
|
338
|
+
meansCode: val(pm, "cbc:PaymentMeansCode"),
|
|
339
|
+
reference: val(pm, "cbc:PaymentID"),
|
|
340
|
+
iban: val(acct, "cbc:ID"),
|
|
341
|
+
accountName: val(acct, "cbc:Name"),
|
|
342
|
+
bic: val(inner(acct, "cac:FinancialInstitutionBranch"), "cbc:ID"),
|
|
343
|
+
terms: val(terms, "cbc:Note"),
|
|
344
|
+
}
|
|
345
|
+
: undefined;
|
|
346
|
+
const paid = val(inner(xml, "cac:LegalMonetaryTotal"), "cbc:PrepaidAmount");
|
|
347
|
+
// document-level allowances/charges live before the lines (UBL also allows them per-line, which we skip)
|
|
348
|
+
const li = xml.indexOf("<cac:InvoiceLine");
|
|
349
|
+
const allowancesCharges = innerAll(li >= 0 ? xml.slice(0, li) : xml, "cac:AllowanceCharge").map(parseAllowanceUbl);
|
|
350
|
+
return {
|
|
351
|
+
number: (_c = val(head, "cbc:ID")) !== null && _c !== void 0 ? _c : "",
|
|
352
|
+
issueDate: (_d = val(head, "cbc:IssueDate")) !== null && _d !== void 0 ? _d : "",
|
|
353
|
+
type: type && type !== 380 ? type : undefined,
|
|
354
|
+
currency: (_e = val(head, "cbc:DocumentCurrencyCode")) !== null && _e !== void 0 ? _e : "",
|
|
355
|
+
dueDate: val(head, "cbc:DueDate"),
|
|
356
|
+
buyerReference: val(head, "cbc:BuyerReference"),
|
|
357
|
+
purchaseOrderRef: val(inner(head, "cac:OrderReference"), "cbc:ID"),
|
|
358
|
+
contractRef: val(inner(head, "cac:ContractDocumentReference"), "cbc:ID"),
|
|
359
|
+
notes: notes.length ? notes : undefined,
|
|
360
|
+
seller: {
|
|
361
|
+
name: seller.name,
|
|
362
|
+
tradingName: seller.tradingName,
|
|
363
|
+
legalRegistrationId: seller.legalRegistrationId,
|
|
364
|
+
vatId: seller.vatId,
|
|
365
|
+
taxNumber: ublTaxId(seller.party, "FC"),
|
|
366
|
+
electronicAddress: seller.electronicAddress,
|
|
367
|
+
address: seller.address,
|
|
368
|
+
contact: seller.contact,
|
|
369
|
+
},
|
|
370
|
+
buyer: {
|
|
371
|
+
name: buyer.name,
|
|
372
|
+
tradingName: buyer.tradingName,
|
|
373
|
+
legalRegistrationId: buyer.legalRegistrationId,
|
|
374
|
+
vatId: buyer.vatId,
|
|
375
|
+
electronicAddress: buyer.electronicAddress,
|
|
376
|
+
address: buyer.address,
|
|
377
|
+
contact: buyer.contact,
|
|
378
|
+
},
|
|
379
|
+
delivery,
|
|
380
|
+
lines: innerAll(xml, "cac:InvoiceLine").map(parseLineUbl),
|
|
381
|
+
allowancesCharges: allowancesCharges.length ? allowancesCharges : undefined,
|
|
382
|
+
vatExemptionReasons: exemptionsUbl(xml),
|
|
383
|
+
payment,
|
|
384
|
+
paidAmount: paid !== undefined ? num(paid) : undefined,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/** Parse e-invoice XML into the Invoice model, picking CII/UBL from what it is. */
|
|
388
|
+
export function parseInvoice(xml) {
|
|
389
|
+
const { syntax } = detectInvoice(xml);
|
|
390
|
+
if (syntax === "UBL")
|
|
391
|
+
return parseUBL(xml);
|
|
392
|
+
if (syntax === "CII")
|
|
393
|
+
return parseCII(xml);
|
|
394
|
+
throw new Error(`${syntax} parsing is not implemented yet`);
|
|
395
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface PdfaCheck {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
ok: boolean;
|
|
5
|
+
detail?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PdfaReport {
|
|
8
|
+
/** true when every structural check passed (NOT an ISO certification - see veraPDF for that). */
|
|
9
|
+
ok: boolean;
|
|
10
|
+
checks: PdfaCheck[];
|
|
11
|
+
}
|
|
12
|
+
export declare function checkPdfA3(pdf: Uint8Array): PdfaReport;
|