@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,290 @@
1
+ import { Decimal } from '../money/decimal.js';
2
+ import { checkInvoiceSummary, hasAmountBearingLines } from '../money/summary.js';
3
+ import { isValidCountyCode, isValidTaxpayerId } from './tax-number.js';
4
+ /** Current Hungarian VAT rates, for a plausibility warning only. */
5
+ const KNOWN_VAT_RATES = ['0', '0.05', '0.18', '0.27'];
6
+ /**
7
+ * Faults NAV reports that no local validator can decide, because they depend
8
+ * on NAV's own records. Documented so the coverage of this module is honest.
9
+ */
10
+ export const LOCALLY_UNDECIDABLE = [
11
+ 'INVOICE_NUMBER_NOT_UNIQUE',
12
+ 'INVALID_INVOICE_REFERENCE',
13
+ 'INVALID_ANNULMENT_REFERENCE',
14
+ 'MULTIPLE_INVOICES_FOUND',
15
+ 'MODIFICATION_INDEX_NOT_UNIQUE',
16
+ 'MODIFY_WITHOUT_MASTER_MISMATCH',
17
+ 'INVOICE_TYPE_MISMATCH',
18
+ 'INVOICE_LINE_ALREADY_EXISTS',
19
+ 'ANNULMENT_IN_PROGRESS',
20
+ 'CUSTOMER_NOT_ASSIGNED',
21
+ 'INCORRECT_HEAD_DATA_CUSTOMER_TAX_NUMBER',
22
+ ];
23
+ export function collectBusinessIssues(document, collector, context = {}) {
24
+ checkDocument(document, collector, context);
25
+ const single = document.invoiceMain.invoice;
26
+ if (single) {
27
+ checkInvoice(single, '', collector, context);
28
+ return;
29
+ }
30
+ const batch = document.invoiceMain.batchInvoice ?? [];
31
+ // A batch modification document exists to amend several invoices at once;
32
+ // one entry means it should have been a plain modification.
33
+ if (batch.length === 1) {
34
+ collector.error('BATCH_INVOICE_CARDINALITY_ERROR', 'invoiceMain.batchInvoice', 'a batch document must amend at least two invoices, found 1');
35
+ }
36
+ let previous = 0;
37
+ for (const [position, entry] of batch.entries()) {
38
+ if (entry.batchIndex <= previous) {
39
+ collector.error('BATCH_INDEX_NOT_SEQUENTIAL', `invoiceMain.batchInvoice.${position}.batchIndex`, `must be strictly ascending, ${entry.batchIndex} follows ${previous}`);
40
+ }
41
+ previous = entry.batchIndex;
42
+ checkInvoice(entry.invoice, `invoiceMain.batchInvoice.${position}.`, collector, context);
43
+ }
44
+ }
45
+ function checkDocument(document, collector, context) {
46
+ const today = context.today ?? new Date().toISOString().slice(0, 10);
47
+ if (document.invoiceIssueDate > today) {
48
+ collector.error('INCORRECT_DATE_INVOICE_ISSUE_DATE_LATE', 'invoiceIssueDate', `is in the future: ${document.invoiceIssueDate} (today is ${today})`);
49
+ }
50
+ // completenessIndicator asserts that the report *is* the invoice, which only
51
+ // makes sense for an invoice that exists in electronic form.
52
+ if (document.completenessIndicator) {
53
+ const appearance = document.invoiceMain.invoice?.invoiceHead.invoiceDetail.invoiceAppearance;
54
+ if (appearance !== undefined && appearance !== 'ELECTRONIC') {
55
+ collector.error('INVOICE_COMPLETENESS_NOT_ALLOWED', 'completenessIndicator', `is set, which requires invoiceAppearance ELECTRONIC, but it is ${appearance}`);
56
+ }
57
+ if (document.invoiceMain.batchInvoice !== undefined) {
58
+ collector.error('INVOICE_COMPLETENESS_NOT_ALLOWED', 'completenessIndicator', 'cannot be set on a batch document');
59
+ }
60
+ }
61
+ }
62
+ function checkInvoice(invoice, prefix, collector, context) {
63
+ const at = (path) => `${prefix}${path}`;
64
+ const head = invoice.invoiceHead;
65
+ const detail = head.invoiceDetail;
66
+ const lines = invoice.invoiceLines?.line ?? [];
67
+ const isModification = context.operation === 'MODIFY' || context.operation === 'STORNO';
68
+ // --- references -------------------------------------------------------
69
+ if (isModification && invoice.invoiceReference === undefined) {
70
+ collector.error('INVOICE_REFERENCE_EXPECTED', at('invoiceReference'), `is required for a ${context.operation} operation`);
71
+ }
72
+ if (context.operation === 'CREATE' && invoice.invoiceReference !== undefined) {
73
+ collector.error('INVOICE_REFERENCE_NOT_EXPECTED', at('invoiceReference'), 'an original invoice must not reference another document');
74
+ }
75
+ // --- parties ----------------------------------------------------------
76
+ const supplierId = head.supplierInfo.supplierTaxNumber.taxpayerId;
77
+ if (context.supplierTaxNumber !== undefined && supplierId !== context.supplierTaxNumber) {
78
+ collector.error('SUPPLIER_TAX_NUMBER_MISMATCH', at('invoiceHead.supplierInfo.supplierTaxNumber.taxpayerId'), `is ${supplierId} but the request authenticates as ${context.supplierTaxNumber}`);
79
+ }
80
+ checkTaxNumber(supplierId, head.supplierInfo.supplierTaxNumber.countyCode, at('invoiceHead.supplierInfo.supplierTaxNumber'), collector);
81
+ const customer = head.customerInfo;
82
+ const customerId = customer?.customerVatData?.customerTaxNumber?.taxpayerId;
83
+ if (customer) {
84
+ checkCustomer(customer, at('invoiceHead.customerInfo'), collector);
85
+ }
86
+ if (customerId !== undefined && customerId === supplierId) {
87
+ collector.error('SUPPLIER_CUSTOMER_MATCH_TAXPAYER', at('invoiceHead.customerInfo'), `the customer's tax number ${customerId} is the supplier's own`);
88
+ }
89
+ // --- dates ------------------------------------------------------------
90
+ if (detail.invoiceDeliveryPeriodStart !== undefined &&
91
+ detail.invoiceDeliveryPeriodEnd !== undefined &&
92
+ detail.invoiceDeliveryPeriodStart > detail.invoiceDeliveryPeriodEnd) {
93
+ collector.error('INCORRECT_DATE_INVOICE_DELIVERY_TO_FROM', at('invoiceHead.invoiceDetail.invoiceDeliveryPeriodStart'), `${detail.invoiceDeliveryPeriodStart} is after the period end ${detail.invoiceDeliveryPeriodEnd}`);
94
+ }
95
+ // --- lines ------------------------------------------------------------
96
+ if (lines.length === 0 && context.operation === 'CREATE') {
97
+ collector.error('INVOICE_LINE_MISSING', at('invoiceLines'), 'an invoice must have lines');
98
+ }
99
+ let previousLineNumber = 0;
100
+ for (const [position, line] of lines.entries()) {
101
+ const linePath = at(`invoiceLines.line.${position}`);
102
+ if (line.lineNumber <= previousLineNumber) {
103
+ collector.error('LINE_NUMBER_NOT_SEQUENTIAL', `${linePath}.lineNumber`, `must be strictly ascending, ${line.lineNumber} follows ${previousLineNumber}`);
104
+ }
105
+ previousLineNumber = line.lineNumber;
106
+ checkLine(line, linePath, detail.exchangeRate, collector, context);
107
+ }
108
+ // --- line and summary must describe the same kind of invoice ----------
109
+ checkLineSummaryConsistency(lines, invoice.invoiceSummary, at(''), collector);
110
+ // --- arithmetic -------------------------------------------------------
111
+ if (hasAmountBearingLines(invoice)) {
112
+ for (const issue of checkInvoiceSummary(invoice)) {
113
+ collector.error(summaryFaultCode(issue.path), at(issue.path), issue.message);
114
+ }
115
+ }
116
+ }
117
+ /**
118
+ * The NAV fault code for a summary discrepancy.
119
+ *
120
+ * NAV distinguishes these carefully and the suffixes carry the meaning: a
121
+ * `_LINE` code means the lines disagree with the summary, a `_SUMMARY` code
122
+ * means the per-rate figures disagree with the invoice total, and `_HUF`
123
+ * marks the forint counterpart. Picking the wrong one would attach NAV
124
+ * wording that describes a different fault, which is worse than attaching
125
+ * none, so the mapping is explicit rather than inferred.
126
+ */
127
+ function summaryFaultCode(path) {
128
+ const huf = path.endsWith('HUF');
129
+ const perRate = path.includes('summaryByVatRate');
130
+ const field = path.split('.').pop() ?? '';
131
+ if (perRate) {
132
+ // A per-rate figure is compared against the lines carrying that rate.
133
+ if (field.startsWith('vatRateNet')) {
134
+ return huf
135
+ ? 'INCORRECT_SUMMARY_CALCULATION_VAT_RATE_NET_AMOUNT_HUF_LINE'
136
+ : 'INCORRECT_SUMMARY_CALCULATION_VAT_RATE_NET_AMOUNT_LINE';
137
+ }
138
+ if (field.startsWith('vatRateGross')) {
139
+ return 'INCORRECT_SUMMARY_CALCULATION_VAT_RATE_GROSS_AMOUNT_LINE';
140
+ }
141
+ return 'INCORRECT_SUMMARY_CALCULATION_VAT_RATE_VAT_AMOUNT_SUMMARY';
142
+ }
143
+ // An invoice total is compared against the sum of the per-rate figures.
144
+ if (field.startsWith('invoiceNet')) {
145
+ return huf
146
+ ? 'INCORRECT_SUMMARY_CALCULATION_INVOICE_NET_AMOUNT_HUF'
147
+ : 'INCORRECT_SUMMARY_CALCULATION_VAT_RATE_NET_AMOUNT_SUMMARY';
148
+ }
149
+ if (field.startsWith('invoiceVat')) {
150
+ return huf
151
+ ? 'INCORRECT_SUMMARY_CALCULATION_INVOICE_VAT_AMOUNT_HUF_SUMMARY'
152
+ : 'INCORRECT_SUMMARY_CALCULATION_INVOICE_VAT_AMOUNT_SUMMARY';
153
+ }
154
+ return huf
155
+ ? 'INCORRECT_SUMMARY_CALCULATION_INVOICE_GROSS_AMOUNT_HUF_SUMMARY'
156
+ : 'INCORRECT_SUMMARY_CALCULATION_INVOICE_GROSS_AMOUNT_SUMMARY';
157
+ }
158
+ function checkCustomer(customer, path, collector) {
159
+ const status = customer.customerVatStatus;
160
+ const vatData = customer.customerVatData;
161
+ if (status === 'DOMESTIC') {
162
+ if (vatData?.customerTaxNumber === undefined) {
163
+ collector.error('MISSING_CUSTOMER_DOMESTIC_TAXNUMBER', `${path}.customerVatData.customerTaxNumber`, 'a domestic customer must be identified by a Hungarian tax number');
164
+ }
165
+ if (vatData?.communityVatNumber !== undefined) {
166
+ collector.error('CUSTOMER_COMMUNITY_TAXNUMBER_NOT_EXPECTED', `${path}.customerVatData.communityVatNumber`, 'must not be supplied for a domestic customer');
167
+ }
168
+ if (vatData?.thirdStateTaxId !== undefined) {
169
+ collector.error('CUSTOMER_THIRD_STATE_TAXNUMBER_NOT_EXPECTED', `${path}.customerVatData.thirdStateTaxId`, 'must not be supplied for a domestic customer');
170
+ }
171
+ }
172
+ if (status === 'PRIVATE_PERSON' && vatData !== undefined) {
173
+ // NAV's own private-person sample carries nothing but the status: no VAT
174
+ // data, no name, no address.
175
+ collector.error('CUSTOMER_DATA_NOT_EXPECTED', `${path}.customerVatData`, 'must not be supplied for a private person');
176
+ }
177
+ const taxNumber = vatData?.customerTaxNumber;
178
+ if (taxNumber) {
179
+ checkTaxNumber(taxNumber.taxpayerId, taxNumber.countyCode, `${path}.customerVatData.customerTaxNumber`, collector);
180
+ }
181
+ }
182
+ function checkTaxNumber(taxpayerId, countyCode, path, collector) {
183
+ // A warning, not an error: NAV validates a tax number against its taxpayer
184
+ // registry rather than arithmetically, and two of the tax numbers in its own
185
+ // samples fail this check. Use queryTaxpayer for an authoritative answer.
186
+ if (!isValidTaxpayerId(taxpayerId)) {
187
+ collector.warn('LOCAL_TAXPAYER_ID_CHECK_DIGIT', `${path}.taxpayerId`, `${taxpayerId} fails the check digit; verify it with queryTaxpayer`);
188
+ }
189
+ if (countyCode !== undefined && !isValidCountyCode(countyCode)) {
190
+ collector.warn('LOCAL_COUNTY_CODE_UNKNOWN', `${path}.countyCode`, `${countyCode} is not a county code NAV is known to issue`);
191
+ }
192
+ }
193
+ function checkLine(line, path, exchangeRate, collector, context) {
194
+ const isModification = context.operation === 'MODIFY' || context.operation === 'STORNO';
195
+ if (context.operation === 'CREATE' && line.lineModificationReference !== undefined) {
196
+ collector.error('LINE_MODIFICATION_NOT_EXPECTED', `${path}.lineModificationReference`, 'an original invoice line must not reference a modification');
197
+ }
198
+ if (isModification && line.lineModificationReference === undefined) {
199
+ collector.error('LINE_MODIFICATION_EXPECTED', `${path}.lineModificationReference`, `is required on every line of a ${context.operation} document`);
200
+ }
201
+ if (line.referencesToOtherLines?.referenceToOtherLine.includes(line.lineNumber)) {
202
+ collector.error('INCORRECT_LINE_DATA_SELF_LINE_NUMBER', `${path}.referencesToOtherLines`, `line ${line.lineNumber} references itself`);
203
+ }
204
+ if (line.lineExpressionIndicator && line.quantity === undefined) {
205
+ collector.error('MISSING_LINE_DATA_QUANTITY', `${path}.quantity`, 'is required when lineExpressionIndicator is set');
206
+ }
207
+ if (line.unitOfMeasure === 'OWN' && line.unitOfMeasureOwn === undefined) {
208
+ collector.error('INCORRECT_LINE_DATA_UOM_INCOMPLETE', `${path}.unitOfMeasureOwn`, 'is required when unitOfMeasure is OWN');
209
+ }
210
+ if (line.unitOfMeasure !== 'OWN' && line.unitOfMeasureOwn !== undefined) {
211
+ collector.error('INCORRECT_LINE_DATA_UOM', `${path}.unitOfMeasureOwn`, 'may only be supplied when unitOfMeasure is OWN');
212
+ }
213
+ for (const [index, productCode] of (line.productCodes?.productCode ?? []).entries()) {
214
+ const codePath = `${path}.productCodes.productCode.${index}`;
215
+ // NAV's rule runs one way only: productCodeOwnValue belongs to an OWN
216
+ // category. An OWN category may still carry an ordinary productCodeValue,
217
+ // as NAV's own simplified-invoice sample does — requiring the own-value
218
+ // field for every OWN category would reject eight of its thirty samples.
219
+ if (productCode.productCodeOwnValue !== undefined &&
220
+ productCode.productCodeCategory !== 'OWN') {
221
+ collector.error('INCORRECT_PRODUCT_CODE_VALUE_OWN', `${codePath}.productCodeOwnValue`, `may only be supplied when productCodeCategory is OWN, but it is ${productCode.productCodeCategory}`);
222
+ }
223
+ }
224
+ const normal = line.lineAmountsNormal;
225
+ const simplified = line.lineAmountsSimplified;
226
+ if (normal) {
227
+ if (normal.lineVatRate.vatContent !== undefined) {
228
+ collector.error('INVALID_LINE_VAT_RATE_NORMAL', `${path}.lineAmountsNormal.lineVatRate.vatContent`, 'VAT content may only be used on a simplified invoice line');
229
+ }
230
+ checkNormalLineAmounts(line, normal, path, exchangeRate, collector);
231
+ }
232
+ if (simplified && simplified.lineVatRate.vatPercentage !== undefined) {
233
+ collector.error('INVALID_LINE_VAT_RATE_SIMPLIFIED', `${path}.lineAmountsSimplified.lineVatRate.vatPercentage`, 'a simplified invoice line states VAT content, not a VAT percentage');
234
+ }
235
+ const percentage = normal?.lineVatRate.vatPercentage;
236
+ if (percentage !== undefined &&
237
+ !KNOWN_VAT_RATES.some((rate) => Decimal.from(rate).equals(percentage))) {
238
+ collector.warn('LOCAL_VAT_PERCENTAGE_UNUSUAL', `${path}.lineAmountsNormal.lineVatRate.vatPercentage`, `${percentage} is not a current Hungarian VAT rate (${KNOWN_VAT_RATES.join(', ')})`);
239
+ }
240
+ }
241
+ function checkNormalLineAmounts(line, normal, path, exchangeRate, collector) {
242
+ const net = Decimal.from(normal.lineNetAmountData.lineNetAmount);
243
+ const vat = normal.lineVatData ? Decimal.from(normal.lineVatData.lineVatAmount) : undefined;
244
+ // net = quantity x unit price, unless a line discount was applied.
245
+ if (line.lineExpressionIndicator &&
246
+ line.quantity !== undefined &&
247
+ line.unitPrice !== undefined &&
248
+ line.lineDiscountData === undefined) {
249
+ const expected = Decimal.from(line.quantity).multiply(line.unitPrice).round(net.scale);
250
+ if (!expected.equals(net)) {
251
+ collector.error('INCORRECT_LINE_CALCULATION_NET_AMOUNT', `${path}.lineAmountsNormal.lineNetAmountData.lineNetAmount`, `is ${net.toString()} but quantity ${line.quantity} times unit price ${line.unitPrice} is ${expected.toString()}`);
252
+ }
253
+ }
254
+ // gross = net + VAT
255
+ const gross = normal.lineGrossAmountData?.lineGrossAmountNormal;
256
+ if (gross !== undefined && vat !== undefined) {
257
+ const expected = net.add(vat);
258
+ if (!expected.equals(gross)) {
259
+ collector.error('INCORRECT_LINE_CALCULATION_GROSS_AMOUNT', `${path}.lineAmountsNormal.lineGrossAmountData.lineGrossAmountNormal`, `is ${gross} but net ${net.toString()} plus VAT ${vat.toString()} is ${expected.toString()}`);
260
+ }
261
+ }
262
+ // Forint equivalents follow from the exchange rate.
263
+ if (exchangeRate !== undefined) {
264
+ const rate = Decimal.from(exchangeRate);
265
+ const netHuf = Decimal.from(normal.lineNetAmountData.lineNetAmountHUF);
266
+ const expectedNetHuf = net.multiply(rate).round(netHuf.scale);
267
+ if (!expectedNetHuf.equals(netHuf)) {
268
+ collector.error('INCORRECT_LINE_CALCULATION_LINE_NET_AMOUNT_HUF', `${path}.lineAmountsNormal.lineNetAmountData.lineNetAmountHUF`, `is ${netHuf.toString()} but ${net.toString()} at ${exchangeRate} is ${expectedNetHuf.toString()}`);
269
+ }
270
+ }
271
+ }
272
+ /**
273
+ * A normal invoice needs normal line amounts and a normal summary; a
274
+ * simplified one needs the simplified forms. Mixing them is the mistake NAV
275
+ * reports as a LINE_SUMMARY_TYPE_MISMATCH.
276
+ */
277
+ function checkLineSummaryConsistency(lines, summary, prefix, collector) {
278
+ const hasNormalLines = lines.some((line) => line.lineAmountsNormal !== undefined);
279
+ const hasSimplifiedLines = lines.some((line) => line.lineAmountsSimplified !== undefined);
280
+ if (hasSimplifiedLines && summary.summaryNormal !== undefined) {
281
+ collector.error('LINE_SUMMARY_TYPE_MISMATCH_SUMMARY_NORMAL', `${prefix}invoiceSummary.summaryNormal`, 'the invoice has simplified lines, so the summary must be simplified too');
282
+ }
283
+ if (hasNormalLines && summary.summarySimplified !== undefined) {
284
+ collector.error('LINE_SUMMARY_TYPE_MISMATCH_SUMMARY_SIMPLIFIED', `${prefix}invoiceSummary.summarySimplified`, 'the invoice has normal lines, so the summary must be normal too');
285
+ }
286
+ if (hasNormalLines && hasSimplifiedLines) {
287
+ collector.error('LINE_SUMMARY_TYPE_MISMATCH_LINE_NORMAL', `${prefix}invoiceLines`, 'an invoice cannot mix normal and simplified line amounts');
288
+ }
289
+ }
290
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/validation/rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { IssueCollector } from './issue.js';
2
+ /**
3
+ * Validate a document against the schema, using the generated descriptors.
4
+ *
5
+ * This covers what an XSD validator would: mandatory elements, value
6
+ * constraints, cardinality and choice exclusivity. Doing it from the same
7
+ * descriptor table that drives serialisation means the two cannot disagree,
8
+ * and it needs no native XML library.
9
+ *
10
+ * NAV reports all of these as `SCHEMA_VIOLATION`, except a missing mandatory
11
+ * element which it reports as `MANDATORY_CONTENT_MISSING`.
12
+ */
13
+ export declare function collectSchemaIssues(rootName: string, value: unknown, collector: IssueCollector): void;
14
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/validation/schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,cAAc,GACxB,IAAI,CAON"}
@@ -0,0 +1,231 @@
1
+ import { ROOTS, TYPES } from '../generated/schema.js';
2
+ import { Decimal } from '../money/decimal.js';
3
+ import { XS_PRIMITIVES } from '../xml/descriptor.js';
4
+ import { IssueCollector } from './issue.js';
5
+ /**
6
+ * Validate a document against the schema, using the generated descriptors.
7
+ *
8
+ * This covers what an XSD validator would: mandatory elements, value
9
+ * constraints, cardinality and choice exclusivity. Doing it from the same
10
+ * descriptor table that drives serialisation means the two cannot disagree,
11
+ * and it needs no native XML library.
12
+ *
13
+ * NAV reports all of these as `SCHEMA_VIOLATION`, except a missing mandatory
14
+ * element which it reports as `MANDATORY_CONTENT_MISSING`.
15
+ */
16
+ export function collectSchemaIssues(rootName, value, collector) {
17
+ const root = ROOTS[rootName];
18
+ if (!root) {
19
+ collector.error('SCHEMA_VIOLATION', '', `unknown document root ${rootName}`);
20
+ return;
21
+ }
22
+ walk(root.type, value, '', collector);
23
+ }
24
+ function walk(typeKey, value, path, collector) {
25
+ const primitive = XS_PRIMITIVES[typeKey];
26
+ if (primitive) {
27
+ checkPrimitive(primitive, undefined, value, path, collector);
28
+ return;
29
+ }
30
+ const descriptor = TYPES[typeKey];
31
+ if (!descriptor) {
32
+ collector.error('SCHEMA_VIOLATION', path, `unknown type ${typeKey}`);
33
+ return;
34
+ }
35
+ if (descriptor.kind === 'simple') {
36
+ checkPrimitive(descriptor.primitive, descriptor, value, path, collector);
37
+ return;
38
+ }
39
+ if (descriptor.content === 'simple') {
40
+ if (!isRecord(value)) {
41
+ collector.error('SCHEMA_VIOLATION', path, `expected an object, got ${describe(value)}`);
42
+ return;
43
+ }
44
+ const contentType = descriptor.contentType;
45
+ if (contentType)
46
+ walk(contentType, value['value'], `${path}.value`, collector);
47
+ for (const attribute of descriptor.attributes ?? []) {
48
+ const attributeValue = value[attribute.name];
49
+ if (attributeValue === undefined || attributeValue === null) {
50
+ if (attribute.required) {
51
+ collector.error('MANDATORY_CONTENT_MISSING', `${path}@${attribute.name}`, 'attribute is required');
52
+ }
53
+ continue;
54
+ }
55
+ walk(attribute.type, attributeValue, `${path}@${attribute.name}`, collector);
56
+ }
57
+ return;
58
+ }
59
+ if (!isRecord(value)) {
60
+ collector.error('SCHEMA_VIOLATION', path, `expected an object, got ${describe(value)}`);
61
+ return;
62
+ }
63
+ for (const field of descriptor.fields) {
64
+ const fieldPath = path ? `${path}.${field.name}` : field.name;
65
+ const fieldValue = value[field.name];
66
+ if (fieldValue === undefined || fieldValue === null) {
67
+ if (!field.optional) {
68
+ collector.error('MANDATORY_CONTENT_MISSING', fieldPath, 'is required');
69
+ }
70
+ continue;
71
+ }
72
+ if (field.repeated) {
73
+ if (!Array.isArray(fieldValue)) {
74
+ collector.error('SCHEMA_VIOLATION', fieldPath, 'is repeatable and expects an array');
75
+ continue;
76
+ }
77
+ if (fieldValue.length === 0) {
78
+ if (!field.optional) {
79
+ collector.error('MANDATORY_CONTENT_MISSING', fieldPath, 'must have at least one entry');
80
+ }
81
+ continue;
82
+ }
83
+ if (field.maxOccurs !== undefined && fieldValue.length > field.maxOccurs) {
84
+ collector.error('SCHEMA_VIOLATION', fieldPath, `allows at most ${field.maxOccurs} entries, got ${fieldValue.length}`);
85
+ }
86
+ for (const [index, item] of fieldValue.entries()) {
87
+ walk(field.type, item, `${fieldPath}.${index}`, collector);
88
+ }
89
+ continue;
90
+ }
91
+ if (Array.isArray(fieldValue)) {
92
+ collector.error('SCHEMA_VIOLATION', fieldPath, 'occurs at most once');
93
+ continue;
94
+ }
95
+ walk(field.type, fieldValue, fieldPath, collector);
96
+ }
97
+ for (const key of Object.keys(value)) {
98
+ if (!descriptor.fields.some((field) => field.name === key)) {
99
+ const isAttribute = (descriptor.attributes ?? []).some((attribute) => attribute.name === key);
100
+ if (!isAttribute) {
101
+ collector.error('SCHEMA_VIOLATION', path ? `${path}.${key}` : key, `is not part of ${descriptor.name}`);
102
+ }
103
+ }
104
+ }
105
+ // Choice groups: NAV nests these inside sequences, so they are checked
106
+ // against the flattened field list rather than by structural position.
107
+ for (const group of descriptor.choiceGroups ?? []) {
108
+ const present = group.members.filter((member) => value[member] !== undefined && value[member] !== null);
109
+ if (present.length > 1) {
110
+ collector.error('SCHEMA_VIOLATION', path, `only one of ${group.members.join(', ')} may be set, found ${present.join(' and ')}`);
111
+ }
112
+ else if (present.length === 0 && group.required) {
113
+ collector.error('MANDATORY_CONTENT_MISSING', path, `one of ${group.members.join(', ')} is required`);
114
+ }
115
+ }
116
+ }
117
+ function checkPrimitive(kind, descriptor, value, path, collector) {
118
+ switch (kind) {
119
+ case 'boolean':
120
+ if (typeof value !== 'boolean') {
121
+ collector.error('SCHEMA_VIOLATION', path, `expected a boolean, got ${describe(value)}`);
122
+ }
123
+ return;
124
+ case 'integer': {
125
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
126
+ collector.error('SCHEMA_VIOLATION', path, `expected an integer, got ${describe(value)}`);
127
+ return;
128
+ }
129
+ // Numeric comparison, not lexicographic: '2' sorts after '100' as text.
130
+ checkBounds(String(value), descriptor, path, collector, true);
131
+ if (descriptor?.totalDigits !== undefined) {
132
+ const digits = Math.abs(value).toString().length;
133
+ if (digits > descriptor.totalDigits) {
134
+ collector.error('SCHEMA_VIOLATION', path, `has ${digits} digits, at most ${descriptor.totalDigits} allowed`);
135
+ }
136
+ }
137
+ return;
138
+ }
139
+ case 'decimal': {
140
+ if (typeof value !== 'string' && typeof value !== 'number') {
141
+ collector.error('SCHEMA_VIOLATION', path, `expected a decimal, got ${describe(value)}`);
142
+ return;
143
+ }
144
+ let amount;
145
+ try {
146
+ amount = Decimal.from(value);
147
+ }
148
+ catch {
149
+ collector.error('SCHEMA_VIOLATION', path, `${JSON.stringify(value)} is not a decimal`);
150
+ return;
151
+ }
152
+ if (descriptor?.fractionDigits !== undefined && amount.scale > descriptor.fractionDigits) {
153
+ collector.error('SCHEMA_VIOLATION', path, `has ${amount.scale} decimal places, at most ${descriptor.fractionDigits} allowed`);
154
+ }
155
+ if (descriptor?.totalDigits !== undefined && amount.totalDigits() > descriptor.totalDigits) {
156
+ collector.error('SCHEMA_VIOLATION', path, `has ${amount.totalDigits()} significant digits, at most ${descriptor.totalDigits} allowed`);
157
+ }
158
+ checkBounds(amount.toString(), descriptor, path, collector, true);
159
+ return;
160
+ }
161
+ default: {
162
+ if (typeof value !== 'string') {
163
+ collector.error('SCHEMA_VIOLATION', path, `expected a string, got ${describe(value)}`);
164
+ return;
165
+ }
166
+ if (!descriptor)
167
+ return;
168
+ if (descriptor.enumValues && !descriptor.enumValues.includes(value)) {
169
+ collector.error('SCHEMA_VIOLATION', path, `${JSON.stringify(value)} is not one of ${descriptor.enumValues.join(', ')}`);
170
+ }
171
+ if (descriptor.length !== undefined && value.length !== descriptor.length) {
172
+ collector.error('SCHEMA_VIOLATION', path, `must be exactly ${descriptor.length} characters, got ${value.length}`);
173
+ }
174
+ if (descriptor.minLength !== undefined && value.length < descriptor.minLength) {
175
+ collector.error('SCHEMA_VIOLATION', path, `must be at least ${descriptor.minLength} characters, got ${value.length}`);
176
+ }
177
+ if (descriptor.maxLength !== undefined && value.length > descriptor.maxLength) {
178
+ collector.error('SCHEMA_VIOLATION', path, `must be at most ${descriptor.maxLength} characters, got ${value.length}`);
179
+ }
180
+ if (descriptor.pattern !== undefined && !anchored(descriptor.pattern).test(value)) {
181
+ collector.error('SCHEMA_VIOLATION', path, `${JSON.stringify(value)} does not match ${descriptor.pattern}`);
182
+ }
183
+ // Dates and timestamps carry their bounds as text, and NAV's are all
184
+ // lexicographically comparable ISO forms.
185
+ checkBounds(value, descriptor, path, collector);
186
+ return;
187
+ }
188
+ }
189
+ }
190
+ function checkBounds(value, descriptor, path, collector, numeric = false) {
191
+ if (!descriptor)
192
+ return;
193
+ const compare = numeric
194
+ ? (left, right) => Decimal.from(left).compare(right)
195
+ : (left, right) => (left < right ? -1 : left > right ? 1 : 0);
196
+ const checks = [
197
+ ['minInclusive', (result) => result < 0, 'must not be less than'],
198
+ ['maxInclusive', (result) => result > 0, 'must not be greater than'],
199
+ ['minExclusive', (result) => result <= 0, 'must be greater than'],
200
+ ['maxExclusive', (result) => result >= 0, 'must be less than'],
201
+ ];
202
+ for (const [facet, violates, wording] of checks) {
203
+ const bound = descriptor[facet];
204
+ if (typeof bound !== 'string')
205
+ continue;
206
+ if (violates(compare(value, bound))) {
207
+ collector.error('SCHEMA_VIOLATION', path, `${wording} ${bound}, got ${value}`);
208
+ }
209
+ }
210
+ }
211
+ const anchoredCache = new Map();
212
+ /** XSD patterns match the whole value, unlike JavaScript regular expressions. */
213
+ function anchored(pattern) {
214
+ const cached = anchoredCache.get(pattern);
215
+ if (cached)
216
+ return cached;
217
+ const compiled = new RegExp(`^(?:${pattern})$`, 'u');
218
+ anchoredCache.set(pattern, compiled);
219
+ return compiled;
220
+ }
221
+ function isRecord(value) {
222
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
223
+ }
224
+ function describe(value) {
225
+ if (value === null)
226
+ return 'null';
227
+ if (Array.isArray(value))
228
+ return 'an array';
229
+ return typeof value;
230
+ }
231
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/validation/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Hungarian tax number checks.
3
+ *
4
+ * A tax number is eleven digits in three parts: an eight digit core
5
+ * (törzsszám) whose last digit is a check digit, a one digit VAT code
6
+ * (áfakód), and a two digit county code (megyekód). NAV's schemas carry the
7
+ * three parts separately, in `taxpayerId`, `vatCode` and `countyCode`.
8
+ */
9
+ /**
10
+ * Whether the check digit of an eight digit core tax number is consistent.
11
+ *
12
+ * Verified against the real tax numbers in NAV's published samples. Note that
13
+ * two of the four tax numbers those samples use (`98765432`, `87654321`) are
14
+ * obvious placeholders and do *not* satisfy the check — evidence that NAV
15
+ * validates a tax number against its taxpayer registry rather than
16
+ * arithmetically. Treat a failure here as a likely typo, and use
17
+ * `queryTaxpayer` when you need an authoritative answer.
18
+ */
19
+ export declare function isValidTaxpayerId(taxpayerId: string): boolean;
20
+ /** The check digit an eight digit core tax number should end with. */
21
+ export declare function taxpayerIdCheckDigit(firstSevenDigits: string): number;
22
+ /** Whether a VAT code is one NAV defines. */
23
+ export declare function isValidVatCode(vatCode: string): boolean;
24
+ /** Whether a county code is one NAV issues. Best-effort; see COUNTY_CODES. */
25
+ export declare function isValidCountyCode(countyCode: string): boolean;
26
+ export interface ParsedTaxNumber {
27
+ taxpayerId: string;
28
+ vatCode?: string;
29
+ countyCode?: string;
30
+ }
31
+ /**
32
+ * Split an eleven digit tax number into the three parts NAV's schema wants.
33
+ *
34
+ * Accepts the common written forms — `12345678-2-41`, `12345678 2 41` and
35
+ * `12345678241` — because that is how the number appears on paper, and
36
+ * pasting it whole into `taxpayerId` is the mistake this avoids.
37
+ */
38
+ export declare function parseTaxNumber(input: string): ParsedTaxNumber;
39
+ //# sourceMappingURL=tax-number.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tax-number.d.ts","sourceRoot":"","sources":["../../src/validation/tax-number.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAuBH;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAO7D;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CASrE;AAED,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAW7D"}