@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
@@ -4,7 +4,14 @@ import { DecimalCurrencyCalculator } from './currency.calculator.decimal.js';
4
4
  import { toPlainDecimalString } from './number.text.js';
5
5
  import { EInvoiceFormatError } from '../../errors.js';
6
6
  import type { ValidationResult } from '../validation/validation.types.js';
7
- import { getVatCategory, getVatExemption, type IVatExemption, type TVatCategoryCode } from './vat.category.js';
7
+ import {
8
+ getDefaultVatExemption,
9
+ getItemVatCategory,
10
+ getItemVatExemption,
11
+ mergeVatExemption,
12
+ type IVatExemption,
13
+ type TVatCategoryCode,
14
+ } from './vat.category.js';
8
15
 
9
16
  /** An item amount that is no finite number, so no total can be computed from it */
10
17
  export interface IInvalidItemAmount {
@@ -49,6 +56,14 @@ export const getTotalsSkippedResult = (invalid: IInvalidItemAmount[], source: st
49
56
  field: invalid.map((entry) => `items[${entry.index}].${entry.field}`).join(', '),
50
57
  });
51
58
 
59
+ /**
60
+ * The VAT categories whose tax amount (BT-117) is the taxable amount × rate:
61
+ * standard rated (BR-S-09), IGIC (BR-AF-09) and IPSI (BR-AG-09). Every other
62
+ * category states the tax 0 (BR-Z-09, BR-E-09, BR-AE-09, BR-IC-09, BR-G-09,
63
+ * BR-O-09).
64
+ */
65
+ const TAXED_CATEGORIES: readonly TVatCategoryCode[] = ['S', 'L', 'M'];
66
+
52
67
  /** The most decimals an amount may have in EN 16931 (BR-DEC-09 to BR-DEC-23) */
53
68
  export const EN16931_MAX_AMOUNT_DECIMALS = 2;
54
69
 
@@ -65,8 +80,8 @@ export const getDocumentCalculator = (currency: TAccountingDoc['currency']): Dec
65
80
  /**
66
81
  * The VAT breakdown of one VAT category and rate (BG-23): the category
67
82
  * (BT-118), the rate (BT-119), the taxable amount (BT-116), the tax on it
68
- * (BT-117) and, for a category that states one, the exemption reason
69
- * (BT-121, BT-120).
83
+ * (BT-117) and the exemption reason (BT-121, BT-120): the one its items state,
84
+ * else the one its category states by default (`getDefaultVatExemption`).
70
85
  */
