@fin.cx/einvoice 5.0.2

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 (171) hide show
  1. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  2. package/dist_ts/00_commitinfo_data.js +9 -0
  3. package/dist_ts/classes.decoder.d.ts +40 -0
  4. package/dist_ts/classes.decoder.js +320 -0
  5. package/dist_ts/classes.encoder.d.ts +51 -0
  6. package/dist_ts/classes.encoder.js +293 -0
  7. package/dist_ts/classes.xinvoice.d.ts +136 -0
  8. package/dist_ts/classes.xinvoice.js +352 -0
  9. package/dist_ts/einvoice.d.ts +217 -0
  10. package/dist_ts/einvoice.js +505 -0
  11. package/dist_ts/errors.d.ts +121 -0
  12. package/dist_ts/errors.js +241 -0
  13. package/dist_ts/formats/base/base.decoder.d.ts +32 -0
  14. package/dist_ts/formats/base/base.decoder.js +52 -0
  15. package/dist_ts/formats/base/base.encoder.d.ts +13 -0
  16. package/dist_ts/formats/base/base.encoder.js +7 -0
  17. package/dist_ts/formats/base/base.validator.d.ts +44 -0
  18. package/dist_ts/formats/base/base.validator.js +39 -0
  19. package/dist_ts/formats/base.decoder.d.ts +19 -0
  20. package/dist_ts/formats/base.decoder.js +130 -0
  21. package/dist_ts/formats/base.validator.d.ts +44 -0
  22. package/dist_ts/formats/base.validator.js +39 -0
  23. package/dist_ts/formats/cii/cii.decoder.d.ts +61 -0
  24. package/dist_ts/formats/cii/cii.decoder.js +111 -0
  25. package/dist_ts/formats/cii/cii.encoder.d.ts +43 -0
  26. package/dist_ts/formats/cii/cii.encoder.js +48 -0
  27. package/dist_ts/formats/cii/cii.types.d.ts +31 -0
  28. package/dist_ts/formats/cii/cii.types.js +40 -0
  29. package/dist_ts/formats/cii/cii.validator.d.ts +56 -0
  30. package/dist_ts/formats/cii/cii.validator.js +146 -0
  31. package/dist_ts/formats/cii/facturx/facturx.decoder.d.ts +43 -0
  32. package/dist_ts/formats/cii/facturx/facturx.decoder.js +207 -0
  33. package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +82 -0
  34. package/dist_ts/formats/cii/facturx/facturx.encoder.js +400 -0
  35. package/dist_ts/formats/cii/facturx/facturx.types.d.ts +10 -0
  36. package/dist_ts/formats/cii/facturx/facturx.types.js +15 -0
  37. package/dist_ts/formats/cii/facturx/facturx.validator.d.ts +32 -0
  38. package/dist_ts/formats/cii/facturx/facturx.validator.js +138 -0
  39. package/dist_ts/formats/cii/zugferd/zugferd.decoder.d.ts +43 -0
  40. package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +218 -0
  41. package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +97 -0
  42. package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +550 -0
  43. package/dist_ts/formats/cii/zugferd/zugferd.types.d.ts +10 -0
  44. package/dist_ts/formats/cii/zugferd/zugferd.types.js +15 -0
  45. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +49 -0
  46. package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +229 -0
  47. package/dist_ts/formats/cii/zugferd/zugferd.validator.d.ts +16 -0
  48. package/dist_ts/formats/cii/zugferd/zugferd.validator.js +29 -0
  49. package/dist_ts/formats/decoder.factory.d.ts +15 -0
  50. package/dist_ts/formats/decoder.factory.js +46 -0
  51. package/dist_ts/formats/factories/decoder.factory.d.ts +13 -0
  52. package/dist_ts/formats/factories/decoder.factory.js +56 -0
  53. package/dist_ts/formats/factories/encoder.factory.d.ts +14 -0
  54. package/dist_ts/formats/factories/encoder.factory.js +40 -0
  55. package/dist_ts/formats/factories/validator.factory.d.ts +12 -0
  56. package/dist_ts/formats/factories/validator.factory.js +110 -0
  57. package/dist_ts/formats/factorx.decoder.d.ts +18 -0
  58. package/dist_ts/formats/factorx.decoder.js +178 -0
  59. package/dist_ts/formats/factorx.encoder.d.ts +51 -0
  60. package/dist_ts/formats/factorx.encoder.js +293 -0
  61. package/dist_ts/formats/facturx.decoder.d.ts +18 -0
  62. package/dist_ts/formats/facturx.decoder.js +210 -0
  63. package/dist_ts/formats/facturx.encoder.d.ts +51 -0
  64. package/dist_ts/formats/facturx.encoder.js +293 -0
  65. package/dist_ts/formats/facturx.validator.d.ts +67 -0
  66. package/dist_ts/formats/facturx.validator.js +266 -0
  67. package/dist_ts/formats/pdf/extractors/associated.extractor.d.ts +14 -0
  68. package/dist_ts/formats/pdf/extractors/associated.extractor.js +68 -0
  69. package/dist_ts/formats/pdf/extractors/base.extractor.d.ts +92 -0
  70. package/dist_ts/formats/pdf/extractors/base.extractor.js +319 -0
  71. package/dist_ts/formats/pdf/extractors/index.d.ts +4 -0
  72. package/dist_ts/formats/pdf/extractors/index.js +5 -0
  73. package/dist_ts/formats/pdf/extractors/standard.extractor.d.ts +13 -0
  74. package/dist_ts/formats/pdf/extractors/standard.extractor.js +74 -0
  75. package/dist_ts/formats/pdf/extractors/text.extractor.d.ts +45 -0
  76. package/dist_ts/formats/pdf/extractors/text.extractor.js +151 -0
  77. package/dist_ts/formats/pdf/pdf.embedder.d.ts +69 -0
  78. package/dist_ts/formats/pdf/pdf.embedder.js +183 -0
  79. package/dist_ts/formats/pdf/pdf.extractor.d.ts +49 -0
  80. package/dist_ts/formats/pdf/pdf.extractor.js +98 -0
  81. package/dist_ts/formats/pdf/robust-pdf.extractor.d.ts +40 -0
  82. package/dist_ts/formats/pdf/robust-pdf.extractor.js +324 -0
  83. package/dist_ts/formats/ubl/en16931.ubl.validator.d.ts +18 -0
  84. package/dist_ts/formats/ubl/en16931.ubl.validator.js +169 -0
  85. package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +151 -0
  86. package/dist_ts/formats/ubl/generic/ubl.encoder.js +892 -0
  87. package/dist_ts/formats/ubl/ubl.decoder.d.ts +61 -0
  88. package/dist_ts/formats/ubl/ubl.decoder.js +95 -0
  89. package/dist_ts/formats/ubl/ubl.encoder.d.ts +38 -0
  90. package/dist_ts/formats/ubl/ubl.encoder.js +46 -0
  91. package/dist_ts/formats/ubl/ubl.types.d.ts +16 -0
  92. package/dist_ts/formats/ubl/ubl.types.js +21 -0
  93. package/dist_ts/formats/ubl/ubl.validator.d.ts +50 -0
  94. package/dist_ts/formats/ubl/ubl.validator.js +112 -0
  95. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.d.ts +34 -0
  96. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +424 -0
  97. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +75 -0
  98. package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +555 -0
  99. package/dist_ts/formats/ubl/xrechnung.validator.d.ts +15 -0
  100. package/dist_ts/formats/ubl/xrechnung.validator.js +107 -0
  101. package/dist_ts/formats/ubl.validator.d.ts +82 -0
  102. package/dist_ts/formats/ubl.validator.js +306 -0
  103. package/dist_ts/formats/utils/format.detector.d.ts +62 -0
  104. package/dist_ts/formats/utils/format.detector.js +249 -0
  105. package/dist_ts/formats/validation/en16931.validator.d.ts +23 -0
  106. package/dist_ts/formats/validation/en16931.validator.js +119 -0
  107. package/dist_ts/formats/validator.factory.d.ts +18 -0
  108. package/dist_ts/formats/validator.factory.js +78 -0
  109. package/dist_ts/formats/xinvoice.decoder.d.ts +28 -0
  110. package/dist_ts/formats/xinvoice.decoder.js +332 -0
  111. package/dist_ts/formats/xinvoice.encoder.d.ts +19 -0
  112. package/dist_ts/formats/xinvoice.encoder.js +282 -0
  113. package/dist_ts/index.d.ts +51 -0
  114. package/dist_ts/index.js +90 -0
  115. package/dist_ts/interfaces/common.d.ts +79 -0
  116. package/dist_ts/interfaces/common.js +24 -0
  117. package/dist_ts/interfaces.d.ts +87 -0
  118. package/dist_ts/interfaces.js +23 -0
  119. package/dist_ts/plugins.d.ts +14 -0
  120. package/dist_ts/plugins.js +31 -0
  121. package/npmextra.json +35 -0
  122. package/package.json +71 -0
  123. package/readme.hints.md +1107 -0
  124. package/readme.howtofixtests.md +38 -0
  125. package/readme.literature.md +1 -0
  126. package/readme.md +998 -0
  127. package/readme.plan.md +481 -0
  128. package/ts/00_commitinfo_data.ts +8 -0
  129. package/ts/einvoice.ts +603 -0
  130. package/ts/errors.ts +341 -0
  131. package/ts/formats/base/base.decoder.ts +68 -0
  132. package/ts/formats/base/base.encoder.ts +14 -0
  133. package/ts/formats/base/base.validator.ts +64 -0
  134. package/ts/formats/cii/cii.decoder.ts +139 -0
  135. package/ts/formats/cii/cii.encoder.ts +64 -0
  136. package/ts/formats/cii/cii.types.ts +44 -0
  137. package/ts/formats/cii/cii.validator.ts +171 -0
  138. package/ts/formats/cii/facturx/facturx.decoder.ts +243 -0
  139. package/ts/formats/cii/facturx/facturx.encoder.ts +483 -0
  140. package/ts/formats/cii/facturx/facturx.types.ts +18 -0
  141. package/ts/formats/cii/facturx/facturx.validator.ts +180 -0
  142. package/ts/formats/cii/zugferd/zugferd.decoder.ts +256 -0
  143. package/ts/formats/cii/zugferd/zugferd.encoder.ts +661 -0
  144. package/ts/formats/cii/zugferd/zugferd.types.ts +18 -0
  145. package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +267 -0
  146. package/ts/formats/cii/zugferd/zugferd.validator.ts +31 -0
  147. package/ts/formats/factories/decoder.factory.ts +62 -0
  148. package/ts/formats/factories/encoder.factory.ts +47 -0
  149. package/ts/formats/factories/validator.factory.ts +134 -0
  150. package/ts/formats/pdf/extractors/associated.extractor.ts +78 -0
  151. package/ts/formats/pdf/extractors/base.extractor.ts +355 -0
  152. package/ts/formats/pdf/extractors/index.ts +4 -0
  153. package/ts/formats/pdf/extractors/standard.extractor.ts +86 -0
  154. package/ts/formats/pdf/extractors/text.extractor.ts +162 -0
  155. package/ts/formats/pdf/pdf.embedder.ts +242 -0
  156. package/ts/formats/pdf/pdf.extractor.ts +141 -0
  157. package/ts/formats/ubl/en16931.ubl.validator.ts +216 -0
  158. package/ts/formats/ubl/generic/ubl.encoder.ts +1041 -0
  159. package/ts/formats/ubl/ubl.decoder.ts +121 -0
  160. package/ts/formats/ubl/ubl.encoder.ts +63 -0
  161. package/ts/formats/ubl/ubl.types.ts +22 -0
  162. package/ts/formats/ubl/ubl.validator.ts +133 -0
  163. package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +471 -0
  164. package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +619 -0
  165. package/ts/formats/ubl/xrechnung.validator.ts +185 -0
  166. package/ts/formats/utils/format.detector.ts +306 -0
  167. package/ts/formats/validation/en16931.validator.ts +135 -0
  168. package/ts/index.ts +164 -0
  169. package/ts/interfaces/common.ts +90 -0
  170. package/ts/interfaces.ts +98 -0
  171. package/ts/plugins.ts +61 -0
