@fin.cx/einvoice 10.0.0 → 10.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 (50) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/einvoice.d.ts +2 -0
  3. package/dist_ts/einvoice.js +7 -13
  4. package/dist_ts/formats/base/base.decoder.d.ts +9 -1
  5. package/dist_ts/formats/base/base.decoder.js +24 -12
  6. package/dist_ts/formats/cii/cii.decoder.d.ts +11 -3
  7. package/dist_ts/formats/cii/cii.decoder.js +27 -8
  8. package/dist_ts/formats/cii/cii.encoder.d.ts +12 -0
  9. package/dist_ts/formats/cii/cii.encoder.js +36 -1
  10. package/dist_ts/formats/cii/cii.types.js +2 -1
  11. package/dist_ts/formats/cii/facturx/facturx.encoder.js +12 -8
  12. package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +12 -8
  13. package/dist_ts/formats/semantic/semantic.adapter.d.ts +0 -4
  14. package/dist_ts/formats/semantic/semantic.adapter.js +7 -18
  15. package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +6 -1
  16. package/dist_ts/formats/ubl/generic/ubl.encoder.js +40 -8
  17. package/dist_ts/formats/ubl/ubl.decoder.d.ts +11 -0
  18. package/dist_ts/formats/ubl/ubl.decoder.js +30 -1
  19. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +3 -3
  20. package/dist_ts/formats/utils/date.value.d.ts +24 -0
  21. package/dist_ts/formats/utils/date.value.js +77 -12
  22. package/dist_ts/formats/utils/delivery.address.d.ts +18 -0
  23. package/dist_ts/formats/utils/delivery.address.js +30 -0
  24. package/dist_ts/formats/utils/document.totals.d.ts +5 -4
  25. package/dist_ts/formats/utils/document.totals.js +24 -8
  26. package/dist_ts/formats/utils/stated.values.js +4 -2
  27. package/dist_ts/formats/utils/vat.category.d.ts +146 -55
  28. package/dist_ts/formats/utils/vat.category.js +357 -69
  29. package/dist_ts/formats/validation/vat-categories.validator.d.ts +14 -15
  30. package/dist_ts/formats/validation/vat-categories.validator.js +123 -85
  31. package/package.json +5 -5
  32. package/readme.md +61 -20
  33. package/ts/00_commitinfo_data.ts +1 -1
  34. package/ts/einvoice.ts +8 -14
  35. package/ts/formats/base/base.decoder.ts +24 -17
  36. package/ts/formats/cii/cii.decoder.ts +28 -7
  37. package/ts/formats/cii/cii.encoder.ts +36 -0
  38. package/ts/formats/cii/cii.types.ts +1 -0
  39. package/ts/formats/cii/facturx/facturx.encoder.ts +12 -7
  40. package/ts/formats/cii/zugferd/zugferd.encoder.ts +12 -7
  41. package/ts/formats/semantic/semantic.adapter.ts +6 -15
  42. package/ts/formats/ubl/generic/ubl.encoder.ts +40 -7
  43. package/ts/formats/ubl/ubl.decoder.ts +31 -0
  44. package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +2 -2
  45. package/ts/formats/utils/date.value.ts +84 -11
  46. package/ts/formats/utils/delivery.address.ts +36 -0
  47. package/ts/formats/utils/document.totals.ts +38 -11
  48. package/ts/formats/utils/stated.values.ts +4 -1
  49. package/ts/formats/utils/vat.category.ts +426 -95
  50. package/ts/formats/validation/vat-categories.validator.ts +169 -88
@@ -9,7 +9,12 @@ import type { TAccountingDocItem } from '@tsclass/tsclass/dist_ts/finance/index.
9
9
  import type { EInvoice } from '../../einvoice.js';
10
10
  import type { DecimalCurrencyCalculator } from '../utils/currency.calculator.decimal.js';
11
11
  import { Decimal } from '../utils/decimal.js';
12
- import type { TVatCategoryCode } from '../utils/vat.category.js';
12
+ import {
13
+ findVatCategoryViolations,
14
+ VAT_EXEMPTION_DIFFERS,
15
+ type IVatCategoryViolation,
16
+ type TVatCategoryCode,
17
+ } from '../utils/vat.category.js';
13
18
  import type { ValidationResult } from './validation.types.js';
