@open-nav/core 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 (133) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +132 -0
  3. package/dist/constants.d.ts +49 -0
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/constants.js +58 -0
  6. package/dist/constants.js.map +1 -0
  7. package/dist/crypto/hash.d.ts +16 -0
  8. package/dist/crypto/hash.d.ts.map +1 -0
  9. package/dist/crypto/hash.js +30 -0
  10. package/dist/crypto/hash.js.map +1 -0
  11. package/dist/crypto/node-provider.d.ts +9 -0
  12. package/dist/crypto/node-provider.d.ts.map +1 -0
  13. package/dist/crypto/node-provider.js +18 -0
  14. package/dist/crypto/node-provider.js.map +1 -0
  15. package/dist/crypto/provider.d.ts +28 -0
  16. package/dist/crypto/provider.d.ts.map +1 -0
  17. package/dist/crypto/provider.js +14 -0
  18. package/dist/crypto/provider.js.map +1 -0
  19. package/dist/crypto/request-id.d.ts +16 -0
  20. package/dist/crypto/request-id.d.ts.map +1 -0
  21. package/dist/crypto/request-id.js +59 -0
  22. package/dist/crypto/request-id.js.map +1 -0
  23. package/dist/crypto/signature.d.ts +40 -0
  24. package/dist/crypto/signature.d.ts.map +1 -0
  25. package/dist/crypto/signature.js +38 -0
  26. package/dist/crypto/signature.js.map +1 -0
  27. package/dist/crypto/token.d.ts +14 -0
  28. package/dist/crypto/token.d.ts.map +1 -0
  29. package/dist/crypto/token.js +82 -0
  30. package/dist/crypto/token.js.map +1 -0
  31. package/dist/crypto/web-provider.d.ts +18 -0
  32. package/dist/crypto/web-provider.d.ts.map +1 -0
  33. package/dist/crypto/web-provider.js +31 -0
  34. package/dist/crypto/web-provider.js.map +1 -0
  35. package/dist/errors.d.ts +75 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +71 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/generated/fault-codes.d.ts +1343 -0
  40. package/dist/generated/fault-codes.d.ts.map +1 -0
  41. package/dist/generated/fault-codes.js +1331 -0
  42. package/dist/generated/fault-codes.js.map +1 -0
  43. package/dist/generated/index.d.ts +4 -0
  44. package/dist/generated/index.d.ts.map +1 -0
  45. package/dist/generated/index.js +5 -0
  46. package/dist/generated/index.js.map +1 -0
  47. package/dist/generated/schema.d.ts +13 -0
  48. package/dist/generated/schema.d.ts.map +1 -0
  49. package/dist/generated/schema.js +3756 -0
  50. package/dist/generated/schema.js.map +1 -0
  51. package/dist/generated/types.d.ts +5580 -0
  52. package/dist/generated/types.d.ts.map +1 -0
  53. package/dist/generated/types.js +6 -0
  54. package/dist/generated/types.js.map +1 -0
  55. package/dist/index.d.ts +23 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +23 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/invoice/payload.d.ts +40 -0
  60. package/dist/invoice/payload.d.ts.map +1 -0
  61. package/dist/invoice/payload.js +66 -0
  62. package/dist/invoice/payload.js.map +1 -0
  63. package/dist/money/decimal.d.ts +69 -0
  64. package/dist/money/decimal.d.ts.map +1 -0
  65. package/dist/money/decimal.js +190 -0
  66. package/dist/money/decimal.js.map +1 -0
  67. package/dist/money/summary.d.ts +60 -0
  68. package/dist/money/summary.d.ts.map +1 -0
  69. package/dist/money/summary.js +236 -0
  70. package/dist/money/summary.js.map +1 -0
  71. package/dist/time.d.ts +16 -0
  72. package/dist/time.d.ts.map +1 -0
  73. package/dist/time.js +41 -0
  74. package/dist/time.js.map +1 -0
  75. package/dist/validation/issue.d.ts +67 -0
  76. package/dist/validation/issue.d.ts.map +1 -0
  77. package/dist/validation/issue.js +40 -0
  78. package/dist/validation/issue.js.map +1 -0
  79. package/dist/validation/rules.d.ts +34 -0
  80. package/dist/validation/rules.d.ts.map +1 -0
  81. package/dist/validation/rules.js +290 -0
  82. package/dist/validation/rules.js.map +1 -0
  83. package/dist/validation/schema.d.ts +14 -0
  84. package/dist/validation/schema.d.ts.map +1 -0
  85. package/dist/validation/schema.js +231 -0
  86. package/dist/validation/schema.js.map +1 -0
  87. package/dist/validation/tax-number.d.ts +39 -0
  88. package/dist/validation/tax-number.d.ts.map +1 -0
  89. package/dist/validation/tax-number.js +79 -0
  90. package/dist/validation/tax-number.js.map +1 -0
  91. package/dist/validation/validate.d.ts +29 -0
  92. package/dist/validation/validate.d.ts.map +1 -0
  93. package/dist/validation/validate.js +33 -0
  94. package/dist/validation/validate.js.map +1 -0
  95. package/dist/xml/descriptor.d.ts +82 -0
  96. package/dist/xml/descriptor.d.ts.map +1 -0
  97. package/dist/xml/descriptor.js +32 -0
  98. package/dist/xml/descriptor.js.map +1 -0
  99. package/dist/xml/read.d.ts +25 -0
  100. package/dist/xml/read.d.ts.map +1 -0
  101. package/dist/xml/read.js +230 -0
  102. package/dist/xml/read.js.map +1 -0
  103. package/dist/xml/write.d.ts +28 -0
  104. package/dist/xml/write.d.ts.map +1 -0
  105. package/dist/xml/write.js +299 -0
  106. package/dist/xml/write.js.map +1 -0
  107. package/package.json +55 -0
  108. package/src/constants.ts +76 -0
  109. package/src/crypto/hash.ts +35 -0
  110. package/src/crypto/node-provider.ts +19 -0
  111. package/src/crypto/provider.ts +37 -0
  112. package/src/crypto/request-id.ts +63 -0
  113. package/src/crypto/signature.ts +58 -0
  114. package/src/crypto/token.ts +86 -0
  115. package/src/crypto/web-provider.ts +32 -0
  116. package/src/errors.ts +109 -0
  117. package/src/generated/fault-codes.ts +1338 -0
  118. package/src/generated/index.ts +4 -0
  119. package/src/generated/schema.ts +3760 -0
  120. package/src/generated/types.ts +5829 -0
  121. package/src/index.ts +22 -0
  122. package/src/invoice/payload.ts +102 -0
  123. package/src/money/decimal.ts +218 -0
  124. package/src/money/summary.ts +380 -0
  125. package/src/time.ts +44 -0
  126. package/src/validation/issue.ts +102 -0
  127. package/src/validation/rules.ts +548 -0
  128. package/src/validation/schema.ts +315 -0
  129. package/src/validation/tax-number.ts +96 -0
  130. package/src/validation/validate.ts +45 -0
  131. package/src/xml/descriptor.ts +112 -0
  132. package/src/xml/read.ts +303 -0
  133. package/src/xml/write.ts +391 -0