@@ -0,0 +1,483 @@
1
+ import { CIIBaseEncoder } from '../cii.encoder.js';
2
+ import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js';
3
+ import { FACTURX_PROFILE_IDS } from './facturx.types.js';
4
+ import { DOMParser, XMLSerializer } from '../../../plugins.js';
5
+
6
+ /**
7
+ * Encoder for Factur-X invoice format
8
+ */
9
+ export class FacturXEncoder extends CIIBaseEncoder {
10
+ /**
11
+ * Encodes a TCreditNote object into Factur-X XML
12
+ * @param creditNote TCreditNote object to encode
13
+ * @returns Factur-X XML string
14
+ */
15
+ protected async encodeCreditNote(creditNote: TCreditNote): Promise<string> {
16
+ // Create base XML
17
+ const xmlDoc = this.createBaseXml();
18
+
19
+ // Set document type code to credit note (381)
20
+ this.setDocumentTypeCode(xmlDoc, '381');
21
+
22
+ // Add common invoice data
23
+ this.addCommonInvoiceData(xmlDoc, creditNote as unknown as TInvoice);
24
+
25
+ // Serialize to string
26
+ return new XMLSerializer().serializeToString(xmlDoc);
27
+ }
28
+
29
+ /**
30
+ * Encodes a TDebitNote object into Factur-X XML
31
+ * @param debitNote TDebitNote object to encode
32
+ * @returns Factur-X XML string
33
+ */
34
+ protected async encodeDebitNote(debitNote: TDebitNote): Promise<string> {
35
+ // Create base XML
36
+ const xmlDoc = this.createBaseXml();
37
+
38
+ // Set document type code to invoice (380)
39
+ this.setDocumentTypeCode(xmlDoc, '380');
40
+
41
+ // Add common invoice data
42
+ this.addCommonInvoiceData(xmlDoc, debitNote as unknown as TInvoice);
43
+
44
+ // Serialize to string
45
+ return new XMLSerializer().serializeToString(xmlDoc);
46
+ }
47
+
48
+ /**
49
+ * Creates a base Factur-X XML document
50
+ * @returns XML document with basic structure
51
+ */
52
+ private createBaseXml(): Document {
53
+ // Create XML document from template
54
+ const xmlString = this.createXmlRoot();
55
+ const doc = new DOMParser().parseFromString(xmlString, 'application/xml');
56
+
57
+ // Add Factur-X profile
58
+ this.addProfile(doc);
59
+
60
+ return doc;
61
+ }
62
+
63
+ /**
64
+ * Adds Factur-X profile information to the XML document
65
+ * @param doc XML document
66
+ */
67
+ private addProfile(doc: Document): void {
68
+ // Get root element
69
+ const root = doc.documentElement;
70
+
71
+ // Create context element if it doesn't exist
72
+ let contextElement = root.getElementsByTagName('rsm:ExchangedDocumentContext')[0];
73
+ if (!contextElement) {
74
+ contextElement = doc.createElement('rsm:ExchangedDocumentContext');
75
+ root.appendChild(contextElement);
76
+ }
77
+
78
+ // Create guideline parameter element
79
+ const guidelineElement = doc.createElement('ram:GuidelineSpecifiedDocumentContextParameter');
80
+ contextElement.appendChild(guidelineElement);
81
+
82
+ // Add ID element with profile
83
+ const idElement = doc.createElement('ram:ID');
84
+
85
+ // Set profile based on the selected profile
86
+ let profileId = FACTURX_PROFILE_IDS.EN16931;
87
+ if (this.profile === 'BASIC') {
88
+ profileId = FACTURX_PROFILE_IDS.BASIC;
89
+ } else if (this.profile === 'MINIMUM') {
90
+ profileId = FACTURX_PROFILE_IDS.MINIMUM;
91
+ }
92
+
93
+ idElement.textContent = profileId;
94
+ guidelineElement.appendChild(idElement);
95
+ }
96
+
97
+ /**
98
+ * Sets the document type code in the XML document
99
+ * @param doc XML document
100
+ * @param typeCode Document type code (380 for invoice, 381 for credit note)
101
+ */
102
+ private setDocumentTypeCode(doc: Document, typeCode: string): void {
103
+ // Get root element
104
+ const root = doc.documentElement;
105
+
106
+ // Create document element if it doesn't exist
107
+ let documentElement = root.getElementsByTagName('rsm:ExchangedDocument')[0];
108
+ if (!documentElement) {
109
+ documentElement = doc.createElement('rsm:ExchangedDocument');
110
+ root.appendChild(documentElement);
111
+ }
112
+
113
+ // Add type code element
114
+ const typeCodeElement = doc.createElement('ram:TypeCode');
115
+ typeCodeElement.textContent = typeCode;
116
+ documentElement.appendChild(typeCodeElement);
117
+ }
118
+
119
+ /**
120
+ * Adds common invoice data to the XML document
121
+ * @param doc XML document
122
+ * @param invoice Invoice data
123
+ */
124
+ private addCommonInvoiceData(doc: Document, invoice: TInvoice): void {
125
+ // Get root element
126
+ const root = doc.documentElement;
127
+
128
+ // Get document element or create it
129
+ let documentElement = root.getElementsByTagName('rsm:ExchangedDocument')[0];
130
+ if (!documentElement) {
131
+ documentElement = doc.createElement('rsm:ExchangedDocument');
132
+ root.appendChild(documentElement);
133
+ }
134
+
135
+ // Add ID element
136
+ const idElement = doc.createElement('ram:ID');
137
+ idElement.textContent = invoice.id;
138
+ documentElement.appendChild(idElement);
139
+
140
+ // Add issue date element
141
+ const issueDateElement = doc.createElement('ram:IssueDateTime');
142
+ const dateStringElement = doc.createElement('udt:DateTimeString');
143
+ dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
144
+ dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.date);
145
+ issueDateElement.appendChild(dateStringElement);
146
+ documentElement.appendChild(issueDateElement);
147
+
148
+ // Add notes - include subject as first note if it exists
149
+ const allNotes: string[] = [];
150
+ if (invoice.subject && invoice.subject.trim()) {
151
+ allNotes.push(invoice.subject);
152
+ }
153
+ if (invoice.notes && invoice.notes.length > 0) {
154
+ allNotes.push(...invoice.notes);
155
+ }
156
+
157
+ // Write all notes
158
+ for (const note of allNotes) {
159
+ const noteElement = doc.createElement('ram:IncludedNote');
160
+ const contentElement = doc.createElement('ram:Content');
161
+ contentElement.textContent = note;
162
+ noteElement.appendChild(contentElement);
163
+ documentElement.appendChild(noteElement);
164
+ }
165
+
166
+ // Create transaction element if it doesn't exist
167
+ let transactionElement = root.getElementsByTagName('rsm:SupplyChainTradeTransaction')[0];
168
+ if (!transactionElement) {
169
+ transactionElement = doc.createElement('rsm:SupplyChainTradeTransaction');
170
+ root.appendChild(transactionElement);
171
+ }
172
+
173
+ // Add agreement section with seller and buyer
174
+ this.addAgreementSection(doc, transactionElement, invoice);
175
+
176
+ // Add delivery section
177
+ this.addDeliverySection(doc, transactionElement, invoice);
178
+
179
+ // Add settlement section with payment terms and totals
180
+ this.addSettlementSection(doc, transactionElement, invoice);
181
+
182
+ // Add line items
183
+ this.addLineItems(doc, transactionElement, invoice);
184
+ }
185
+
186
+ /**
187
+ * Adds agreement section with seller and buyer information
188
+ * @param doc XML document
189
+ * @param transactionElement Transaction element
190
+ * @param invoice Invoice data
191
+ */
192
+ private addAgreementSection(doc: Document, transactionElement: Element, invoice: TInvoice): void {
193
+ // Create agreement element
194
+ const agreementElement = doc.createElement('ram:ApplicableHeaderTradeAgreement');
195
+ transactionElement.appendChild(agreementElement);
196
+
197
+ // Add seller
198
+ const sellerElement = doc.createElement('ram:SellerTradeParty');
199
+ this.addPartyInfo(doc, sellerElement, invoice.from);
200
+ agreementElement.appendChild(sellerElement);
201
+
202
+ // Add buyer
203
+ const buyerElement = doc.createElement('ram:BuyerTradeParty');
204
+ this.addPartyInfo(doc, buyerElement, invoice.to);
205
+ agreementElement.appendChild(buyerElement);
206
+ }
207
+
208
+ /**
209
+ * Adds party information to an element
210
+ * @param doc XML document
211
+ * @param partyElement Party element
212
+ * @param party Party data
213
+ */
214
+ private addPartyInfo(doc: Document, partyElement: Element, party: any): void {
215
+ // Add name
216
+ const nameElement = doc.createElement('ram:Name');
217
+ nameElement.textContent = party.name;
218
+ partyElement.appendChild(nameElement);
219
+
220
+ // Add postal address
221
+ const addressElement = doc.createElement('ram:PostalTradeAddress');
222
+
223
+ // Add address line 1 (street)
224
+ const line1Element = doc.createElement('ram:LineOne');
225
+ line1Element.textContent = party.address.streetName;
226
+ addressElement.appendChild(line1Element);
227
+
228
+ // Add address line 2 (house number)
229
+ const line2Element = doc.createElement('ram:LineTwo');
230
+ line2Element.textContent = party.address.houseNumber;
231
+ addressElement.appendChild(line2Element);
232
+
233
+ // Add postal code
234
+ const postalCodeElement = doc.createElement('ram:PostcodeCode');
235
+ postalCodeElement.textContent = party.address.postalCode;
236
+ addressElement.appendChild(postalCodeElement);
237
+
238
+ // Add city
239
+ const cityElement = doc.createElement('ram:CityName');
240
+ cityElement.textContent = party.address.city;
241
+ addressElement.appendChild(cityElement);
242
+
243
+ // Add country
244
+ const countryElement = doc.createElement('ram:CountryID');
245
+ countryElement.textContent = party.address.countryCode || party.address.country;
246
+ addressElement.appendChild(countryElement);
247
+
248
+ partyElement.appendChild(addressElement);
249
+
250
+ // Add VAT ID if available
251
+ if (party.registrationDetails && party.registrationDetails.vatId) {
252
+ const taxRegistrationElement = doc.createElement('ram:SpecifiedTaxRegistration');
253
+ const taxIdElement = doc.createElement('ram:ID');
254
+ taxIdElement.setAttribute('schemeID', 'VA');
255
+ taxIdElement.textContent = party.registrationDetails.vatId;
256
+ taxRegistrationElement.appendChild(taxIdElement);
257
+ partyElement.appendChild(taxRegistrationElement);
258
+ }
259
+
260
+ // Add registration ID if available
261
+ if (party.registrationDetails && party.registrationDetails.registrationId) {
262
+ const regRegistrationElement = doc.createElement('ram:SpecifiedTaxRegistration');
263
+ const regIdElement = doc.createElement('ram:ID');
264
+ regIdElement.setAttribute('schemeID', 'FC');
265
+ regIdElement.textContent = party.registrationDetails.registrationId;
266
+ regRegistrationElement.appendChild(regIdElement);
267
+ partyElement.appendChild(regRegistrationElement);
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Adds delivery section with delivery information
273
+ * @param doc XML document
274
+ * @param transactionElement Transaction element
275
+ * @param invoice Invoice data
276
+ */
277
+ private addDeliverySection(doc: Document, transactionElement: Element, invoice: TInvoice): void {
278
+ // Create delivery element
279
+ const deliveryElement = doc.createElement('ram:ApplicableHeaderTradeDelivery');
280
+ transactionElement.appendChild(deliveryElement);
281
+
282
+ // Add delivery date if available
283
+ if (invoice.deliveryDate) {
284
+ const deliveryDateElement = doc.createElement('ram:ActualDeliverySupplyChainEvent');
285
+ const occurrenceDateElement = doc.createElement('ram:OccurrenceDateTime');
286
+ const dateStringElement = doc.createElement('udt:DateTimeString');
287
+ dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
288
+ dateStringElement.textContent = this.formatDateYYYYMMDD(invoice.deliveryDate);
289
+ occurrenceDateElement.appendChild(dateStringElement);
290
+ deliveryDateElement.appendChild(occurrenceDateElement);
291
+ deliveryElement.appendChild(deliveryDateElement);
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Adds settlement section with payment terms and totals
297
+ * @param doc XML document
298
+ * @param transactionElement Transaction element
299
+ * @param invoice Invoice data
300
+ */
301
+ private addSettlementSection(doc: Document, transactionElement: Element, invoice: TInvoice): void {
302
+ // Create settlement element
303
+ const settlementElement = doc.createElement('ram:ApplicableHeaderTradeSettlement');
304
+ transactionElement.appendChild(settlementElement);
305
+
306
+ // Add currency
307
+ const currencyElement = doc.createElement('ram:InvoiceCurrencyCode');
308
+ currencyElement.textContent = invoice.currency;
309
+ settlementElement.appendChild(currencyElement);
310
+
311
+ // Add payment terms
312
+ const paymentTermsElement = doc.createElement('ram:SpecifiedTradePaymentTerms');
313
+
314
+ // Add due date
315
+ const dueDateElement = doc.createElement('ram:DueDateDateTime');
316
+ const dateStringElement = doc.createElement('udt:DateTimeString');
317
+ dateStringElement.setAttribute('format', '102'); // YYYYMMDD format
318
+
319
+ // Calculate due date
320
+ const dueDate = new Date(invoice.date);
321
+ dueDate.setDate(dueDate.getDate() + invoice.dueInDays);
322
+
323
+ dateStringElement.textContent = this.formatDateYYYYMMDD(dueDate.getTime());
324
+ dueDateElement.appendChild(dateStringElement);
325
+ paymentTermsElement.appendChild(dueDateElement);
326
+
327
+ settlementElement.appendChild(paymentTermsElement);
328
+
329
+ // Add totals
330
+ const monetarySummationElement = doc.createElement('ram:SpecifiedTradeSettlementHeaderMonetarySummation');
331
+
332
+ // Calculate totals
333
+ let totalNetAmount = 0;
334
+ let totalTaxAmount = 0;
335
+
336
+ // Calculate from items
337
+ if (invoice.items) {
338
+ for (const item of invoice.items) {
339
+ const itemNetAmount = item.unitNetPrice * item.unitQuantity;
340
+ const itemTaxAmount = itemNetAmount * (item.vatPercentage / 100);
341
+
342
+ totalNetAmount += itemNetAmount;
343
+ totalTaxAmount += itemTaxAmount;
344
+ }
345
+ }
346
+
347
+ const totalGrossAmount = totalNetAmount + totalTaxAmount;
348
+
349
+ // Add line total amount
350
+ const lineTotalElement = doc.createElement('ram:LineTotalAmount');
351
+ lineTotalElement.textContent = totalNetAmount.toFixed(2);
352
+ monetarySummationElement.appendChild(lineTotalElement);
353
+
354
+ // Add tax total amount
355
+ const taxTotalElement = doc.createElement('ram:TaxTotalAmount');
356
+ taxTotalElement.textContent = totalTaxAmount.toFixed(2);
357
+ taxTotalElement.setAttribute('currencyID', invoice.currency);
358
+ monetarySummationElement.appendChild(taxTotalElement);
359
+
360
+ // Add grand total amount
361
+ const grandTotalElement = doc.createElement('ram:GrandTotalAmount');
362
+ grandTotalElement.textContent = totalGrossAmount.toFixed(2);
363
+ monetarySummationElement.appendChild(grandTotalElement);
364
+
365
+ // Add due payable amount
366
+ const duePayableElement = doc.createElement('ram:DuePayableAmount');
367
+ duePayableElement.textContent = totalGrossAmount.toFixed(2);
368
+ monetarySummationElement.appendChild(duePayableElement);
369
+
370
+ settlementElement.appendChild(monetarySummationElement);
371
+ }
372
+
373
+ /**
374
+ * Adds line items to the XML document
375
+ * @param doc XML document
376
+ * @param transactionElement Transaction element
377
+ * @param invoice Invoice data
378
+ */
379
+ private addLineItems(doc: Document, transactionElement: Element, invoice: TInvoice): void {
380
+ // Add each line item
381
+ if (invoice.items) {
382
+ for (const item of invoice.items) {
383
+ // Create line item element
384
+ const lineItemElement = doc.createElement('ram:IncludedSupplyChainTradeLineItem');
385
+
386
+ // Add line ID
387
+ const lineIdElement = doc.createElement('ram:AssociatedDocumentLineDocument');
388
+ const lineIdValueElement = doc.createElement('ram:LineID');
389
+ lineIdValueElement.textContent = item.position.toString();
390
+ lineIdElement.appendChild(lineIdValueElement);
391
+ lineItemElement.appendChild(lineIdElement);
392
+
393
+ // Add product information
394
+ const productElement = doc.createElement('ram:SpecifiedTradeProduct');
395
+
396
+ // Add name
397
+ const nameElement = doc.createElement('ram:Name');
398
+ nameElement.textContent = item.name;
399
+ productElement.appendChild(nameElement);
400
+
401
+ // Add article number if available
402
+ if (item.articleNumber) {
403
+ const articleNumberElement = doc.createElement('ram:SellerAssignedID');
404
+ articleNumberElement.textContent = item.articleNumber;
405
+ productElement.appendChild(articleNumberElement);
406
+ }
407
+
408
+ lineItemElement.appendChild(productElement);
409
+
410
+ // Add agreement information (price)
411
+ const agreementElement = doc.createElement('ram:SpecifiedLineTradeAgreement');
412
+ const priceElement = doc.createElement('ram:NetPriceProductTradePrice');
413
+ const chargeAmountElement = doc.createElement('ram:ChargeAmount');
414
+ chargeAmountElement.textContent = item.unitNetPrice.toFixed(2);
415
+ priceElement.appendChild(chargeAmountElement);
416
+ agreementElement.appendChild(priceElement);
417
+ lineItemElement.appendChild(agreementElement);
418
+
419
+ // Add delivery information (quantity)
420
+ const deliveryElement = doc.createElement('ram:SpecifiedLineTradeDelivery');
421
+ const quantityElement = doc.createElement('ram:BilledQuantity');
422
+ quantityElement.textContent = item.unitQuantity.toString();
423
+ quantityElement.setAttribute('unitCode', item.unitType);
424
+ deliveryElement.appendChild(quantityElement);
425
+ lineItemElement.appendChild(deliveryElement);
426
+
427
+ // Add settlement information (tax)
428
+ const settlementElement = doc.createElement('ram:SpecifiedLineTradeSettlement');
429
+
430
+ // Add tax information
431
+ const taxElement = doc.createElement('ram:ApplicableTradeTax');
432
+
433
+ // Add tax type code
434
+ const taxTypeCodeElement = doc.createElement('ram:TypeCode');
435
+ taxTypeCodeElement.textContent = 'VAT';
436
+ taxElement.appendChild(taxTypeCodeElement);
437
+
438
+ // Add tax category code
439
+ const taxCategoryCodeElement = doc.createElement('ram:CategoryCode');
440
+ taxCategoryCodeElement.textContent = 'S';
441
+ taxElement.appendChild(taxCategoryCodeElement);
442
+
443
+ // Add tax rate
444
+ const taxRateElement = doc.createElement('ram:RateApplicablePercent');
445
+ taxRateElement.textContent = item.vatPercentage.toString();
446
+ taxElement.appendChild(taxRateElement);
447
+
448
+ settlementElement.appendChild(taxElement);
449
+
450
+ // Add monetary summation
451
+ const monetarySummationElement = doc.createElement('ram:SpecifiedLineTradeSettlementMonetarySummation');
452
+
453
+ // Calculate item total
454
+ const itemNetAmount = item.unitNetPrice * item.unitQuantity;
455
+
456
+ // Add line total amount
457
+ const lineTotalElement = doc.createElement('ram:LineTotalAmount');
458
+ lineTotalElement.textContent = itemNetAmount.toFixed(2);
459
+ monetarySummationElement.appendChild(lineTotalElement);
460
+
461
+ settlementElement.appendChild(monetarySummationElement);
462
+
463
+ lineItemElement.appendChild(settlementElement);
464
+
465
+ // Add line item to transaction
466
+ transactionElement.appendChild(lineItemElement);
467
+ }
468
+ }
469
+ }
470
+
471
+ /**
472
+ * Formats a date as YYYYMMDD
473
+ * @param timestamp Timestamp to format
474
+ * @returns Formatted date string
475
+ */
476
+ private formatDateYYYYMMDD(timestamp: number): string {
477
+ const date = new Date(timestamp);
478
+ const year = date.getFullYear();
479
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
480
+ const day = date.getDate().toString().padStart(2, '0');
481
+ return `${year}${month}${day}`;
482
+ }
483
+ }
@@ -0,0 +1,18 @@
1
+ import { CIIProfile, CII_PROFILE_IDS } from '../cii.types.js';
2
+
3
+ /**
4
+ * Factur-X specific constants and types
5
+ */
6
+
7
+ // Factur-X profile IDs
8
+ export const FACTURX_PROFILE_IDS = {
9
+ MINIMUM: CII_PROFILE_IDS.FACTURX_MINIMUM,
10
+ BASIC: CII_PROFILE_IDS.FACTURX_BASIC,
11
+ EN16931: CII_PROFILE_IDS.FACTURX_EN16931
12
+ };
13
+
14
+ // Factur-X PDF attachment filename
15
+ export const FACTURX_ATTACHMENT_FILENAME = 'factur-x.xml';
16
+
17
+ // Factur-X PDF attachment description
18
+ export const FACTURX_ATTACHMENT_DESCRIPTION = 'Factur-X XML Invoice';
@@ -0,0 +1,180 @@
1
+ import { CIIBaseValidator } from '../cii.validator.js';
2
+ import { ValidationLevel } from '../../../interfaces/common.js';
3
+ import type { ValidationResult } from '../../../interfaces/common.js';
4
+
5
+ /**
6
+ * Validator for Factur-X invoice format
7
+ * Implements validation rules according to EN16931 and Factur-X specification
8
+ */
9
+ export class FacturXValidator extends CIIBaseValidator {
10
+ /**
11
+ * Validates structure of the Factur-X XML document
12
+ * @returns True if structure validation passed
13
+ */
14
+ protected validateStructure(): boolean {
15
+ if (!this.doc) return false;
16
+
17
+ let valid = true;
18
+
19
+ // Check for required main sections
20
+ const sections = [
21
+ 'rsm:ExchangedDocumentContext',
22
+ 'rsm:ExchangedDocument',
23
+ 'rsm:SupplyChainTradeTransaction'
24
+ ];
25
+
26
+ for (const section of sections) {
27
+ if (!this.exists(section)) {
28
+ this.addError('FX-STRUCT-1', `Required section ${section} is missing`, '/rsm:CrossIndustryInvoice');
29
+ valid = false;
30
+ }
31
+ }
32
+
33
+ // Check for SupplyChainTradeTransaction sections
34
+ if (this.exists('rsm:SupplyChainTradeTransaction')) {
35
+ const tradeSubsections = [
36
+ 'ram:ApplicableHeaderTradeAgreement',
37
+ 'ram:ApplicableHeaderTradeDelivery',
38
+ 'ram:ApplicableHeaderTradeSettlement'
39
+ ];
40
+
41
+ for (const subsection of tradeSubsections) {
42
+ if (!this.exists(`rsm:SupplyChainTradeTransaction/${subsection}`)) {
43
+ this.addError('FX-STRUCT-2', `Required subsection ${subsection} is missing`,
44
+ '/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction');
45
+ valid = false;
46
+ }
47
+ }
48
+ }
49
+
50
+ return valid;
51
+ }
52
+
53
+ /**
54
+ * Validates business rules
55
+ * @returns True if business rule validation passed
56
+ */
57
+ protected validateBusinessRules(): boolean {
58
+ if (!this.doc) return false;
59
+
60
+ let valid = true;
61
+
62
+ // BR-16: Amount due for payment (BT-115) = Invoice total amount with VAT (BT-112) - Paid amount (BT-113)
63
+ valid = this.validateAmounts() && valid;
64
+
65
+ // BR-CO-3: Value added tax point date (BT-7) and Value added tax point date code (BT-8) are mutually exclusive
66
+ valid = this.validateMutuallyExclusiveFields() && valid;
67
+
68
+ // BR-S-1: An Invoice that contains a line (BG-25) where the Invoiced item VAT category code (BT-151) is "Standard rated"
69
+ // shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32)
70
+ // and/or the Seller tax representative VAT identifier (BT-63).
71
+ valid = this.validateSellerVatIdentifier() && valid;
72
+
73
+ return valid;
74
+ }
75
+
76
+ /**
77
+ * Validates amount calculations in the invoice
78
+ * @returns True if amount validation passed
79
+ */
80
+ private validateAmounts(): boolean {
81
+ if (!this.doc) return false;
82
+
83
+ try {
84
+ // Extract amounts
85
+ const totalAmount = this.getNumber(
86
+ '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount'
87
+ );
88
+
89
+ const paidAmount = this.getNumber(
90
+ '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TotalPrepaidAmount'
91
+ ) || 0;
92
+
93
+ const dueAmount = this.getNumber(
94
+ '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:DuePayableAmount'
95
+ );
96
+
97
+ // Calculate expected due amount
98
+ const expectedDueAmount = totalAmount - paidAmount;
99
+
100
+ // Compare with a small tolerance for rounding errors
101
+ if (Math.abs(dueAmount - expectedDueAmount) > 0.01) {
102
+ this.addError(
103
+ 'BR-16',
104
+ `Amount due for payment (${dueAmount}) must equal Invoice total amount with VAT (${totalAmount}) - Paid amount (${paidAmount})`,
105
+ '//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation'
106
+ );
107
+ return false;
108
+ }
109
+
110
+ return true;
111
+ } catch (error) {
112
+ this.addError('FX-AMOUNT', `Error validating amounts: ${error}`, '/');
113
+ return false;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Validates mutually exclusive fields
119
+ * @returns True if validation passed
120
+ */
121
+ private validateMutuallyExclusiveFields(): boolean {
122
+ if (!this.doc) return false;
123
+
124
+ try {
125
+ // Check for VAT point date and code (BR-CO-3)
126
+ const vatPointDate = this.exists('//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax/ram:TaxPointDate');
127
+ const vatPointDateCode = this.exists('//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax/ram:DueDateTypeCode');
128
+
129
+ if (vatPointDate && vatPointDateCode) {
130
+ this.addError(
131
+ 'BR-CO-3',
132
+ 'Value added tax point date and Value added tax point date code are mutually exclusive',
133
+ '//ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax'
134
+ );
135
+ return false;
136
+ }
137
+
138
+ return true;
139
+ } catch (error) {
140
+ this.addError('FX-MUTUAL', `Error validating mutually exclusive fields: ${error}`, '/');
141
+ return false;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Validates seller VAT identifier requirements
147
+ * @returns True if validation passed
148
+ */
149
+ private validateSellerVatIdentifier(): boolean {
150
+ if (!this.doc) return false;
151
+
152
+ try {
153
+ // Check if there are any standard rated line items
154
+ const standardRatedItems = this.exists(
155
+ '//ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:CategoryCode[text()="S"]'
156
+ );
157
+
158
+ if (standardRatedItems) {
159
+ // Check for seller VAT identifier
160
+ const sellerVatId = this.exists('//ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]');
161
+ const sellerTaxId = this.exists('//ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]');
162
+ const sellerTaxRepId = this.exists('//ram:ApplicableHeaderTradeAgreement/ram:SellerTaxRepresentativeTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]');
163
+
164
+ if (!sellerVatId && !sellerTaxId && !sellerTaxRepId) {
165
+ this.addError(
166
+ 'BR-S-1',
167
+ 'An Invoice with standard rated items must contain the Seller VAT Identifier, Tax registration identifier or Tax representative VAT identifier',
168
+ '//ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty'
169
+ );
170
+ return false;
171
+ }
172
+ }
173
+
174
+ return true;
175
+ } catch (error) {
176
+ this.addError('FX-VAT', `Error validating seller VAT identifier: ${error}`, '/');
177
+ return false;
178
+ }
179
+ }
180
+ }