@open-nav/invoicing 0.1.0

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.
Files changed (53) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +190 -0
  3. package/dist/color.d.ts +31 -0
  4. package/dist/color.d.ts.map +1 -0
  5. package/dist/color.js +150 -0
  6. package/dist/color.js.map +1 -0
  7. package/dist/export.d.ts +89 -0
  8. package/dist/export.d.ts.map +1 -0
  9. package/dist/export.js +115 -0
  10. package/dist/export.js.map +1 -0
  11. package/dist/format.d.ts +23 -0
  12. package/dist/format.d.ts.map +1 -0
  13. package/dist/format.js +52 -0
  14. package/dist/format.js.map +1 -0
  15. package/dist/html.d.ts +43 -0
  16. package/dist/html.d.ts.map +1 -0
  17. package/dist/html.js +355 -0
  18. package/dist/html.js.map +1 -0
  19. package/dist/index.d.ts +10 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +10 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/labels.d.ts +8 -0
  24. package/dist/labels.d.ts.map +1 -0
  25. package/dist/labels.js +199 -0
  26. package/dist/labels.js.map +1 -0
  27. package/dist/markings.d.ts +28 -0
  28. package/dist/markings.d.ts.map +1 -0
  29. package/dist/markings.js +69 -0
  30. package/dist/markings.js.map +1 -0
  31. package/dist/pdf-native.d.ts +32 -0
  32. package/dist/pdf-native.d.ts.map +1 -0
  33. package/dist/pdf-native.js +601 -0
  34. package/dist/pdf-native.js.map +1 -0
  35. package/dist/pdf.d.ts +92 -0
  36. package/dist/pdf.d.ts.map +1 -0
  37. package/dist/pdf.js +203 -0
  38. package/dist/pdf.js.map +1 -0
  39. package/dist/theme.d.ts +85 -0
  40. package/dist/theme.d.ts.map +1 -0
  41. package/dist/theme.js +210 -0
  42. package/dist/theme.js.map +1 -0
  43. package/package.json +54 -0
  44. package/src/color.ts +170 -0
  45. package/src/export.ts +204 -0
  46. package/src/format.ts +66 -0
  47. package/src/html.ts +446 -0
  48. package/src/index.ts +9 -0
  49. package/src/labels.ts +222 -0
  50. package/src/markings.ts +102 -0
  51. package/src/pdf-native.ts +754 -0
  52. package/src/pdf.ts +302 -0
  53. package/src/theme.ts +305 -0