71
86
  export interface IDocumentVatGroup {
72
87
  category: TVatCategoryCode;
@@ -86,7 +101,7 @@ export interface IDocumentTotals {
86
101
  minorUnits: number;
87
102
  /** the Invoice line net amount (BT-131) of each item, in item order: quantity × net price, rounded */
88
103
  lineNetAmounts: Decimal[];
89
- /** the VAT category (BT-151) of each item, in item order */
104
+ /** the VAT category (BT-151) of each item, in item order: the one it states, else AE or S (`getItemVatCategory`) */
90
105
  lineVatCategories: TVatCategoryCode[];
91
106
  /** Sum of Invoice line net amount (BT-106) */
92
107
  lineTotal: Decimal;
@@ -107,7 +122,8 @@ export interface IDocumentTotals {
107
122
  * rounded; the sum of line net amounts (BT-106) is their sum (BR-CO-10);
108
123
  * each VAT category taxable amount (BT-116) is the sum of the line net amounts
109
124
  * of that category and rate, and its tax amount (BT-117) is BT-116 × rate / 100
110
- * rounded (BR-CO-17), 0 for reverse charge (BR-AE-09); the total VAT (BT-110) is the sum of the category tax amounts
125
+ * rounded (BR-CO-17), 0 for the categories whose rate is 0 or that state no tax
126
+ * (BR-Z-09, BR-E-09, BR-AE-09, BR-IC-09, BR-G-09, BR-O-09); the total VAT (BT-110) is the sum of the category tax amounts
111
127
  * (BR-CO-14); the total with VAT (BT-112) is BT-109 + BT-110 (BR-CO-15).
112
128
  * @param accountingDoc The document
113
129
  */
@@ -116,10 +132,12 @@ export const computeDocumentTotals = (
116
132
  ): IDocumentTotals => {
117
133
  const calculator = getDocumentCalculator(accountingDoc.currency);
118
134
  const minorUnits = calculator.getCurrencyInfo().minorUnits;
119
- const category = getVatCategory(accountingDoc);
120
135
  const lineNetAmounts: Decimal[] = [];
121
136
  const lineVatCategories: TVatCategoryCode[] = [];
122
- const taxableByGroup = new Map<string, { category: TVatCategoryCode; rate: number; taxable: Decimal }>();
137
+ const taxableByGroup = new Map<
138
+ string,
139
+ { category: TVatCategoryCode; rate: number; taxable: Decimal; exemptions: IVatExemption[] }
140
+ >();
123
141
  // an amount that is no number cannot be computed with; it is refused, not treated as 0
124
142
  const [firstInvalid] = findInvalidItemAmounts(accountingDoc.items);
125
143
  if (firstInvalid) {
@@ -136,21 +154,30 @@ export const computeDocumentTotals = (
136
154
  toPlainDecimalString(item.unitNetPrice),
137
155
  );
138
156
  lineNetAmounts.push(lineNet);
157
+ const category = getItemVatCategory(accountingDoc, item);
139
158
  lineVatCategories.push(category);
140
159
  // grouped by VAT category and rate: the same rate under two categories is two groups
141
160
  const key = `${category}|${item.vatPercentage}`;
142
- const group = taxableByGroup.get(key) ?? { category, rate: item.vatPercentage, taxable: Decimal.ZERO };
161
+ const group = taxableByGroup.get(key) ?? { category, rate: item.vatPercentage, taxable: Decimal.ZERO, exemptions: [] };
143
162
  group.taxable = group.taxable.add(lineNet);
163
+ const exemption = getItemVatExemption(item);
164
+ if (exemption) {
165
+ group.exemptions.push(exemption);
166
+ }
144
167
  taxableByGroup.set(key, group);
145
168
  }
146
169
  const vatGroups: IDocumentVatGroup[] = [...taxableByGroup.values()].map((group) => {
147
- const exemption = getVatExemption(group.category, accountingDoc.language);
170
+ // the reason the items state; items of one breakdown that state different ones are refused
171
+ // on export and reported by the validators (findVatCategoryViolations)
172
+ const exemption = mergeVatExemption(group.exemptions[0], getDefaultVatExemption(group.category, accountingDoc.language));
148
173
  return {
149
174
  category: group.category,
150
175
  rate: group.rate,
151
176
  taxableAmount: calculator.round(group.taxable),
152
- // reverse charge states no tax: the recipient owes it (BR-AE-09)
153
- taxAmount: group.category === 'AE' ? calculator.round(Decimal.ZERO) : calculator.calculateVAT(group.taxable, group.rate),
177
+ // only a taxed category states tax; reverse charge states none, the recipient owes it (BR-AE-09)
178
+ taxAmount: TAXED_CATEGORIES.includes(group.category)
179
+ ? calculator.calculateVAT(group.taxable, group.rate)
180
+ : calculator.round(Decimal.ZERO),
154
181
  ...(exemption ? { exemption } : {}),
155
182
  };
156
183
  });
@@ -485,8 +485,11 @@ export const findEnvelopeDifferences = (
485
485
  }
486
486
  });
487
487
  values.vatBreakdown.forEach((group, index) => {
488
+ // a breakdown not subject to VAT (O) may state no rate; the envelope's has the rate 0
488
489
  const computed = totals.vatGroups.find(
489
- (candidate) => candidate.category === group.categoryCode && group.rate !== undefined && decimal(candidate.rate).equals(decimal(group.rate)),
490
+ (candidate) =>
491
+ candidate.category === group.categoryCode &&
492
+ (group.rate === undefined ? group.categoryCode === 'O' : decimal(candidate.rate).equals(decimal(group.rate))),
490
493
  );
491
494
  if (!computed) {
492
495
  differs('BG-23', `vatBreakdown[${index}]`, `${group.categoryCode ?? '?'} ${group.rate ?? ''}`.trim(), 'no breakdown of that category and rate');