@@ -0,0 +1,236 @@
1
+ import { Decimal, sum } from './decimal.js';
2
+ /** Decimal places NAV's MonetaryType permits. */
3
+ const MONETARY_SCALE = 2;
4
+ /**
5
+ * Compute the invoice summary from the lines.
6
+ *
7
+ * The summary is not free-form: `summaryByVatRate` must carry one entry per
8
+ * distinct VAT rate appearing on the lines, its net and VAT amounts must be
9
+ * the sums of the corresponding line amounts, and the invoice totals must be
10
+ * the sums of those. NAV rejects the whole batch when they disagree, so this
11
+ * derives them rather than trusting a caller to keep them in step.
12
+ *
13
+ * Lines are grouped by their entire `lineVatRate` value, not by percentage
14
+ * alone: a 27% line and an exempt line are different rates, and so are two
15
+ * exempt lines citing different legal grounds.
16
+ */
17
+ export function computeInvoiceSummary(invoice, options = {}) {
18
+ const scale = options.scale ?? MONETARY_SCALE;
19
+ const mode = options.mode ?? 'half-up';
20
+ const lines = invoice.invoiceLines?.line ?? [];
21
+ const normal = lines.filter((line) => line.lineAmountsNormal !== undefined);
22
+ const simplified = lines.filter((line) => line.lineAmountsSimplified !== undefined);
23
+ const summary = {};
24
+ if (simplified.length > 0) {
25
+ summary.summarySimplified = groupBy(simplified, (line) => line.lineAmountsSimplified.lineVatRate).map(([vatRate, group]) => ({
26
+ vatRate,
27
+ vatContentGrossAmount: total(group.map((line) => line.lineAmountsSimplified.lineGrossAmountSimplified), scale, mode),
28
+ vatContentGrossAmountHUF: total(group.map((line) => line.lineAmountsSimplified.lineGrossAmountSimplifiedHUF), scale, mode),
29
+ }));
30
+ }
31
+ if (normal.length > 0 || simplified.length === 0) {
32
+ const byVatRate = groupBy(normal, (line) => line.lineAmountsNormal.lineVatRate).map(([vatRate, group]) => ({
33
+ vatRate,
34
+ vatRateNetData: {
35
+ vatRateNetAmount: total(group.map((line) => line.lineAmountsNormal.lineNetAmountData.lineNetAmount), scale, mode),
36
+ vatRateNetAmountHUF: total(group.map((line) => line.lineAmountsNormal.lineNetAmountData.lineNetAmountHUF), scale, mode),
37
+ },
38
+ vatRateVatData: {
39
+ vatRateVatAmount: total(group.map((line) => lineVatAmount(line, 'amount', scale, mode)), scale, mode),
40
+ vatRateVatAmountHUF: total(group.map((line) => lineVatAmount(line, 'huf', scale, mode)), scale, mode),
41
+ },
42
+ }));
43
+ summary.summaryNormal = {
44
+ summaryByVatRate: byVatRate,
45
+ invoiceNetAmount: total(byVatRate.map((entry) => entry.vatRateNetData.vatRateNetAmount), scale, mode),
46
+ invoiceNetAmountHUF: total(byVatRate.map((entry) => entry.vatRateNetData.vatRateNetAmountHUF), scale, mode),
47
+ invoiceVatAmount: total(byVatRate.map((entry) => entry.vatRateVatData.vatRateVatAmount), scale, mode),
48
+ invoiceVatAmountHUF: total(byVatRate.map((entry) => entry.vatRateVatData.vatRateVatAmountHUF), scale, mode),
49
+ };
50
+ }
51
+ summary.summaryGrossData = {
52
+ invoiceGrossAmount: grossTotal(summary, 'amount', scale, mode),
53
+ invoiceGrossAmountHUF: grossTotal(summary, 'huf', scale, mode),
54
+ };
55
+ return summary;
56
+ }
57
+ /**
58
+ * Check that an invoice's stated summary agrees with its lines.
59
+ *
60
+ * Returns the discrepancies rather than throwing, because a report that lists
61
+ * every mismatch at once is far more useful than the first one NAV happens to
62
+ * complain about.
63
+ */
64
+ export function checkInvoiceSummary(invoice, options = {}) {
65
+ if (!hasAmountBearingLines(invoice))
66
+ return [];
67
+ const issues = [];
68
+ const stated = invoice.invoiceSummary;
69
+ const expected = computeInvoiceSummary(invoice, options);
70
+ const compare = (path, actual, wanted) => {
71
+ if (actual === undefined || wanted === undefined)
72
+ return;
73
+ if (!Decimal.from(actual).equals(Decimal.from(wanted))) {
74
+ issues.push({
75
+ path,
76
+ code: 'SUMMARY_MISMATCH',
77
+ message: `is ${actual} but the lines total ${wanted}`,
78
+ });
79
+ }
80
+ };
81
+ if (stated.summaryNormal && expected.summaryNormal) {
82
+ const statedNormal = stated.summaryNormal;
83
+ const expectedNormal = expected.summaryNormal;
84
+ compare('invoiceSummary.summaryNormal.invoiceNetAmount', statedNormal.invoiceNetAmount, expectedNormal.invoiceNetAmount);
85
+ compare('invoiceSummary.summaryNormal.invoiceNetAmountHUF', statedNormal.invoiceNetAmountHUF, expectedNormal.invoiceNetAmountHUF);
86
+ compare('invoiceSummary.summaryNormal.invoiceVatAmount', statedNormal.invoiceVatAmount, expectedNormal.invoiceVatAmount);
87
+ compare('invoiceSummary.summaryNormal.invoiceVatAmountHUF', statedNormal.invoiceVatAmountHUF, expectedNormal.invoiceVatAmountHUF);
88
+ if (statedNormal.summaryByVatRate.length !== expectedNormal.summaryByVatRate.length) {
89
+ issues.push({
90
+ path: 'invoiceSummary.summaryNormal.summaryByVatRate',
91
+ code: 'SUMMARY_VAT_RATE_COUNT',
92
+ message: `has ${statedNormal.summaryByVatRate.length} entries but the lines use ` +
93
+ `${expectedNormal.summaryByVatRate.length} distinct VAT rates`,
94
+ });
95
+ }
96
+ else {
97
+ // Order is not significant, so match entries by their VAT rate.
98
+ for (const [index, statedEntry] of statedNormal.summaryByVatRate.entries()) {
99
+ const key = vatRateKey(statedEntry.vatRate);
100
+ const expectedEntry = expectedNormal.summaryByVatRate.find((candidate) => vatRateKey(candidate.vatRate) === key);
101
+ const path = `invoiceSummary.summaryNormal.summaryByVatRate.${index}`;
102
+ if (!expectedEntry) {
103
+ issues.push({
104
+ path,
105
+ code: 'SUMMARY_VAT_RATE_UNKNOWN',
106
+ message: 'states a VAT rate that no line uses',
107
+ });
108
+ continue;
109
+ }
110
+ compare(`${path}.vatRateNetData.vatRateNetAmount`, statedEntry.vatRateNetData.vatRateNetAmount, expectedEntry.vatRateNetData.vatRateNetAmount);
111
+ compare(`${path}.vatRateVatData.vatRateVatAmount`, statedEntry.vatRateVatData.vatRateVatAmount, expectedEntry.vatRateVatData.vatRateVatAmount);
112
+ }
113
+ }
114
+ }
115
+ if (stated.summaryGrossData && expected.summaryGrossData) {
116
+ compare('invoiceSummary.summaryGrossData.invoiceGrossAmount', stated.summaryGrossData.invoiceGrossAmount, expected.summaryGrossData.invoiceGrossAmount);
117
+ compare('invoiceSummary.summaryGrossData.invoiceGrossAmountHUF', stated.summaryGrossData.invoiceGrossAmountHUF, expected.summaryGrossData.invoiceGrossAmountHUF);
118
+ }
119
+ return issues;
120
+ }
121
+ /**
122
+ * Check every invoice in a document, including batch modification documents.
123
+ *
124
+ * Paths are prefixed with the batch index so a caller can tell which invoice
125
+ * of a multi-invoice document is out.
126
+ */
127
+ export function checkDocumentSummaries(document, options = {}) {
128
+ const single = document.invoiceMain.invoice;
129
+ if (single)
130
+ return checkInvoiceSummary(single, options);
131
+ return (document.invoiceMain.batchInvoice ?? []).flatMap((entry) => checkInvoiceSummary(entry.invoice, options).map((issue) => ({
132
+ ...issue,
133
+ path: `batchInvoice.${entry.batchIndex}.${issue.path}`,
134
+ })));
135
+ }
136
+ function grossTotal(summary, which, scale, mode) {
137
+ const parts = [];
138
+ if (summary.summaryNormal) {
139
+ parts.push(which === 'amount'
140
+ ? summary.summaryNormal.invoiceNetAmount
141
+ : summary.summaryNormal.invoiceNetAmountHUF, which === 'amount'
142
+ ? summary.summaryNormal.invoiceVatAmount
143
+ : summary.summaryNormal.invoiceVatAmountHUF);
144
+ }
145
+ for (const entry of summary.summarySimplified ?? []) {
146
+ parts.push(which === 'amount' ? entry.vatContentGrossAmount : entry.vatContentGrossAmountHUF);
147
+ }
148
+ return total(parts, scale, mode);
149
+ }
150
+ /**
151
+ * VAT of a line, stated or derived.
152
+ *
153
+ * `lineVatData` is optional in the schema, and NAV's own advance-invoice
154
+ * samples omit it: the line states its net amount and its rate, and the VAT
155
+ * follows from the two. Treating an absent `lineVatData` as zero would make
156
+ * every such invoice look unbalanced.
157
+ *
158
+ * When the rate is not a percentage — exempt, out of scope, reverse charge —
159
+ * there is no VAT to derive and the contribution is zero.
160
+ */
161
+ function lineVatAmount(line, which, scale, mode) {
162
+ const amounts = line.lineAmountsNormal;
163
+ const stated = which === 'amount' ? amounts.lineVatData?.lineVatAmount : amounts.lineVatData?.lineVatAmountHUF;
164
+ if (stated !== undefined)
165
+ return stated;
166
+ const percentage = amounts.lineVatRate.vatPercentage;
167
+ if (percentage === undefined)
168
+ return '0';
169
+ const net = which === 'amount'
170
+ ? amounts.lineNetAmountData.lineNetAmount
171
+ : amounts.lineNetAmountData.lineNetAmountHUF;
172
+ return Decimal.from(net).multiply(percentage).round(scale, mode).toString();
173
+ }
174
+ /**
175
+ * Whether any line carries amounts at all.
176
+ *
177
+ * A line-data modification document can reference lines of the original
178
+ * invoice without restating amounts. There is nothing to reconcile then, and
179
+ * reporting a mismatch would be a false positive.
180
+ */
181
+ export function hasAmountBearingLines(invoice) {
182
+ return (invoice.invoiceLines?.line ?? []).some((line) => line.lineAmountsNormal !== undefined || line.lineAmountsSimplified !== undefined);
183
+ }
184
+ function total(values, scale, mode) {
185
+ return sum(values).round(scale, mode).toString();
186
+ }
187
+ function groupBy(lines, rateOf) {
188
+ const groups = new Map();
189
+ for (const line of lines) {
190
+ const rate = rateOf(line);
191
+ const key = vatRateKey(rate);
192
+ const existing = groups.get(key);
193
+ if (existing)
194
+ existing[1].push(line);
195
+ else
196
+ groups.set(key, [rate, [line]]);
197
+ }
198
+ return [...groups.values()];
199
+ }
200
+ /**
201
+ * Stable identity of a VAT rate.
202
+ *
203
+ * Numeric rates are compared by value, so `0.27` and `0.2700` group together.
204
+ *
205
+ * Exemption and out-of-scope rates are identified by their `case` — the legal
206
+ * ground code — and *not* by the accompanying `reason`, which is free text.
207
+ * NAV's own new-means-of-transport sample makes the point: the line cites
208
+ * case `KBAUK` with reason "Áfa tv. 89. §" while the summary cites the same
209
+ * case with reason "Adómentes Közösségen belüli új közlekedési eszköz
210
+ * értékesítés". Same rate, different prose. Grouping on the prose would split
211
+ * one rate into two and report a spurious mismatch.
212
+ */
213
+ export function vatRateKey(rate) {
214
+ const parts = [];
215
+ for (const key of Object.keys(rate).sort()) {
216
+ const value = rate[key];
217
+ if (value === undefined)
218
+ continue;
219
+ if ((key === 'vatPercentage' || key === 'vatContent') && typeof value === 'string') {
220
+ parts.push(`${key}=${Decimal.from(value).round(6).toString()}`);
221
+ }
222
+ else if (isDetailedReason(value)) {
223
+ parts.push(`${key}=${value.case}`);
224
+ }
225
+ else {
226
+ parts.push(`${key}=${JSON.stringify(value)}`);
227
+ }
228
+ }
229
+ return parts.join('|');
230
+ }
231
+ function isDetailedReason(value) {
232
+ return (typeof value === 'object' &&
233
+ value !== null &&
234
+ typeof value.case === 'string');
235
+ }
236
+ //# sourceMappingURL=summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/money/summary.ts"],"names":[],"mappings":"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"}
package/dist/time.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Render the `header/timestamp` value: UTC, ISO 8601, millisecond precision,
3
+ * e.g. `2026-09-03T14:25:11.482Z`.
4
+ */
5
+ export declare function toHeaderTimestamp(value?: Date | string): string;
6
+ /**
7
+ * Render a timestamp the way the request signature consumes it: UTC,
8
+ * `yyyyMMddHHmmss`, truncated to whole seconds, no separators.
9
+ *
10
+ * This must correspond to the same instant as the `header/timestamp` of the
11
+ * request, otherwise NAV rejects the signature.
12
+ */
13
+ export declare function toSignatureTimestamp(value?: Date | string): string;
14
+ /** Render a NAV `date` field (`xs:date`) as `yyyy-MM-dd` in UTC. */
15
+ export declare function toNavDate(value?: Date | string): string;
16
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,GAAE,IAAI,GAAG,MAAmB,GAAG,MAAM,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,GAAE,IAAI,GAAG,MAAmB,GAAG,MAAM,CAI9E;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,KAAK,GAAE,IAAI,GAAG,MAAmB,GAAG,MAAM,CAKnE"}
package/dist/time.js ADDED
@@ -0,0 +1,41 @@
1
+ import { NavValidationError } from './errors.js';
2
+ /**
3
+ * Render the `header/timestamp` value: UTC, ISO 8601, millisecond precision,
4
+ * e.g. `2026-09-03T14:25:11.482Z`.
5
+ */
6
+ export function toHeaderTimestamp(value = new Date()) {
7
+ return toDate(value).toISOString();
8
+ }
9
+ /**
10
+ * Render a timestamp the way the request signature consumes it: UTC,
11
+ * `yyyyMMddHHmmss`, truncated to whole seconds, no separators.
12
+ *
13
+ * This must correspond to the same instant as the `header/timestamp` of the
14
+ * request, otherwise NAV rejects the signature.
15
+ */
16
+ export function toSignatureTimestamp(value = new Date()) {
17
+ const iso = toDate(value).toISOString();
18
+ // 2026-09-03T14:25:11.482Z -> 20260903142511
19
+ return `${iso.slice(0, 4)}${iso.slice(5, 7)}${iso.slice(8, 10)}${iso.slice(11, 13)}${iso.slice(14, 16)}${iso.slice(17, 19)}`;
20
+ }
21
+ /** Render a NAV `date` field (`xs:date`) as `yyyy-MM-dd` in UTC. */
22
+ export function toNavDate(value = new Date()) {
23
+ if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
24
+ return value;
25
+ }
26
+ return toDate(value).toISOString().slice(0, 10);
27
+ }
28
+ function toDate(value) {
29
+ const date = typeof value === 'string' ? new Date(value) : value;
30
+ if (Number.isNaN(date.getTime())) {
31
+ throw new NavValidationError('Invalid timestamp', [
32
+ {
33
+ path: 'timestamp',
34
+ code: 'INVALID_TIMESTAMP',
35
+ message: `cannot be parsed as a date: ${JSON.stringify(value)}`,
36
+ },
37
+ ]);
38
+ }
39
+ return date;
40
+ }
41
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAuB,IAAI,IAAI,EAAE;IACjE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAuB,IAAI,IAAI,EAAE;IACpE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IACxC,6CAA6C;IAC7C,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;AAC/H,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,SAAS,CAAC,QAAuB,IAAI,IAAI,EAAE;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACnE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,MAAM,CAAC,KAAoB;IAClC,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACjE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,kBAAkB,CAAC,mBAAmB,EAAE;YAChD;gBACE,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,mBAAmB;gBACzB,OAAO,EAAE,+BAA+B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;aAChE;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,67 @@
1
+ import type { NavFaultCode } from '../generated/fault-codes.js';
2
+ /**
3
+ * How much a finding matters.
4
+ *
5
+ * `error` means NAV would reject the report. `warning` means the data is
6
+ * accepted but suspect — a tax number whose check digit is wrong, say, which
7
+ * NAV validates against its taxpayer registry rather than arithmetically.
8
+ * Keeping the two apart is what makes the validator usable: a validator that
9
+ * cries wolf gets switched off.
10
+ */
11
+ export type Severity = 'error' | 'warning';
12
+ /**
13
+ * Findings this library reports that NAV has no code for.
14
+ *
15
+ * Kept separate from NAV's codes rather than forced into an approximate one,
16
+ * so it is always clear whether a finding means "the service would say this"
17
+ * or "we noticed this".
18
+ */
19
+ export type LocalFaultCode =
20
+ /** A tax number's check digit does not match. NAV checks its registry instead. */
21
+ 'LOCAL_TAXPAYER_ID_CHECK_DIGIT'
22
+ /** A county code outside the set NAV is known to issue. */
23
+ | 'LOCAL_COUNTY_CODE_UNKNOWN'
24
+ /** A VAT percentage that is not a current Hungarian rate. */
25
+ | 'LOCAL_VAT_PERCENTAGE_UNUSUAL';
26
+ export type FaultCode = NavFaultCode | LocalFaultCode;
27
+ export interface InvoiceValidationIssue {
28
+ /**
29
+ * The fault code. Where NAV defines one it is used verbatim, so a local
30
+ * failure names exactly what the service would have said, and the compiler
31
+ * checks it against the generated catalogue.
32
+ */
33
+ code: FaultCode;
34
+ /** `nav` for a code NAV defines, `local` for one of ours. */
35
+ origin: 'nav' | 'local';
36
+ severity: Severity;
37
+ /** Dotted path to the offending value, e.g. `invoiceLines.line.2.quantity`. */
38
+ path: string;
39
+ /** What is wrong, with the offending values quoted. */
40
+ message: string;
41
+ /** NAV's own description of the fault, in the requested language. */
42
+ navMessage?: string;
43
+ }
44
+ export interface ValidationReport {
45
+ /** True when there are no errors. Warnings do not make a report invalid. */
46
+ valid: boolean;
47
+ errors: InvoiceValidationIssue[];
48
+ warnings: InvoiceValidationIssue[];
49
+ /** Everything, in the order found. */
50
+ issues: InvoiceValidationIssue[];
51
+ }
52
+ export type MessageLanguage = 'en' | 'hu' | 'de';
53
+ /** Collects findings while rules run, then assembles the report. */
54
+ export declare class IssueCollector {
55
+ private readonly language;
56
+ private readonly collected;
57
+ constructor(language?: MessageLanguage);
58
+ add(code: FaultCode, severity: Severity, path: string, message: string): void;
59
+ error(code: FaultCode, path: string, message: string): void;
60
+ warn(code: FaultCode, path: string, message: string): void;
61
+ report(): ValidationReport;
62
+ }
63
+ /** Whether a code is one NAV defines, as opposed to one of ours. */
64
+ export declare function isNavCode(code: FaultCode): code is NavFaultCode;
65
+ /** NAV's description of a fault code, for surfacing in your own UI. */
66
+ export declare function faultMessage(code: FaultCode, language?: MessageLanguage): string;
67
+ //# sourceMappingURL=issue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../../src/validation/issue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE;;;;;;;;GAQG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,MAAM,cAAc;AACxB,kFAAkF;AAChF,+BAA+B;AACjC,2DAA2D;GACzD,2BAA2B;AAC7B,6DAA6D;GAC3D,8BAA8B,CAAC;AAEnC,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,cAAc,CAAC;AAEtD,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,IAAI,EAAE,SAAS,CAAC;IAChB,6DAA6D;IAC7D,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC;IACxB,QAAQ,EAAE,QAAQ,CAAC;IACnB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,sCAAsC;IACtC,MAAM,EAAE,sBAAsB,EAAE,CAAC;CAClC;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,oEAAoE;AACpE,qBAAa,cAAc;IAGb,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;gBAE7B,QAAQ,GAAE,eAAsB;IAE7D,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAY7E,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3D,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI1D,MAAM,IAAI,gBAAgB;CAK3B;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,IAAI,YAAY,CAE/D;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,GAAE,eAAsB,GAAG,MAAM,CAEtF"}
@@ -0,0 +1,40 @@
1
+ import { NAV_FAULT_MESSAGES } from '../generated/fault-codes.js';
2
+ /** Collects findings while rules run, then assembles the report. */
3
+ export class IssueCollector {
4
+ language;
5
+ collected = [];
6
+ constructor(language = 'en') {
7
+ this.language = language;
8
+ }
9
+ add(code, severity, path, message) {
10
+ const navMessage = isNavCode(code) ? NAV_FAULT_MESSAGES[code][this.language] : undefined;
11
+ this.collected.push({
12
+ code,
13
+ origin: isNavCode(code) ? 'nav' : 'local',
14
+ severity,
15
+ path,
16
+ message,
17
+ ...(navMessage ? { navMessage } : {}),
18
+ });
19
+ }
20
+ error(code, path, message) {
21
+ this.add(code, 'error', path, message);
22
+ }
23
+ warn(code, path, message) {
24
+ this.add(code, 'warning', path, message);
25
+ }
26
+ report() {
27
+ const errors = this.collected.filter((issue) => issue.severity === 'error');
28
+ const warnings = this.collected.filter((issue) => issue.severity === 'warning');
29
+ return { valid: errors.length === 0, errors, warnings, issues: [...this.collected] };
30
+ }
31
+ }
32
+ /** Whether a code is one NAV defines, as opposed to one of ours. */
33
+ export function isNavCode(code) {
34
+ return code in NAV_FAULT_MESSAGES;
35
+ }
36
+ /** NAV's description of a fault code, for surfacing in your own UI. */
37
+ export function faultMessage(code, language = 'en') {
38
+ return isNavCode(code) ? (NAV_FAULT_MESSAGES[code][language] ?? '') : '';
39
+ }
40
+ //# sourceMappingURL=issue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue.js","sourceRoot":"","sources":["../../src/validation/issue.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AA2DjE,oEAAoE;AACpE,MAAM,OAAO,cAAc;IAGI;IAFZ,SAAS,GAA6B,EAAE,CAAC;IAE1D,YAA6B,WAA4B,IAAI;QAAhC,aAAQ,GAAR,QAAQ,CAAwB;IAAG,CAAC;IAEjE,GAAG,CAAC,IAAe,EAAE,QAAkB,EAAE,IAAY,EAAE,OAAe;QACpE,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzF,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAClB,IAAI;YACJ,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO;YACzC,QAAQ;YACR,IAAI;YACJ,OAAO;YACP,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAe,EAAE,IAAY,EAAE,OAAe;QAClD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,CAAC,IAAe,EAAE,IAAY,EAAE,OAAe;QACjD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;QAC5E,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC;QAChF,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;IACvF,CAAC;CACF;AAED,oEAAoE;AACpE,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,OAAO,IAAI,IAAI,kBAAkB,CAAC;AACpC,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,IAAe,EAAE,WAA4B,IAAI;IAC5E,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { InvoiceData } from '../generated/types.js';
2
+ import type { IssueCollector } from './issue.js';
3
+ /**
4
+ * Business rules that are decidable from the document alone.
5
+ *
6
+ * Each rule reports NAV's own fault code where one exists, so a local failure
7
+ * names exactly what the service would have said. Rules that need state only
8
+ * NAV holds — whether an invoice number was used before, whether a referenced
9
+ * invoice exists, whether a tax number is registered — cannot be checked here
10
+ * and are listed in `LOCALLY_UNDECIDABLE` for the record.
11
+ */
12
+ export interface InvoiceValidationContext {
13
+ /**
14
+ * The operation the document will be submitted under.
15
+ *
16
+ * Several rules depend on it: an original invoice must not reference
17
+ * another document, and a modification must.
18
+ */
19
+ operation?: 'CREATE' | 'MODIFY' | 'STORNO';
20
+ /**
21
+ * The tax number the request authenticates as. NAV rejects a report whose
22
+ * supplier is someone else.
23
+ */
24
+ supplierTaxNumber?: string;
25
+ /** Today's date as `yyyy-mm-dd`, for the date rules. Defaults to the system date. */
26
+ today?: string;
27
+ }
28
+ /**
29
+ * Faults NAV reports that no local validator can decide, because they depend
30
+ * on NAV's own records. Documented so the coverage of this module is honest.
31
+ */
32
+ export declare const LOCALLY_UNDECIDABLE: readonly ["INVOICE_NUMBER_NOT_UNIQUE", "INVALID_INVOICE_REFERENCE", "INVALID_ANNULMENT_REFERENCE", "MULTIPLE_INVOICES_FOUND", "MODIFICATION_INDEX_NOT_UNIQUE", "MODIFY_WITHOUT_MASTER_MISMATCH", "INVOICE_TYPE_MISMATCH", "INVOICE_LINE_ALREADY_EXISTS", "ANNULMENT_IN_PROGRESS", "CUSTOMER_NOT_ASSIGNED", "INCORRECT_HEAD_DATA_CUSTOMER_TAX_NUMBER"];
33
+ export declare function collectBusinessIssues(document: InvoiceData, collector: IssueCollector, context?: InvoiceValidationContext): void;
34
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/validation/rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAsC,MAAM,uBAAuB,CAAC;AAI7F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD;;;;;;;;GAQG;AAEH,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC3C;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAKD;;;GAGG;AACH,eAAO,MAAM,mBAAmB,uVAYtB,CAAC;AAEX,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,WAAW,EACrB,SAAS,EAAE,cAAc,EACzB,OAAO,GAAE,wBAA6B,GACrC,IAAI,CA+BN"}