package/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 open-nav contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This repository vendors XML schemas, sample documents and validation message
26
+ catalogues published by the Hungarian Tax and Customs Administration
27
+ (Nemzeti Adó- és Vámhivatal) under the MIT license. Those files live under
28
+ `schemas/` and `conformance/`, retain NAV's copyright, and are covered by
29
+ `schemas/NAV-LICENCE.md`. See `schemas/README.md` for provenance.
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # @open-nav/invoicing
2
+
3
+ Turn NAV invoice data into the two documents an invoicing program has to be
4
+ able to produce: a printable invoice, and the tax authority data export.
5
+
6
+ ## The printable invoice
7
+
8
+ ```sh
9
+ open-nav render invoice.xml --pdf invoice.pdf --theme theme.json
10
+ ```
11
+
12
+ ```ts
13
+ import { renderInvoicePdf, renderInvoiceHtml, loadTheme } from '@open-nav/invoicing';
14
+
15
+ const theme = loadTheme('theme.json');
16
+ const pdf = await renderInvoicePdf(invoice, { theme }); // Buffer
17
+ const html = renderInvoiceHtml(invoice, { theme }); // string
18
+ ```
19
+
20
+ ### Styling
21
+
22
+ Everything visual comes from a theme, which can live in a JSON file next to
23
+ its logo — see [`examples/invoice-theme.json`](../../examples/invoice-theme.json).
24
+
25
+ | Field | Purpose |
26
+ | ----------------------------------------------------- | ---------------------------------------------------------------------- |
27
+ | `accentColor` | Title, the grand total, the rule above it |
28
+ | `inkColor`, `mutedColor`, `panelColor`, `borderColor` | The rest of the palette |
29
+ | `fontFamily`, `baseFontSize` | Typography; everything scales from the base size |
30
+ | `pageSize`, `pageMargin` | `@page` setup, e.g. `A4` and `16mm 14mm` |
31
+ | `logo` / `logoFile` | Header logo. `logoFile` is read relative to the theme file and inlined |
32
+ | `issuerContact` | Extra lines in the supplier block: phone, email, web |
33
+ | `footerLines` | Footer: payment terms, company registration, bank details |
34
+ | `zebraRows` | Tint alternate table rows |
35
+ | `provenanceNote` | Whether to print the "rendered from reported data" line |
36
+ | `customCss` | Appended after the generated stylesheet. The escape hatch |
37
+
38
+ Theme values end up inside the stylesheet, so they are **validated rather
39
+ than interpolated**: a colour must look like a CSS colour, a font stack may
40
+ not contain parentheses (no font name does, and allowing them would admit
41
+ `url(...)`), and a logo must be a `data:` URI or an `http(s)` URL. A bad value
42
+ names the field it came from.
43
+
44
+ `--logo` on the command line overrides the theme's, so one branded theme can
45
+ be shared and a single document still overridden.
46
+
47
+ ### How the PDF is made, and why
48
+
49
+ `ő` and `ű` are the whole difficulty. Both lie outside WinAnsi, the encoding
50
+ the PDF core fonts use, so **a font has to be embedded** for a Hungarian
51
+ invoice to spell itself. (This is not hypothetical: of the fonts installed on
52
+ the machine this was developed on, one — Loma — is missing exactly those four
53
+ characters and nothing else.)
54
+
55
+ There are two engines, and the default needs nothing installed.
56
+
57
+ **`native`** — the default. pdfmake lays the document out, pdfkit writes the
58
+ PDF, and the Roboto files pdfmake bundles supply the glyphs. Roboto covers
59
+ Latin Extended-A, is Apache-2.0, and embeds as a subset with a correct
60
+ ToUnicode map, so the text stays searchable and copyable. Output is around a
61
+ quarter the size of a browser's.
62
+
63
+ ```ts
64
+ const pdf = await renderInvoicePdf(invoice, { theme }); // native
65
+ ```
66
+
67
+ Bring your own font if the brand needs one:
68
+
69
+ ```ts
70
+ const pdf = await renderInvoicePdf(invoice, {
71
+ theme,
72
+ font: { name: 'Inter', normal: 'fonts/Inter-Regular.ttf', bold: 'fonts/Inter-SemiBold.ttf' },
73
+ });
74
+ ```
75
+
76
+ **`browser`** — renders the HTML document and converts it with Chrome,
77
+ Chromium or Edge. It follows the HTML exactly, which matters if you have
78
+ styled it with `customCss` beyond what the native layout reproduces.
79
+
80
+ ```sh
81
+ open-nav render invoice.xml --pdf invoice.pdf --engine browser --no-sandbox
82
+ ```
83
+
84
+ `findBrowser()` locates one: an explicit `OPEN_NAV_BROWSER`, `CHROME_PATH` or
85
+ `PUPPETEER_EXECUTABLE_PATH`, then a Playwright install, then the usual
86
+ locations, then `PATH` — preferring a full build over a headless shell, which
87
+ cannot print. Or supply `convert` and use Playwright directly:
88
+
89
+ ```ts
90
+ import { chromium } from 'playwright';
91
+
92
+ const convert = async (html: string) => {
93
+ const browser = await chromium.launch();
94
+ const page = await browser.newPage();
95
+ await page.setContent(html, { waitUntil: 'load' });
96
+ const pdf = await page.pdf({ printBackground: true });
97
+ await browser.close();
98
+ return pdf;
99
+ };
100
+
101
+ const pdf = await renderInvoicePdf(invoice, { convert }); // implies browser
102
+ ```
103
+
104
+ **The browser sandbox stays on by default.** Invoice data is input, and the
105
+ sandbox is what contains a malicious document, so conversion fails as root —
106
+ usual in a container. Pass `sandbox: false`, or `--no-sandbox`, deliberately.
107
+
108
+ The native engine has no such exposure: it fetches nothing and reads nothing
109
+ but the font files it registered, both enforced through pdfmake's access
110
+ policies rather than left at their permissive defaults.
111
+
112
+ ### The two engines are two layouts
113
+
114
+ They follow the same theme, but the native engine builds the page from
115
+ pdfmake's document model rather than CSS, so **they will not match pixel for
116
+ pixel**. Known differences: party boxes have square corners rather than
117
+ rounded, an SVG logo is skipped because pdfmake cannot rasterise one (use PNG
118
+ or JPEG), and `customCss` applies only to HTML. The native engine adds page
119
+ numbers when a document runs to more than one page.
120
+
121
+ The HTML has no external references — no fonts, scripts or images fetched — so
122
+ it renders the same offline and archives as a single file. Keep the logo
123
+ inlined for that reason; a logo fetched over the network is the usual cause of
124
+ a conversion that hangs, or a PDF that silently comes out logo-less.
125
+
126
+ ### It derives the markings the law requires
127
+
128
+ Section 169 of the VAT Act prescribes more than figures. An exempt supply must
129
+ state its legal ground, a domestic reverse charge must say _fordított adózás_,
130
+ cash accounting must say _pénzforgalmi elszámolás_, a margin scheme must name
131
+ which one. These are derived from the invoice data and printed with the
132
+ provision they come from, because a missing phrase makes an invoice defective
133
+ even when every number on it is right:
134
+
135
+ ```text
136
+ JOGSZABÁLYI JELÖLÉSEK
137
+ • fordított adózás (Áfa tv. 169. § n))
138
+ • különbözet szerinti szabályozás – használt cikkek (Áfa tv. 169. § o) p))
139
+ • Adómentes — TAM — Mentes ÁFA tv. 85.§ (1) i) (Áfa tv. 169. § m))
140
+ ```
141
+
142
+ Also handled: simplified and aggregate invoices get their own title, a
143
+ modification document names the invoice it amends, a foreign-currency invoice
144
+ shows the rate and the forint total, a private person is never named, and a
145
+ batch document renders each invoice on its own page.
146
+
147
+ Amounts are formatted from the exact decimal strings, never through a
148
+ JavaScript number, so an 18 digit total survives to the page.
149
+
150
+ ## The data export
151
+
152
+ Decree **23/2014. (VI. 30.) NGM** requires every invoicing program to have a
153
+ built-in _adóhatósági ellenőrzési adatszolgáltatás_ function that exports the
154
+ invoices issued in a date range, or in an invoice number range, as XML.
155
+
156
+ ```sh
157
+ open-nav export invoices/*.xml --out export/ --from 2024-01-01 --to 2024-12-31
158
+ ```
159
+
160
+ Two structures are permitted. Annexes 2 and 3 of the decree define one
161
+ (`szamla.xsd`); **section 13/A(1)** permits the other — the structure
162
+ published for the online invoice data service, which is `invoiceData.xsd`.
163
+ This produces the second, because it is the schema this project already
164
+ generates, validates and round-trips against NAV's own documents.
165
+
166
+ That choice belongs to the taxpayer. **If your auditor asks for the Annex 3
167
+ structure specifically, this export is not it.**
168
+
169
+ The export writes one XML per invoice plus a `manifest.json` recording what
170
+ was selected, the structure used with the provision that permits it, and the
171
+ SHA-256 of every file — so the export can later be shown to be the one that
172
+ was produced.
173
+
174
+ ### Invoice number ranges order naturally
175
+
176
+ Selecting `2024/1` to `2024/10` by plain text comparison excludes `2024/9`,
177
+ because `"2024/9" > "2024/10"` as text. Digit runs are therefore compared as
178
+ numbers by default, so a requested range does not silently lose invoices from
179
+ the middle of it. Pass `compareInvoiceNumbers` if your numbering needs
180
+ something else.
181
+
182
+ ## What this does not claim
183
+
184
+ It does not make anyone's invoicing program compliant. Gapless numbering,
185
+ immutable storage and the audit trail are the issuer's obligations, and a
186
+ rendered document is a presentation of reported data, not the legal original.
187
+
188
+ ## Licence
189
+
190
+ MIT. Not affiliated with NAV.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Colour conversion for the PDF engine.
3
+ *
4
+ * A browser accepts every CSS colour form; PDF does not — pdfkit understands
5
+ * hex and the CSS colour keywords. Converting here means a theme behaves the
6
+ * same whichever engine renders it, rather than silently losing a colour in
7
+ * one of them.
8
+ */
9
+ /**
10
+ * Convert any accepted theme colour to `#rrggbb`.
11
+ *
12
+ * Alpha is dropped: PDF has no per-colour alpha in this position, and a
13
+ * silently half-transparent rule is worse than an opaque one.
14
+ */
15
+ export declare function toHexColor(value: string, fallback?: string): string;
16
+ /**
17
+ * Convert a CSS length to PDF points.
18
+ *
19
+ * `em`, `rem` and `%` are relative to a layout that does not exist here, so
20
+ * they fall back rather than being guessed at.
21
+ */
22
+ export declare function toPoints(value: string, fallback: number): number;
23
+ /**
24
+ * Expand a CSS margin shorthand into PDF's `[left, top, right, bottom]`.
25
+ *
26
+ * CSS orders shorthand clockwise from the top; pdfmake starts at the left.
27
+ * Getting that wrong swaps the margins on a non-square page, which is exactly
28
+ * the kind of error nobody notices until something prints.
29
+ */
30
+ export declare function toMargins(value: string, fallback: number): [number, number, number, number];
31
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../src/color.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAuBH;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,MAAM,CA+BtE;AAqED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQhE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAU3F"}
package/dist/color.js ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Colour conversion for the PDF engine.
3
+ *
4
+ * A browser accepts every CSS colour form; PDF does not — pdfkit understands
5
+ * hex and the CSS colour keywords. Converting here means a theme behaves the
6
+ * same whichever engine renders it, rather than silently losing a colour in
7
+ * one of them.
8
+ */
9
+ /** CSS colour keywords, kept to the ones a document is plausibly styled with. */
10
+ const KEYWORDS = {
11
+ black: '#000000',
12
+ white: '#ffffff',
13
+ red: '#ff0000',
14
+ green: '#008000',
15
+ blue: '#0000ff',
16
+ navy: '#000080',
17
+ teal: '#008080',
18
+ olive: '#808000',
19
+ purple: '#800080',
20
+ maroon: '#800000',
21
+ gray: '#808080',
22
+ grey: '#808080',
23
+ silver: '#c0c0c0',
24
+ orange: '#ffa500',
25
+ yellow: '#ffff00',
26
+ rebeccapurple: '#663399',
27
+ transparent: '#ffffff',
28
+ };
29
+ /**
30
+ * Convert any accepted theme colour to `#rrggbb`.
31
+ *
32
+ * Alpha is dropped: PDF has no per-colour alpha in this position, and a
33
+ * silently half-transparent rule is worse than an opaque one.
34
+ */
35
+ export function toHexColor(value, fallback = '#000000') {
36
+ const input = value.trim().toLowerCase();
37
+ const keyword = KEYWORDS[input];
38
+ if (keyword)
39
+ return keyword;
40
+ if (input.startsWith('#')) {
41
+ const digits = input.slice(1);
42
+ if (digits.length === 3 || digits.length === 4) {
43
+ const [r, g, b] = [...digits.slice(0, 3)];
44
+ return `#${r}${r}${g}${g}${b}${b}`;
45
+ }
46
+ if (digits.length === 6 || digits.length === 8)
47
+ return `#${digits.slice(0, 6)}`;
48
+ return fallback;
49
+ }
50
+ const rgb = /^rgba?\(([^)]+)\)$/.exec(input);
51
+ if (rgb) {
52
+ const parts = splitComponents(rgb[1]);
53
+ if (parts.length < 3)
54
+ return fallback;
55
+ return hex(channel(parts[0]), channel(parts[1]), channel(parts[2]));
56
+ }
57
+ const hsl = /^hsla?\(([^)]+)\)$/.exec(input);
58
+ if (hsl) {
59
+ const parts = splitComponents(hsl[1]);
60
+ if (parts.length < 3)
61
+ return fallback;
62
+ return hslToHex(angle(parts[0]), percent(parts[1]), percent(parts[2]));
63
+ }
64
+ return fallback;
65
+ }
66
+ function splitComponents(body) {
67
+ return body
68
+ .split(/[,\s/]+/)
69
+ .map((part) => part.trim())
70
+ .filter((part) => part !== '');
71
+ }
72
+ /** An rgb channel: 0-255, or a percentage. */
73
+ function channel(part) {
74
+ const value = part.endsWith('%')
75
+ ? (Number.parseFloat(part) / 100) * 255
76
+ : Number.parseFloat(part);
77
+ return clamp(Math.round(value), 0, 255);
78
+ }
79
+ function angle(part) {
80
+ const value = Number.parseFloat(part);
81
+ return Number.isFinite(value) ? ((value % 360) + 360) % 360 : 0;
82
+ }
83
+ function percent(part) {
84
+ const value = Number.parseFloat(part);
85
+ return clamp(Number.isFinite(value) ? value / 100 : 0, 0, 1);
86
+ }
87
+ function clamp(value, min, max) {
88
+ return Math.min(Math.max(value, min), max);
89
+ }
90
+ function hex(r, g, b) {
91
+ return `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;
92
+ }
93
+ function hslToHex(hue, saturation, lightness) {
94
+ const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
95
+ const sector = hue / 60;
96
+ const second = chroma * (1 - Math.abs((sector % 2) - 1));
97
+ const [r, g, b] = sector < 1
98
+ ? [chroma, second, 0]
99
+ : sector < 2
100
+ ? [second, chroma, 0]
101
+ : sector < 3
102
+ ? [0, chroma, second]
103
+ : sector < 4
104
+ ? [0, second, chroma]
105
+ : sector < 5
106
+ ? [second, 0, chroma]
107
+ : [chroma, 0, second];
108
+ const offset = lightness - chroma / 2;
109
+ return hex(Math.round((r + offset) * 255), Math.round((g + offset) * 255), Math.round((b + offset) * 255));
110
+ }
111
+ /** Units CSS uses that convert to PDF points without a layout context. */
112
+ const UNIT_TO_POINTS = {
113
+ pt: 1,
114
+ px: 0.75,
115
+ pc: 12,
116
+ mm: 72 / 25.4,
117
+ cm: 72 / 2.54,
118
+ in: 72,
119
+ };
120
+ /**
121
+ * Convert a CSS length to PDF points.
122
+ *
123
+ * `em`, `rem` and `%` are relative to a layout that does not exist here, so
124
+ * they fall back rather than being guessed at.
125
+ */
126
+ export function toPoints(value, fallback) {
127
+ const match = /^([0-9.]+)([a-z%]*)$/.exec(value.trim());
128
+ if (!match)
129
+ return fallback;
130
+ const size = Number.parseFloat(match[1]);
131
+ if (!Number.isFinite(size))
132
+ return fallback;
133
+ const unit = match[2] === '' ? 'pt' : match[2];
134
+ const factor = UNIT_TO_POINTS[unit];
135
+ return factor === undefined ? fallback : size * factor;
136
+ }
137
+ /**
138
+ * Expand a CSS margin shorthand into PDF's `[left, top, right, bottom]`.
139
+ *
140
+ * CSS orders shorthand clockwise from the top; pdfmake starts at the left.
141
+ * Getting that wrong swaps the margins on a non-square page, which is exactly
142
+ * the kind of error nobody notices until something prints.
143
+ */
144
+ export function toMargins(value, fallback) {
145
+ const parts = value.trim().split(/\s+/).filter(Boolean);
146
+ const points = parts.map((part) => toPoints(part, fallback));
147
+ const [top = fallback, right = points[0] ?? fallback, bottom = points[0] ?? fallback, left = points[1] ?? points[0] ?? fallback,] = points;
148
+ return [left, top, right, bottom];
149
+ }
150
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.js","sourceRoot":"","sources":["../src/color.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { type InvoiceData } from '@open-nav/core';
2
+ /**
3
+ * The data export an invoicing program must be able to produce.
4
+ *
5
+ * Decree 23/2014. (VI. 30.) NGM requires every invoicing program to have a
6
+ * built-in "adóhatósági ellenőrzési adatszolgáltatás" function that exports
7
+ * the invoices issued in a given date range, or in a given invoice number
8
+ * range, as XML.
9
+ *
10
+ * Two structures are permitted. Annexes 2 and 3 of the decree define one
11
+ * (`szamla.xsd`); section 13/A(1) permits the other — the structure published
12
+ * for the online invoice data service, which is `invoiceData.xsd`. This
13
+ * implementation produces the second, because it is the same schema this
14
+ * project already generates, validates and round-trips against NAV's own
15
+ * sample documents.
16
+ *
17
+ * That choice belongs to the taxpayer, not to this library. If your auditor
18
+ * asks for the Annex 3 structure specifically, this export is not it.
19
+ */
20
+ export interface DataExportSelection {
21
+ /** First issue date to include, inclusive, as `yyyy-mm-dd`. */
22
+ issueDateFrom?: string;
23
+ /** Last issue date to include, inclusive. */
24
+ issueDateTo?: string;
25
+ /** First invoice number to include, inclusive. */
26
+ invoiceNumberFrom?: string;
27
+ /** Last invoice number to include, inclusive. */
28
+ invoiceNumberTo?: string;
29
+ }
30
+ export interface DataExportOptions extends DataExportSelection {
31
+ /**
32
+ * How invoice numbers are ordered when a number range is given.
33
+ *
34
+ * The default compares digit runs numerically, so `2024/9` sorts before
35
+ * `2024/10`. Plain text ordering puts them the other way round and would
36
+ * silently drop invoices from the middle of a requested range — which is
37
+ * the kind of mistake an audit is precisely designed to find.
38
+ */
39
+ compareInvoiceNumbers?: (left: string, right: string) => number;
40
+ /** Injectable clock, so an export is reproducible in tests. */
41
+ now?: () => Date;
42
+ }
43
+ export interface ExportedFile {
44
+ /** Path within the export, e.g. `invoices/2024-000123.xml`. */
45
+ name: string;
46
+ contents: string;
47
+ }
48
+ export interface ExportedInvoice {
49
+ invoiceNumber: string;
50
+ invoiceIssueDate: string;
51
+ file: string;
52
+ /** SHA-256 of the file, so the export can be shown to be unaltered. */
53
+ sha256: string;
54
+ }
55
+ export interface DataExportManifest {
56
+ /** What was asked for. */
57
+ selection: DataExportSelection;
58
+ /** The structure used, and the provision that permits it. */
59
+ structure: {
60
+ schema: string;
61
+ namespace: string;
62
+ basis: string;
63
+ };
64
+ generatedAt: string;
65
+ invoiceCount: number;
66
+ invoices: ExportedInvoice[];
67
+ }
68
+ export interface DataExportResult {
69
+ /** Every file in the export, manifest included. */
70
+ files: ExportedFile[];
71
+ manifest: DataExportManifest;
72
+ }
73
+ /**
74
+ * Natural ordering for invoice numbers: digit runs compare as numbers.
75
+ *
76
+ * `2024/9` before `2024/10`, and `A-2` before `A-10`.
77
+ */
78
+ export declare function compareInvoiceNumbersNaturally(left: string, right: string): number;
79
+ /** Whether an invoice falls inside the requested selection. */
80
+ export declare function isSelected(invoice: InvoiceData, options: DataExportOptions): boolean;
81
+ /**
82
+ * Build the export from a set of invoices.
83
+ *
84
+ * One XML file per invoice, plus a manifest recording what was selected and
85
+ * the SHA-256 of every file, so the export can later be shown to be the one
86
+ * that was produced.
87
+ */
88
+ export declare function createDataExport(invoices: InvoiceData[], options?: DataExportOptions): DataExportResult;
89
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAErE;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAChE,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,SAAS,EAAE,mBAAmB,CAAC;IAC/B,6DAA6D;IAC7D,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,QAAQ,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAsBlF;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAYpF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CA+ClB"}
package/dist/export.js ADDED
@@ -0,0 +1,115 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { serializeDocument } from '@open-nav/core';
3
+ /**
4
+ * Natural ordering for invoice numbers: digit runs compare as numbers.
5
+ *
6
+ * `2024/9` before `2024/10`, and `A-2` before `A-10`.
7
+ */
8
+ export function compareInvoiceNumbersNaturally(left, right) {
9
+ const chunk = /(\d+|\D+)/g;
10
+ const leftParts = left.match(chunk) ?? [];
11
+ const rightParts = right.match(chunk) ?? [];
12
+ for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
13
+ const a = leftParts[index];
14
+ const b = rightParts[index];
15
+ if (a === undefined)
16
+ return -1;
17
+ if (b === undefined)
18
+ return 1;
19
+ const aNumeric = /^\d+$/.test(a);
20
+ const bNumeric = /^\d+$/.test(b);
21
+ if (aNumeric && bNumeric) {
22
+ // BigInt, because an invoice number can be longer than a safe integer.
23
+ const difference = BigInt(a) - BigInt(b);
24
+ if (difference !== 0n)
25
+ return difference < 0n ? -1 : 1;
26
+ continue;
27
+ }
28
+ if (a !== b)
29
+ return a < b ? -1 : 1;
30
+ }
31
+ return 0;
32
+ }
33
+ /** Whether an invoice falls inside the requested selection. */
34
+ export function isSelected(invoice, options) {
35
+ const compare = options.compareInvoiceNumbers ?? compareInvoiceNumbersNaturally;
36
+ if (options.issueDateFrom && invoice.invoiceIssueDate < options.issueDateFrom)
37
+ return false;
38
+ if (options.issueDateTo && invoice.invoiceIssueDate > options.issueDateTo)
39
+ return false;
40
+ if (options.invoiceNumberFrom && compare(invoice.invoiceNumber, options.invoiceNumberFrom) < 0) {
41
+ return false;
42
+ }
43
+ if (options.invoiceNumberTo && compare(invoice.invoiceNumber, options.invoiceNumberTo) > 0) {
44
+ return false;
45
+ }
46
+ return true;
47
+ }
48
+ /**
49
+ * Build the export from a set of invoices.
50
+ *
51
+ * One XML file per invoice, plus a manifest recording what was selected and
52
+ * the SHA-256 of every file, so the export can later be shown to be the one
53
+ * that was produced.
54
+ */
55
+ export function createDataExport(invoices, options = {}) {
56
+ const now = options.now ?? (() => new Date());
57
+ const compare = options.compareInvoiceNumbers ?? compareInvoiceNumbersNaturally;
58
+ const selected = invoices
59
+ .filter((invoice) => isSelected(invoice, options))
60
+ .sort((left, right) => compare(left.invoiceNumber, right.invoiceNumber));
61
+ const files = [];
62
+ const entries = [];
63
+ const usedNames = new Set();
64
+ for (const invoice of selected) {
65
+ const name = uniqueName(`invoices/${safeFileName(invoice.invoiceNumber)}.xml`, usedNames);
66
+ const contents = serializeDocument('InvoiceData', invoice, { indent: ' ' });
67
+ files.push({ name, contents });
68
+ entries.push({
69
+ invoiceNumber: invoice.invoiceNumber,
70
+ invoiceIssueDate: invoice.invoiceIssueDate,
71
+ file: name,
72
+ sha256: createHash('sha256').update(contents, 'utf8').digest('hex'),
73
+ });
74
+ }
75
+ const selection = {
76
+ ...(options.issueDateFrom ? { issueDateFrom: options.issueDateFrom } : {}),
77
+ ...(options.issueDateTo ? { issueDateTo: options.issueDateTo } : {}),
78
+ ...(options.invoiceNumberFrom ? { invoiceNumberFrom: options.invoiceNumberFrom } : {}),
79
+ ...(options.invoiceNumberTo ? { invoiceNumberTo: options.invoiceNumberTo } : {}),
80
+ };
81
+ const manifest = {
82
+ selection,
83
+ structure: {
84
+ schema: 'invoiceData.xsd',
85
+ namespace: 'http://schemas.nav.gov.hu/OSA/3.0/data',
86
+ basis: '23/2014. (VI. 30.) NGM rendelet 13/A. § (1) — the data structure published ' +
87
+ 'for the online invoice data service',
88
+ },
89
+ generatedAt: now().toISOString(),
90
+ invoiceCount: entries.length,
91
+ invoices: entries,
92
+ };
93
+ files.push({ name: 'manifest.json', contents: `${JSON.stringify(manifest, null, 2)}\n` });
94
+ return { files, manifest };
95
+ }
96
+ /** An invoice number can contain `/`, which a file name cannot. */
97
+ function safeFileName(invoiceNumber) {
98
+ const cleaned = invoiceNumber.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
99
+ return cleaned === '' ? 'invoice' : cleaned;
100
+ }
101
+ /** Two invoice numbers can sanitise to the same name; keep them distinct. */
102
+ function uniqueName(name, used) {
103
+ if (!used.has(name)) {
104
+ used.add(name);
105
+ return name;
106
+ }
107
+ for (let suffix = 2;; suffix += 1) {
108
+ const candidate = name.replace(/\.xml$/, `-${suffix}.xml`);
109
+ if (!used.has(candidate)) {
110
+ used.add(candidate);
111
+ return candidate;
112
+ }
113
+ }
114
+ }
115
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export type DocumentLanguage = 'hu' | 'en';
2
+ /**
3
+ * Format an exact decimal for display without going through a JavaScript
4
+ * number.
5
+ *
6
+ * The amounts on an invoice are exact decimal strings precisely so they do
7
+ * not lose precision; rendering them through `Number` to get thousands
8
+ * separators would throw that away at the top of NAV's 18 digit range.
9
+ */
10
+ export declare function formatAmount(value: string, language: DocumentLanguage, fractionDigits?: number): string;
11
+ /** Format a VAT rate like `0.27` as a percentage. */
12
+ export declare function formatPercentage(value: string, language: DocumentLanguage): string;
13
+ /** Dates are ISO in the data; Hungarian documents write them with dots. */
14
+ export declare function formatDate(value: string, language: DocumentLanguage): string;
15
+ /** Compose a tax number from its parts, in the written form. */
16
+ export declare function formatTaxNumber(parts: {
17
+ taxpayerId: string;
18
+ vatCode?: string;
19
+ countyCode?: string;
20
+ }): string;
21
+ /** Escape text for HTML. */
22
+ export declare function escapeHtml(value: string): string;
23
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,SAAI,GACjB,MAAM,CAaR;AAED,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAOlF;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAK5E;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAET;AAED,4BAA4B;AAC5B,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD"}