14
19
 
15
20
  /**
@@ -40,9 +45,11 @@ export interface VATBreakdown {
40
45
  }
41
46
 
42
47
  /**
43
- * The EN 16931 rules this validator checks, with their official text (CEN/TC 434
44
- * EN 16931 validation artefacts 1.3.16, the UBL model; the CII model differs only
45
- * in the case of "Breakdown" in BR-AE-10 and BR-S-10 and "VAT breakdown" in BR-AE-01)
48
+ * The EN 16931 rules this validator checks, with their official text (CEN/TC 434 EN 16931
49
+ * validation artefacts 1.3.16, the UBL model; the CII model differs only in the case of
50
+ * "Breakdown" in BR-AE-10 and BR-S-10 and "VAT breakdown" in BR-AE-01), BR-CL-22 with the
51
+ * text of the code list rules, and the package's own check that the items of one breakdown
52
+ * state one exemption reason
46
53
  */
47
54
  const VAT_CATEGORY_RULES = {
48
55
  'BR-S-01':
@@ -55,6 +62,26 @@ const VAT_CATEGORY_RULES = {
55
62
  'The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where VAT category code (BT-118) is "Standard rated" shall equal the VAT category taxable amount (BT-116) multiplied by the VAT category rate (BT-119).',
56
63
  'BR-S-10':
57
64
  'A VAT breakdown (BG-23) with VAT Category code (BT-118) "Standard rate" shall not have a VAT exemption reason code (BT-121) or VAT exemption reason text (BT-120).',
65
+ 'BR-Z-02':
66
+ 'An Invoice that contains an Invoice line where the Invoiced item VAT category code (BT-151) is "Zero rated" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63).',
67
+ 'BR-Z-05':
68
+ 'In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "Zero rated" the Invoiced item VAT rate (BT-152) shall be 0 (zero).',
69
+ 'BR-Z-08':
70
+ 'In a VAT breakdown (BG-23) where VAT category code (BT-118) is "Zero rated" the VAT category taxable amount (BT-116) shall equal the sum of Invoice line net amount (BT-131) minus the sum of Document level allowance amounts (BT-92) plus the sum of Document level charge amounts (BT-99) where the VAT category codes (BT-151, BT-95, BT-102) are "Zero rated".',
71
+ 'BR-Z-09':
72
+ 'The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where VAT category code (BT-118) is "Zero rated" shall equal 0 (zero).',
73
+ 'BR-Z-10':
74
+ 'A VAT breakdown (BG-23) with VAT Category code (BT-118) "Zero rated" shall not have a VAT exemption reason code (BT-121) or VAT exemption reason text (BT-120).',
75
+ 'BR-E-02':
76
+ 'An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "Exempt from VAT" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63).',
77
+ 'BR-E-05':
78
+ 'In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "Exempt from VAT", the Invoiced item VAT rate (BT-152) shall be 0 (zero).',
79
+ 'BR-E-08':
80
+ 'In a VAT breakdown (BG-23) where the VAT category code (BT-118) is "Exempt from VAT" the VAT category taxable amount (BT-116) shall equal the sum of Invoice line net amounts (BT-131) minus the sum of Document level allowance amounts (BT-92) plus the sum of Document level charge amounts (BT-99) where the VAT category codes (BT-151, BT-95, BT-102) are "Exempt from VAT".',
81
+ 'BR-E-09':
82
+ 'The VAT category tax amount (BT-117) In a VAT breakdown (BG-23) where the VAT category code (BT-118) equals "Exempt from VAT" shall equal 0 (zero).',
83
+ 'BR-E-10':
84
+ 'A VAT breakdown (BG-23) with VAT Category code (BT-118) "Exempt from VAT" shall have a VAT exemption reason code (BT-121) or a VAT exemption reason text (BT-120).',
58
85
  'BR-AE-01':
59
86
  'An Invoice that contains an Invoice line (BG-25), a Document level allowance (BG-20) or a Document level charge (BG-21) where the VAT category code (BT-151, BT-95 or BT-102) is "Reverse charge" shall contain in the VAT Breakdown (BG-23) exactly one VAT category code (BT-118) equal with "VAT reverse charge".',
60
87
  'BR-AE-02':
@@ -67,8 +94,67 @@ const VAT_CATEGORY_RULES = {
67
94
  'The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where the VAT category code (BT-118) is "Reverse charge" shall be 0 (zero).',
68
95
  'BR-AE-10':
69
96
  'A VAT breakdown (BG-23) with VAT Category code (BT-118) "Reverse charge" shall have a VAT exemption reason code (BT-121), meaning "Reverse charge" or the VAT exemption reason text (BT-120) "Reverse charge" (or the equivalent standard text in another language).',
97
+ 'BR-IC-02':
98
+ 'An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "Intra-community supply" shall contain the Seller VAT Identifier (BT-31) or the Seller tax representative VAT identifier (BT-63) and the Buyer VAT identifier (BT-48).',
99
+ 'BR-IC-05':
100
+ 'In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "Intracommunity supply" the Invoiced item VAT rate (BT-152) shall be 0 (zero).',
101
+ 'BR-IC-08':
102
+ 'In a VAT breakdown (BG-23) where the VAT category code (BT-118) is "Intra-community supply" the VAT category taxable amount (BT-116) shall equal the sum of Invoice line net amounts (BT-131) minus the sum of Document level allowance amounts (BT-92) plus the sum of Document level charge amounts (BT-99) where the VAT category codes (BT-151, BT-95, BT-102) are "Intra-community supply".',
103
+ 'BR-IC-09':
104
+ 'The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where the VAT category code (BT-118) is "Intra-community supply" shall be 0 (zero).',
105
+ 'BR-IC-10':
106
+ 'A VAT breakdown (BG-23) with the VAT Category code (BT-118) "Intra-community supply" shall have a VAT exemption reason code (BT-121), meaning "Intra-community supply" or the VAT exemption reason text (BT-120) "Intra-community supply" (or the equivalent standard text in another language).',
107
+ 'BR-IC-11':
108
+ 'In an Invoice with a VAT breakdown (BG-23) where the VAT category code (BT-118) is "Intra-community supply" the Actual delivery date (BT-72) or the Invoicing period (BG-14) shall not be blank.',
109
+ 'BR-IC-12':
110
+ 'In an Invoice with a VAT breakdown (BG-23) where the VAT category code (BT-118) is "Intra-community supply" the Deliver to country code (BT-80) shall not be blank.',
111
+ 'BR-G-02':
112
+ 'An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "Export outside the EU" shall contain the Seller VAT Identifier (BT-31) or the Seller tax representative VAT identifier (BT-63).',
113
+ 'BR-G-05':
114
+ 'In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "Export outside the EU" the Invoiced item VAT rate (BT-152) shall be 0 (zero).',
115
+ 'BR-G-08':
116
+ 'In a VAT breakdown (BG-23) where the VAT category code (BT-118) is "Export outside the EU" the VAT category taxable amount (BT-116) shall equal the sum of Invoice line net amounts (BT-131) minus the sum of Document level allowance amounts (BT-92) plus the sum of Document level charge amounts (BT-99) where the VAT category codes (BT-151, BT-95, BT-102) are "Export outside the EU".',
117
+ 'BR-G-09':
118
+ 'The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where the VAT category code (BT-118) is "Export outside the EU" shall be 0 (zero).',
119
+ 'BR-G-10':
120
+ 'A VAT breakdown (BG-23) with the VAT Category code (BT-118) "Export outside the EU" shall have a VAT exemption reason code (BT-121), meaning "Export outside the EU" or the VAT exemption reason text (BT-120) "Export outside the EU" (or the equivalent standard text in another language).',
121
+ 'BR-O-02':
122
+ 'An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "Not subject to VAT" shall not contain the Seller VAT identifier (BT-31), the Seller tax representative VAT identifier (BT-63) or the Buyer VAT identifier (BT-48).',
123
+ 'BR-O-05':
124
+ 'An Invoice line (BG-25) where the VAT category code (BT-151) is "Not subject to VAT" shall not contain an Invoiced item VAT rate (BT-152).',
125
+ 'BR-O-08':
126
+ 'In a VAT breakdown (BG-23) where the VAT category code (BT-118) is " Not subject to VAT" the VAT category taxable amount (BT-116) shall equal the sum of Invoice line net amounts (BT-131) minus the sum of Document level allowance amounts (BT-92) plus the sum of Document level charge amounts (BT-99) where the VAT category codes (BT-151, BT-95, BT-102) are "Not subject to VAT".',
127
+ 'BR-O-09':
128
+ 'The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where the VAT category code (BT-118) is "Not subject to VAT" shall be 0 (zero).',
129
+ 'BR-O-10':
130
+ 'A VAT breakdown (BG-23) with VAT Category code (BT-118) " Not subject to VAT" shall have a VAT exemption reason code (BT-121), meaning " Not subject to VAT" or a VAT exemption reason text (BT-120) " Not subject to VAT" (or the equivalent standard text in another language).',
131
+ 'BR-O-11':
132
+ 'An Invoice that contains a VAT breakdown group (BG-23) with a VAT category code (BT-118) "Not subject to VAT" shall not contain other VAT breakdown groups (BG-23).',
133
+ 'BR-AF-02':
134
+ 'An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IGIC" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63).',
135
+ 'BR-AF-05':
136
+ 'In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IGIC" the invoiced item VAT rate (BT-152) shall be 0 (zero) or greater than zero.',
137
+ 'BR-AF-08':
138
+ 'For each different value of VAT category rate (BT-119) where the VAT category code (BT-118) is "IGIC", the VAT category taxable amount (BT-116) in a VAT breakdown (BG-23) shall equal the sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92) where the VAT category code (BT-151, BT-102, BT-95) is "IGIC" and the VAT rate (BT-152, BT-103, BT-96) equals the VAT category rate (BT-119).',
139
+ 'BR-AF-09':
140
+ 'The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where VAT category code (BT-118) is "IGIC" shall equal the VAT category taxable amount (BT-116) multiplied by the VAT category rate (BT-119).',
141
+ 'BR-AF-10':
142
+ 'A VAT breakdown (BG-23) with VAT Category code (BT-118) "IGIC" shall not have a VAT exemption reason code (BT-121) or VAT exemption reason text (BT-120).',
143
+ 'BR-AG-02':
144
+ 'An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IPSI" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63).',
145
+ 'BR-AG-05':
146
+ 'In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IPSI" the Invoiced item VAT rate (BT-152) shall be 0 (zero) or greater than zero.',
147
+ 'BR-AG-08':
148
+ 'For each different value of VAT category rate (BT-119) where the VAT category code (BT-118) is "IPSI", the VAT category taxable amount (BT-116) in a VAT breakdown (BG-23) shall equal the sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92) where the VAT category code (BT-151, BT-102, BT-95) is "IPSI" and the VAT rate (BT-152, BT-103, BT-96) equals the VAT category rate (BT-119).',
149
+ 'BR-AG-09':
150
+ 'The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where VAT category code (BT-118) is "IPSI" shall equal the VAT category taxable amount (BT-116) multiplied by the VAT category rate (BT-119).',
151
+ 'BR-AG-10':
152
+ 'A VAT breakdown (BG-23) with VAT Category code (BT-118) "IPSI" shall not have a VAT exemption reason code (BT-121) or VAT exemption reason text (BT-120).',
70
153
  'BR-CO-17':
71
154
  'VAT category tax amount (BT-117) = VAT category taxable amount (BT-116) x (VAT category rate (BT-119) / 100), rounded to two decimals.',
155
+ 'BR-CL-22': 'Tax exemption reason code identifier scheme identifier MUST belong to the CEF VATEX code list',
156
+ [VAT_EXEMPTION_DIFFERS]:
157
+ 'The Invoice lines (BG-25) of one VAT breakdown (BG-23) state one VAT exemption reason text (BT-120) and code (BT-121), as the breakdown states one.',
72
158
  } as const;
73
159
 
74
160
  type TVatCategoryRuleId = keyof typeof VAT_CATEGORY_RULES;
@@ -83,20 +169,66 @@ interface IVatCategoryFinding {
83
169
  remediation?: string;
84
170
  }
85
171
 
172
+ /** The rule IDs of the taxable amount (-08) and the tax amount (-09) of a VAT breakdown, per category */
173
+ const BREAKDOWN_RULES: Record<TVatCategoryCode, { taxable: TVatCategoryRuleId; tax: TVatCategoryRuleId }> = {
174
+ S: { taxable: 'BR-S-08', tax: 'BR-S-09' },
175
+ Z: { taxable: 'BR-Z-08', tax: 'BR-Z-09' },
176
+ E: { taxable: 'BR-E-08', tax: 'BR-E-09' },
177
+ AE: { taxable: 'BR-AE-08', tax: 'BR-AE-09' },
178
+ K: { taxable: 'BR-IC-08', tax: 'BR-IC-09' },
179
+ G: { taxable: 'BR-G-08', tax: 'BR-G-09' },
180
+ O: { taxable: 'BR-O-08', tax: 'BR-O-09' },
181
+ L: { taxable: 'BR-AF-08', tax: 'BR-AF-09' },
182
+ M: { taxable: 'BR-AG-08', tax: 'BR-AG-09' },
183
+ };
184
+
185
+ /** The categories whose tax amount is the taxable amount × rate (BR-S-09, BR-AF-09, BR-AG-09); the others state 0 */
186
+ const TAXED_CATEGORIES: readonly TVatCategoryCode[] = ['S', 'L', 'M'];
187
+
188
+ /**
189
+ * The business terms and groups a finding of a rule on the items' VAT categories points at
190
+ * @param violation The violation
191
+ */
192
+ const referencesOf = (violation: IVatCategoryViolation): { btReference: string; bgReference?: string } => {
193
+ const suffix = violation.ruleId.split('-').pop();
194
+ if (violation.ruleId === 'BR-CL-22') {
195
+ return { btReference: 'BT-121', bgReference: 'BG-23' };
196
+ }
197
+ if (violation.ruleId === VAT_EXEMPTION_DIFFERS) {
198
+ return { btReference: 'BT-120', bgReference: 'BG-23' };
199
+ }
200
+ if (violation.ruleId === 'BR-IC-11') {
201
+ return { btReference: 'BT-72', bgReference: 'BG-13' };
202
+ }
203
+ if (violation.ruleId === 'BR-IC-12') {
204
+ return { btReference: 'BT-80', bgReference: 'BG-15' };
205
+ }
206
+ if (violation.ruleId === 'BR-O-11') {
207
+ return { btReference: 'BT-151', bgReference: 'BG-25' };
208
+ }
209
+ switch (suffix) {
210
+ case '02':
211
+ return violation.field.startsWith('to.') ? { btReference: 'BT-48', bgReference: 'BG-7' } : { btReference: 'BT-31', bgReference: 'BG-4' };
212
+ case '05':
213
+ return { btReference: 'BT-152', bgReference: 'BG-25' };
214
+ default:
215
+ return { btReference: 'BT-121', bgReference: 'BG-23' };
216
+ }
217
+ };
218
+
86
219
  /**
87
220
  * The EN 16931 VAT category rules of a document, checked on the VAT categories
88
221
  * and the VAT breakdown the encoders write.
89
222
  *
90
- * The category of every line is the one the encoders write (`getVatCategory`):
91
- * reverse charge (AE) when the document states `reverseCharge`, standard rated
92
- * (S) otherwise. The envelope states no category of its own for an item or a
93
- * breakdown, so the other categories (Z, E, K, G, O, L, M) cannot occur, and
94
- * an item's `metadata` is not read for one. A decoded document may state one
95
- * for a line (`statedValues.lines[].vatCategoryCode`); the envelope reads such
96
- * a line as S, so BR-S-05 is not reported for a line whose stated category is
97
- * not S. The VAT breakdown is the one the
98
- * encoders write (`computeDocumentTotals().vatGroups`): one group per category
99
- * and rate. The envelope has no document level allowances or charges, so the
223
+ * The category of every line is the one the encoders write
224
+ * (`getItemVatCategory`): the one the item states (`vatCategory`, BT-151),
225
+ * else reverse charge (AE) when the document states `reverseCharge` and
226
+ * standard rated (S) otherwise. A decoded line carries the category its
227
+ * document states. The rules on the items, their exemption reasons and the
228
+ * parties' identifiers are those export refuses a document for
229
+ * (`findVatCategoryViolations`). The VAT breakdown is the one the encoders
230
+ * write (`computeDocumentTotals().vatGroups`): one group per category and
231
+ * rate. The envelope has no document level allowances or charges, so the
100
232
  * rules on them (BR-S-02 to -04 and -06 to -07 and their counterparts) do not
101
233
  * apply, and a taxable amount is the sum of the line net amounts.
102
234
  */
@@ -121,42 +253,23 @@ export class VATCategoriesValidator {
121
253
  const calculator = getDocumentCalculator(invoice.currency);
122
254
  const items = invoice.items ?? [];
123
255
 
124
- this.validateLines(invoice, items, totals);
256
+ for (const violation of findVatCategoryViolations(invoice)) {
257
+ this.addError(violation.ruleId as TVatCategoryRuleId, {
258
+ field: violation.field,
259
+ ...referencesOf(violation),
260
+ remediation: violation.message,
261
+ });
262
+ }
263
+ this.validateBreakdownPresence(totals);
125
264
  this.validateBreakdown(items, totals, calculator);
126
265
  return this.results;
127
266
  }
128
267
 
129
268
  /**
130
- * The rules on the lines of each category: the rate (BR-S-05, BR-AE-05), the breakdown the
131
- * category needs (BR-S-01, BR-AE-01) and the parties' identifiers of reverse charge (BR-AE-02)
269
+ * The breakdown a category of the lines needs: at least one for S (BR-S-01), exactly one for
270
+ * reverse charge (BR-AE-01)
132
271
  */
133
- private validateLines(invoice: EInvoice, items: TAccountingDocItem[], totals: IDocumentTotals): void {
134
- items.forEach((item, index) => {
135
- const category = totals.lineVatCategories[index];
136
- // A decoded line keeps the category its document states (BT-151). One that is not S is a
137
- // category the envelope cannot express (Z, E, K, G, O, or AE in a document that mixes S and
138
- // AE lines, which the decoders do not read as reverse charge); its rate says nothing of BR-S-05.
139
- const statedCategory = invoice.statedValues?.lines[index]?.vatCategoryCode;
140
- if (category === 'S' && !(item.vatPercentage > 0) && (statedCategory === undefined || statedCategory === 'S')) {
141
- this.addError('BR-S-05', {
142
- field: `items[${index}].vatPercentage`,
143
- btReference: 'BT-152',
144
- bgReference: 'BG-25',
145
- value: item.vatPercentage,
146
- expected: '> 0',
147
- });
148
- }
149
- if (category === 'AE' && item.vatPercentage !== 0) {
150
- this.addError('BR-AE-05', {
151
- field: `items[${index}].vatPercentage`,
152
- btReference: 'BT-152',
153
- bgReference: 'BG-25',
154
- value: item.vatPercentage,
155
- expected: 0,
156
- });
157
- }
158
- });
159
-
272
+ private validateBreakdownPresence(totals: IDocumentTotals): void {
160
273
  const hasCategory = (category: TVatCategoryCode) => totals.lineVatCategories.includes(category);
161
274
  const groupsOf = (category: TVatCategoryCode) => totals.vatGroups.filter((group) => group.category === category);
162
275
  if (hasCategory('S') && groupsOf('S').length === 0) {
@@ -171,32 +284,11 @@ export class VATCategoriesValidator {
171
284
  remediation: 'a reverse charge breakdown per rate: give every reverse charge line the VAT rate 0 (BR-AE-05)',
172
285
  });
173
286
  }
174
-
175
- if (hasCategory('AE')) {
176
- const seller = invoice.from?.registrationDetails;
177
- if (!seller?.vatId?.trim() && !seller?.taxNumber?.trim()) {
178
- this.addError('BR-AE-02', {
179
- field: 'from.registrationDetails',
180
- btReference: 'BT-31',
181
- bgReference: 'BG-4',
182
- remediation: 'state from.registrationDetails.vatId (BT-31) or .taxNumber (BT-32); the envelope cannot state a tax representative (BT-63)',
183
- });
184
- }
185
- const buyer = invoice.to?.registrationDetails;
186
- if (!buyer?.vatId?.trim() && !buyer?.registrationId?.trim()) {
187
- this.addError('BR-AE-02', {
188
- field: 'to.registrationDetails',
189
- bgReference: 'BG-7',
190
- btReference: 'BT-48',
191
- remediation: 'state to.registrationDetails.vatId (BT-48) or .registrationId (BT-47)',
192
- });
193
- }
194
- }
195
287
  }
196
288
 
197
289
  /**
198
- * The rules on each VAT breakdown group: its taxable amount (BR-S-08, BR-AE-08), its tax
199
- * amount (BR-S-09, BR-AE-09, BR-CO-17) and its exemption reason (BR-S-10, BR-AE-10)
290
+ * The rules on each VAT breakdown group: its taxable amount (BR-S-08 and its counterparts of
291
+ * every category) and its tax amount (BR-S-09 and its counterparts, BR-CO-17)
200
292
  */
201
293
  private validateBreakdown(items: TAccountingDocItem[], totals: IDocumentTotals, calculator: DecimalCurrencyCalculator): void {
202
294
  for (const group of totals.vatGroups) {
@@ -206,29 +298,18 @@ export class VATCategoriesValidator {
206
298
  const expectedTaxable = calculator.round(Decimal.sum(lineNetAmounts));
207
299
  const expectedTax = calculator.calculateVAT(group.taxableAmount, group.rate);
208
300
  const amounts = (value: Decimal, expected: Decimal) => ({ value: value.toNumber(), expected: expected.toNumber() });
209
- const at = `rate ${group.rate}`;
301
+ const at = `${group.category} rate ${group.rate}`;
302
+ const rules = BREAKDOWN_RULES[group.category];
210
303
 
211
- if (group.category === 'S') {
212
- if (!group.taxableAmount.equals(expectedTaxable)) {
213
- this.addError('BR-S-08', { field: `taxBreakdown[${at}].netAmount`, btReference: 'BT-116', ...amounts(group.taxableAmount, expectedTaxable) });
214
- }
215
- if (!group.taxAmount.equals(expectedTax)) {
216
- this.addError('BR-S-09', { field: `taxBreakdown[${at}].taxAmount`, btReference: 'BT-117', ...amounts(group.taxAmount, expectedTax) });
217
- }
218
- if (group.exemption) {
219
- this.addError('BR-S-10', { field: `taxBreakdown[${at}]`, btReference: 'BT-121', value: group.exemption.code });
220
- }
304
+ if (!group.taxableAmount.equals(expectedTaxable)) {
305
+ this.addError(rules.taxable, { field: `taxBreakdown[${at}].netAmount`, btReference: 'BT-116', ...amounts(group.taxableAmount, expectedTaxable) });
221
306
  }
222
- if (group.category === 'AE') {
223
- if (!group.taxableAmount.equals(expectedTaxable)) {
224
- this.addError('BR-AE-08', { field: `taxBreakdown[${at}].netAmount`, btReference: 'BT-116', ...amounts(group.taxableAmount, expectedTaxable) });
225
- }
226
- if (!group.taxAmount.isZero()) {
227
- this.addError('BR-AE-09', { field: `taxBreakdown[${at}].taxAmount`, btReference: 'BT-117', value: group.taxAmount.toNumber(), expected: 0 });
228
- }
229
- if (group.exemption?.code !== 'VATEX-EU-AE' && !group.exemption?.reason.trim()) {
230
- this.addError('BR-AE-10', { field: `taxBreakdown[${at}]`, btReference: 'BT-121' });
307
+ if (TAXED_CATEGORIES.includes(group.category)) {
308
+ if (!group.taxAmount.equals(expectedTax)) {
309
+ this.addError(rules.tax, { field: `taxBreakdown[${at}].taxAmount`, btReference: 'BT-117', ...amounts(group.taxAmount, expectedTax) });
231
310
  }
311
+ } else if (!group.taxAmount.isZero()) {
312
+ this.addError(rules.tax, { field: `taxBreakdown[${at}].taxAmount`, btReference: 'BT-117', value: group.taxAmount.toNumber(), expected: 0 });
232
313
  }
233
314
  if (!group.taxAmount.equals(expectedTax)) {
234
315
  this.addError('BR-CO-17', { field: `taxBreakdown[${at}].taxAmount`, btReference: 'BT-117', ...amounts(group.taxAmount, expectedTax) });
@@ -239,7 +320,7 @@ export class VATCategoriesValidator {
239
320
  private addError(ruleId: TVatCategoryRuleId, finding: IVatCategoryFinding): void {
240
321
  this.results.push({
241
322
  ruleId,
242
- source: 'EN16931',
323
+ source: ruleId === VAT_EXEMPTION_DIFFERS ? 'EINVOICE' : 'EN16931',
243
324
  severity: 'error',
244
325
  message: VAT_CATEGORY_RULES[ruleId],
245
326
  bgReference: 'BG-23',