@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,619 @@
1
+ import { UBLEncoder } from '../generic/ubl.encoder.js';
2
+ import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js';
3
+ import { DOMParser, XMLSerializer } from '../../../plugins.js';
4
+
5
+ /**
6
+ * Encoder for XRechnung (UBL) format
7
+ * Extends the generic UBL encoder with XRechnung-specific customizations
8
+ */
9
+ export class XRechnungEncoder extends UBLEncoder {
10
+ /**
11
+ * Encodes a credit note into XRechnung XML
12
+ * @param creditNote Credit note to encode
13
+ * @returns XRechnung XML string
14
+ */
15
+ protected async encodeCreditNote(creditNote: TCreditNote): Promise<string> {
16
+ // First get the base UBL XML
17
+ const baseXml = await super.encodeCreditNote(creditNote);
18
+
19
+ // Parse and modify for XRechnung
20
+ const doc = new DOMParser().parseFromString(baseXml, 'application/xml');
21
+ this.applyXRechnungCustomizations(doc, creditNote as unknown as TInvoice);
22
+
23
+ // Serialize back to string
24
+ return new XMLSerializer().serializeToString(doc);
25
+ }
26
+
27
+ /**
28
+ * Encodes a debit note (invoice) into XRechnung XML
29
+ * @param debitNote Debit note to encode
30
+ * @returns XRechnung XML string
31
+ */
32
+ protected async encodeDebitNote(debitNote: TDebitNote): Promise<string> {
33
+ // First get the base UBL XML
34
+ const baseXml = await super.encodeDebitNote(debitNote);
35
+
36
+ // Parse and modify for XRechnung
37
+ const doc = new DOMParser().parseFromString(baseXml, 'application/xml');
38
+ this.applyXRechnungCustomizations(doc, debitNote as unknown as TInvoice);
39
+
40
+ // Serialize back to string
41
+ return new XMLSerializer().serializeToString(doc);
42
+ }
43
+
44
+ /**
45
+ * Applies XRechnung-specific customizations to the document
46
+ * @param doc XML document
47
+ * @param invoice Invoice data
48
+ */
49
+ private applyXRechnungCustomizations(doc: Document, invoice: TInvoice): void {
50
+ const root = doc.documentElement;
51
+
52
+ // Extract metadata if available
53
+ const metadata = (invoice as any).metadata?.extensions;
54
+
55
+ // Update Customization ID to XRechnung 2.0
56
+ const customizationId = root.getElementsByTagName('cbc:CustomizationID')[0];
57
+ if (customizationId) {
58
+ customizationId.textContent = 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0';
59
+ }
60
+
61
+ // Add or update Buyer Reference (required for XRechnung)
62
+ let buyerRef = root.getElementsByTagName('cbc:BuyerReference')[0];
63
+ const buyerReferenceValue = (invoice as any).buyerReference || metadata?.businessReferences?.buyerReference || invoice.id;
64
+ if (!buyerRef) {
65
+ // Find where to insert it (after DocumentCurrencyCode)
66
+ const currencyCode = root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
67
+ if (currencyCode) {
68
+ buyerRef = doc.createElement('cbc:BuyerReference');
69
+ buyerRef.textContent = buyerReferenceValue;
70
+ currencyCode.parentNode!.insertBefore(buyerRef, currencyCode.nextSibling);
71
+ }
72
+ } else if (!buyerRef.textContent || buyerRef.textContent.trim() === '') {
73
+ buyerRef.textContent = buyerReferenceValue;
74
+ }
75
+
76
+ // Update payment terms to German
77
+ const paymentTermsNotes = root.getElementsByTagName('cac:PaymentTerms');
78
+ if (paymentTermsNotes.length > 0) {
79
+ const noteElement = paymentTermsNotes[0].getElementsByTagName('cbc:Note')[0];
80
+ if (noteElement && noteElement.textContent) {
81
+ noteElement.textContent = `Zahlung innerhalb von ${invoice.dueInDays || 30} Tagen`;
82
+ }
83
+ }
84
+
85
+ // Add electronic address for parties if available
86
+ this.addElectronicAddressToParty(doc, 'cac:AccountingSupplierParty', invoice.from);
87
+ this.addElectronicAddressToParty(doc, 'cac:AccountingCustomerParty', invoice.to);
88
+
89
+ // Ensure payment reference is set
90
+ const paymentMeans = root.getElementsByTagName('cac:PaymentMeans')[0];
91
+ if (paymentMeans) {
92
+ let paymentId = paymentMeans.getElementsByTagName('cbc:PaymentID')[0];
93
+ if (!paymentId) {
94
+ paymentId = doc.createElement('cbc:PaymentID');
95
+ paymentId.textContent = invoice.id;
96
+ paymentMeans.appendChild(paymentId);
97
+ }
98
+ }
99
+
100
+ // Add country code handling for German addresses
101
+ this.fixGermanCountryCodes(doc);
102
+
103
+ // Preserve business references from metadata
104
+ this.addBusinessReferences(doc, metadata?.businessReferences);
105
+
106
+ // Preserve payment information from metadata
107
+ this.enhancePaymentInformation(doc, metadata?.paymentInformation);
108
+
109
+ // Preserve date information from metadata
110
+ this.addDateInformation(doc, metadata?.dateInformation);
111
+
112
+ // Enhance party information with contact details
113
+ this.enhancePartyInformation(doc, invoice);
114
+
115
+ // Enhance line items with metadata
116
+ this.enhanceLineItems(doc, invoice);
117
+ }
118
+
119
+ /**
120
+ * Adds electronic address to party if not already present
121
+ * @param doc XML document
122
+ * @param partyType Party type selector
123
+ * @param party Party data
124
+ */
125
+ private addElectronicAddressToParty(doc: Document, partyType: string, party: any): void {
126
+ const partyContainer = doc.getElementsByTagName(partyType)[0];
127
+ if (!partyContainer) return;
128
+
129
+ const partyElement = partyContainer.getElementsByTagName('cac:Party')[0];
130
+ if (!partyElement) return;
131
+
132
+ // Check if electronic address already exists
133
+ const existingEndpoint = partyElement.getElementsByTagName('cbc:EndpointID')[0];
134
+ if (!existingEndpoint && party.electronicAddress) {
135
+ // Add electronic address at the beginning of party element
136
+ const endpointNode = doc.createElement('cbc:EndpointID');
137
+ endpointNode.setAttribute('schemeID', party.electronicAddress.scheme || '0204');
138
+ endpointNode.textContent = party.electronicAddress.value;
139
+
140
+ // Insert as first child of party element
141
+ if (partyElement.firstChild) {
142
+ partyElement.insertBefore(endpointNode, partyElement.firstChild);
143
+ } else {
144
+ partyElement.appendChild(endpointNode);
145
+ }
146
+ }
147
+
148
+ // Add GLN (Global Location Number) if available
149
+ if (party.gln && !existingEndpoint) {
150
+ const endpointNode = doc.createElement('cbc:EndpointID');
151
+ endpointNode.setAttribute('schemeID', '0088'); // GLN scheme ID
152
+ endpointNode.textContent = party.gln;
153
+
154
+ // Insert as first child of party element
155
+ if (partyElement.firstChild) {
156
+ partyElement.insertBefore(endpointNode, partyElement.firstChild);
157
+ } else {
158
+ partyElement.appendChild(endpointNode);
159
+ }
160
+ }
161
+
162
+ // Add PartyIdentification for additional identifiers
163
+ if (party.additionalIdentifiers) {
164
+ for (const identifier of party.additionalIdentifiers) {
165
+ const partyId = doc.createElement('cac:PartyIdentification');
166
+ const id = doc.createElement('cbc:ID');
167
+ if (identifier.scheme) {
168
+ id.setAttribute('schemeID', identifier.scheme);
169
+ }
170
+ id.textContent = identifier.value;
171
+ partyId.appendChild(id);
172
+
173
+ // Insert after EndpointID or at beginning
174
+ const endpoint = partyElement.getElementsByTagName('cbc:EndpointID')[0];
175
+ if (endpoint && endpoint.nextSibling) {
176
+ partyElement.insertBefore(partyId, endpoint.nextSibling);
177
+ } else if (partyElement.firstChild) {
178
+ partyElement.insertBefore(partyId, partyElement.firstChild);
179
+ } else {
180
+ partyElement.appendChild(partyId);
181
+ }
182
+ }
183
+ }
184
+
185
+ // Add company registration number to PartyLegalEntity
186
+ if (party.registrationDetails?.registrationId) {
187
+ let legalEntity = partyElement.getElementsByTagName('cac:PartyLegalEntity')[0];
188
+ if (!legalEntity) {
189
+ legalEntity = doc.createElement('cac:PartyLegalEntity');
190
+ // Insert after PostalAddress
191
+ const postalAddress = partyElement.getElementsByTagName('cac:PostalAddress')[0];
192
+ if (postalAddress && postalAddress.nextSibling) {
193
+ partyElement.insertBefore(legalEntity, postalAddress.nextSibling);
194
+ } else {
195
+ partyElement.appendChild(legalEntity);
196
+ }
197
+ }
198
+
199
+ // Add registration name if not present
200
+ if (!legalEntity.getElementsByTagName('cbc:RegistrationName')[0]) {
201
+ const regName = doc.createElement('cbc:RegistrationName');
202
+ regName.textContent = party.registrationDetails.registrationName || party.name;
203
+ legalEntity.appendChild(regName);
204
+ }
205
+
206
+ // Add company ID if not present
207
+ if (!legalEntity.getElementsByTagName('cbc:CompanyID')[0]) {
208
+ const companyId = doc.createElement('cbc:CompanyID');
209
+ companyId.textContent = party.registrationDetails.registrationId;
210
+ legalEntity.appendChild(companyId);
211
+ }
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Fixes German country codes in the document
217
+ * @param doc XML document
218
+ */
219
+ private fixGermanCountryCodes(doc: Document): void {
220
+ const countryNodes = doc.getElementsByTagName('cbc:IdentificationCode');
221
+ for (let i = 0; i < countryNodes.length; i++) {
222
+ const node = countryNodes[i];
223
+ if (node.textContent) {
224
+ const text = node.textContent.toLowerCase();
225
+ if (text === 'germany' || text === 'deutschland' || text === 'de') {
226
+ node.textContent = 'DE';
227
+ } else if (text.length > 2) {
228
+ // Try to use first 2 characters as country code
229
+ node.textContent = text.substring(0, 2).toUpperCase();
230
+ }
231
+ }
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Adds business references from metadata to the document
237
+ * @param doc XML document
238
+ * @param businessReferences Business references from metadata
239
+ */
240
+ private addBusinessReferences(doc: Document, businessReferences?: any): void {
241
+ if (!businessReferences) return;
242
+
243
+ const root = doc.documentElement;
244
+
245
+ // Add OrderReference
246
+ if (businessReferences.orderReference && !root.getElementsByTagName('cac:OrderReference')[0]) {
247
+ const orderRef = doc.createElement('cac:OrderReference');
248
+ const orderId = doc.createElement('cbc:ID');
249
+ orderId.textContent = businessReferences.orderReference;
250
+ orderRef.appendChild(orderId);
251
+
252
+ // Insert after DocumentCurrencyCode
253
+ const currencyCode = root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
254
+ if (currencyCode && currencyCode.parentNode) {
255
+ currencyCode.parentNode.insertBefore(orderRef, currencyCode.nextSibling);
256
+ }
257
+ }
258
+
259
+ // Add ContractDocumentReference
260
+ if (businessReferences.contractReference && !root.getElementsByTagName('cac:ContractDocumentReference')[0]) {
261
+ const contractRef = doc.createElement('cac:ContractDocumentReference');
262
+ const contractId = doc.createElement('cbc:ID');
263
+ contractId.textContent = businessReferences.contractReference;
264
+ contractRef.appendChild(contractId);
265
+
266
+ // Insert after OrderReference or DocumentCurrencyCode
267
+ const orderRef = root.getElementsByTagName('cac:OrderReference')[0];
268
+ const insertAfter = orderRef || root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
269
+ if (insertAfter && insertAfter.parentNode) {
270
+ insertAfter.parentNode.insertBefore(contractRef, insertAfter.nextSibling);
271
+ }
272
+ }
273
+
274
+ // Add ProjectReference
275
+ if (businessReferences.projectReference && !root.getElementsByTagName('cac:ProjectReference')[0]) {
276
+ const projectRef = doc.createElement('cac:ProjectReference');
277
+ const projectId = doc.createElement('cbc:ID');
278
+ projectId.textContent = businessReferences.projectReference;
279
+ projectRef.appendChild(projectId);
280
+
281
+ // Insert after ContractDocumentReference or other refs
282
+ const contractRef = root.getElementsByTagName('cac:ContractDocumentReference')[0];
283
+ const orderRef = root.getElementsByTagName('cac:OrderReference')[0];
284
+ const insertAfter = contractRef || orderRef || root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
285
+ if (insertAfter && insertAfter.parentNode) {
286
+ insertAfter.parentNode.insertBefore(projectRef, insertAfter.nextSibling);
287
+ }
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Enhances payment information from metadata
293
+ * @param doc XML document
294
+ * @param paymentInfo Payment information from metadata
295
+ */
296
+ private enhancePaymentInformation(doc: Document, paymentInfo?: any): void {
297
+ if (!paymentInfo) return;
298
+
299
+ const root = doc.documentElement;
300
+ let paymentMeans = root.getElementsByTagName('cac:PaymentMeans')[0];
301
+
302
+ // Create PaymentMeans if it doesn't exist
303
+ if (!paymentMeans) {
304
+ paymentMeans = doc.createElement('cac:PaymentMeans');
305
+ // Insert before TaxTotal
306
+ const taxTotal = root.getElementsByTagName('cac:TaxTotal')[0];
307
+ if (taxTotal && taxTotal.parentNode) {
308
+ taxTotal.parentNode.insertBefore(paymentMeans, taxTotal);
309
+ }
310
+ }
311
+
312
+ // Add PaymentMeansCode
313
+ if (paymentInfo.paymentMeansCode && !paymentMeans.getElementsByTagName('cbc:PaymentMeansCode')[0]) {
314
+ const meansCode = doc.createElement('cbc:PaymentMeansCode');
315
+ meansCode.textContent = paymentInfo.paymentMeansCode;
316
+ paymentMeans.appendChild(meansCode);
317
+ }
318
+
319
+ // Add PaymentID
320
+ if (paymentInfo.paymentID && !paymentMeans.getElementsByTagName('cbc:PaymentID')[0]) {
321
+ const paymentId = doc.createElement('cbc:PaymentID');
322
+ paymentId.textContent = paymentInfo.paymentID;
323
+ paymentMeans.appendChild(paymentId);
324
+ }
325
+
326
+ // Add PaymentDueDate
327
+ if (paymentInfo.paymentDueDate && !paymentMeans.getElementsByTagName('cbc:PaymentDueDate')[0]) {
328
+ const dueDate = doc.createElement('cbc:PaymentDueDate');
329
+ dueDate.textContent = paymentInfo.paymentDueDate;
330
+ paymentMeans.appendChild(dueDate);
331
+ }
332
+
333
+ // Add IBAN and BIC
334
+ if (paymentInfo.iban || paymentInfo.bic) {
335
+ let payeeAccount = paymentMeans.getElementsByTagName('cac:PayeeFinancialAccount')[0];
336
+ if (!payeeAccount) {
337
+ payeeAccount = doc.createElement('cac:PayeeFinancialAccount');
338
+ paymentMeans.appendChild(payeeAccount);
339
+ }
340
+
341
+ // Add IBAN
342
+ if (paymentInfo.iban && !payeeAccount.getElementsByTagName('cbc:ID')[0]) {
343
+ const iban = doc.createElement('cbc:ID');
344
+ iban.textContent = paymentInfo.iban;
345
+ payeeAccount.appendChild(iban);
346
+ }
347
+
348
+ // Add account name (must come after ID but before FinancialInstitutionBranch)
349
+ if (paymentInfo.accountName && !payeeAccount.getElementsByTagName('cbc:Name')[0]) {
350
+ const accountName = doc.createElement('cbc:Name');
351
+ accountName.textContent = paymentInfo.accountName;
352
+ // Insert after ID but before FinancialInstitutionBranch
353
+ const id = payeeAccount.getElementsByTagName('cbc:ID')[0];
354
+ const finInstBranch = payeeAccount.getElementsByTagName('cac:FinancialInstitutionBranch')[0];
355
+ if (finInstBranch) {
356
+ payeeAccount.insertBefore(accountName, finInstBranch);
357
+ } else if (id && id.nextSibling) {
358
+ payeeAccount.insertBefore(accountName, id.nextSibling);
359
+ } else {
360
+ payeeAccount.appendChild(accountName);
361
+ }
362
+ }
363
+
364
+ // Add BIC and bank name
365
+ if (paymentInfo.bic || paymentInfo.bankName) {
366
+ let finInstBranch = payeeAccount.getElementsByTagName('cac:FinancialInstitutionBranch')[0];
367
+ if (!finInstBranch) {
368
+ finInstBranch = doc.createElement('cac:FinancialInstitutionBranch');
369
+ payeeAccount.appendChild(finInstBranch);
370
+ }
371
+
372
+ // Add BIC as branch ID
373
+ if (paymentInfo.bic && !finInstBranch.getElementsByTagName('cbc:ID')[0]) {
374
+ const bicElement = doc.createElement('cbc:ID');
375
+ bicElement.textContent = paymentInfo.bic;
376
+ finInstBranch.appendChild(bicElement);
377
+ }
378
+
379
+ // Add bank name
380
+ if (paymentInfo.bankName && !finInstBranch.getElementsByTagName('cbc:Name')[0]) {
381
+ const bankNameElement = doc.createElement('cbc:Name');
382
+ bankNameElement.textContent = paymentInfo.bankName;
383
+ finInstBranch.appendChild(bankNameElement);
384
+ }
385
+ }
386
+ }
387
+
388
+ // Add payment terms with discount if available
389
+ if (paymentInfo.paymentTermsNote && paymentInfo.paymentTermsNote.includes('early payment')) {
390
+ let paymentTerms = root.getElementsByTagName('cac:PaymentTerms')[0];
391
+ if (!paymentTerms) {
392
+ paymentTerms = doc.createElement('cac:PaymentTerms');
393
+ // Insert before PaymentMeans
394
+ const paymentMeans = root.getElementsByTagName('cac:PaymentMeans')[0];
395
+ if (paymentMeans && paymentMeans.parentNode) {
396
+ paymentMeans.parentNode.insertBefore(paymentTerms, paymentMeans);
397
+ }
398
+ }
399
+
400
+ // Update or add note
401
+ let note = paymentTerms.getElementsByTagName('cbc:Note')[0];
402
+ if (!note) {
403
+ note = doc.createElement('cbc:Note');
404
+ paymentTerms.appendChild(note);
405
+ }
406
+ note.textContent = paymentInfo.paymentTermsNote;
407
+
408
+ // Add discount percent if available
409
+ if (paymentInfo.discountPercent && !paymentTerms.getElementsByTagName('cbc:SettlementDiscountPercent')[0]) {
410
+ const discountElement = doc.createElement('cbc:SettlementDiscountPercent');
411
+ discountElement.textContent = paymentInfo.discountPercent;
412
+ paymentTerms.appendChild(discountElement);
413
+ }
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Adds date information from metadata
419
+ * @param doc XML document
420
+ * @param dateInfo Date information from metadata
421
+ */
422
+ private addDateInformation(doc: Document, dateInfo?: any): void {
423
+ if (!dateInfo) return;
424
+
425
+ const root = doc.documentElement;
426
+
427
+ // Add InvoicePeriod
428
+ if ((dateInfo.periodStart || dateInfo.periodEnd) && !root.getElementsByTagName('cac:InvoicePeriod')[0]) {
429
+ const invoicePeriod = doc.createElement('cac:InvoicePeriod');
430
+
431
+ if (dateInfo.periodStart) {
432
+ const startDate = doc.createElement('cbc:StartDate');
433
+ startDate.textContent = dateInfo.periodStart;
434
+ invoicePeriod.appendChild(startDate);
435
+ }
436
+
437
+ if (dateInfo.periodEnd) {
438
+ const endDate = doc.createElement('cbc:EndDate');
439
+ endDate.textContent = dateInfo.periodEnd;
440
+ invoicePeriod.appendChild(endDate);
441
+ }
442
+
443
+ // Insert after business references or DocumentCurrencyCode
444
+ const projectRef = root.getElementsByTagName('cac:ProjectReference')[0];
445
+ const contractRef = root.getElementsByTagName('cac:ContractDocumentReference')[0];
446
+ const orderRef = root.getElementsByTagName('cac:OrderReference')[0];
447
+ const insertAfter = projectRef || contractRef || orderRef || root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
448
+ if (insertAfter && insertAfter.parentNode) {
449
+ insertAfter.parentNode.insertBefore(invoicePeriod, insertAfter.nextSibling);
450
+ }
451
+ }
452
+
453
+ // Add Delivery with ActualDeliveryDate
454
+ if (dateInfo.deliveryDate && !root.getElementsByTagName('cac:Delivery')[0]) {
455
+ const delivery = doc.createElement('cac:Delivery');
456
+ const deliveryDate = doc.createElement('cbc:ActualDeliveryDate');
457
+ deliveryDate.textContent = dateInfo.deliveryDate;
458
+ delivery.appendChild(deliveryDate);
459
+
460
+ // Insert before PaymentMeans
461
+ const paymentMeans = root.getElementsByTagName('cac:PaymentMeans')[0];
462
+ if (paymentMeans && paymentMeans.parentNode) {
463
+ paymentMeans.parentNode.insertBefore(delivery, paymentMeans);
464
+ }
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Enhances party information with contact details from metadata
470
+ * @param doc XML document
471
+ * @param invoice Invoice data
472
+ */
473
+ private enhancePartyInformation(doc: Document, invoice: TInvoice): void {
474
+ // Enhance supplier party
475
+ this.addContactToParty(doc, 'cac:AccountingSupplierParty', (invoice.from as any)?.metadata?.contactInformation);
476
+
477
+ // Enhance customer party
478
+ this.addContactToParty(doc, 'cac:AccountingCustomerParty', (invoice.to as any)?.metadata?.contactInformation);
479
+ }
480
+
481
+ /**
482
+ * Adds contact information to a party
483
+ * @param doc XML document
484
+ * @param partySelector Party selector
485
+ * @param contactInfo Contact information from metadata
486
+ */
487
+ private addContactToParty(doc: Document, partySelector: string, contactInfo?: any): void {
488
+ if (!contactInfo) return;
489
+
490
+ const partyContainer = doc.getElementsByTagName(partySelector)[0];
491
+ if (!partyContainer) return;
492
+
493
+ const party = partyContainer.getElementsByTagName('cac:Party')[0];
494
+ if (!party) return;
495
+
496
+ // Check if Contact already exists
497
+ let contact = party.getElementsByTagName('cac:Contact')[0];
498
+ if (!contact && (contactInfo.name || contactInfo.phone || contactInfo.email)) {
499
+ contact = doc.createElement('cac:Contact');
500
+
501
+ // Insert after PartyName
502
+ const partyName = party.getElementsByTagName('cac:PartyName')[0];
503
+ if (partyName && partyName.parentNode) {
504
+ partyName.parentNode.insertBefore(contact, partyName.nextSibling);
505
+ } else {
506
+ party.appendChild(contact);
507
+ }
508
+ }
509
+
510
+ if (contact) {
511
+ // Add contact name
512
+ if (contactInfo.name && !contact.getElementsByTagName('cbc:Name')[0]) {
513
+ const name = doc.createElement('cbc:Name');
514
+ name.textContent = contactInfo.name;
515
+ contact.appendChild(name);
516
+ }
517
+
518
+ // Add telephone
519
+ if (contactInfo.phone && !contact.getElementsByTagName('cbc:Telephone')[0]) {
520
+ const phone = doc.createElement('cbc:Telephone');
521
+ phone.textContent = contactInfo.phone;
522
+ contact.appendChild(phone);
523
+ }
524
+
525
+ // Add email
526
+ if (contactInfo.email && !contact.getElementsByTagName('cbc:ElectronicMail')[0]) {
527
+ const email = doc.createElement('cbc:ElectronicMail');
528
+ email.textContent = contactInfo.email;
529
+ contact.appendChild(email);
530
+ }
531
+ }
532
+ }
533
+
534
+ /**
535
+ * Enhances line items with metadata
536
+ * @param doc XML document
537
+ * @param invoice Invoice data
538
+ */
539
+ private enhanceLineItems(doc: Document, invoice: TInvoice): void {
540
+ const invoiceLines = doc.getElementsByTagName('cac:InvoiceLine');
541
+
542
+ for (let i = 0; i < invoiceLines.length && i < invoice.items.length; i++) {
543
+ const line = invoiceLines[i];
544
+ const item = invoice.items[i];
545
+ const itemMetadata = (item as any).metadata;
546
+
547
+ if (!itemMetadata) continue;
548
+
549
+ const itemElement = line.getElementsByTagName('cac:Item')[0];
550
+ if (!itemElement) continue;
551
+
552
+ // Add item description
553
+ if (itemMetadata.description && !itemElement.getElementsByTagName('cbc:Description')[0]) {
554
+ const desc = doc.createElement('cbc:Description');
555
+ desc.textContent = itemMetadata.description;
556
+ // Insert before Name
557
+ const name = itemElement.getElementsByTagName('cbc:Name')[0];
558
+ if (name && name.parentNode) {
559
+ name.parentNode.insertBefore(desc, name);
560
+ } else {
561
+ itemElement.appendChild(desc);
562
+ }
563
+ }
564
+
565
+ // Add SellersItemIdentification
566
+ if (itemMetadata.buyerItemID && !itemElement.getElementsByTagName('cac:SellersItemIdentification')[0]) {
567
+ const sellerId = doc.createElement('cac:SellersItemIdentification');
568
+ const id = doc.createElement('cbc:ID');
569
+ id.textContent = item.articleNumber || itemMetadata.buyerItemID;
570
+ sellerId.appendChild(id);
571
+ itemElement.appendChild(sellerId);
572
+ }
573
+
574
+ // Add BuyersItemIdentification
575
+ if (itemMetadata.buyerItemID && !itemElement.getElementsByTagName('cac:BuyersItemIdentification')[0]) {
576
+ const buyerId = doc.createElement('cac:BuyersItemIdentification');
577
+ const id = doc.createElement('cbc:ID');
578
+ id.textContent = itemMetadata.buyerItemID;
579
+ buyerId.appendChild(id);
580
+ itemElement.appendChild(buyerId);
581
+ }
582
+
583
+ // Add StandardItemIdentification
584
+ if (itemMetadata.standardItemID && !itemElement.getElementsByTagName('cac:StandardItemIdentification')[0]) {
585
+ const standardId = doc.createElement('cac:StandardItemIdentification');
586
+ const id = doc.createElement('cbc:ID');
587
+ id.textContent = itemMetadata.standardItemID;
588
+ standardId.appendChild(id);
589
+ itemElement.appendChild(standardId);
590
+ }
591
+
592
+ // Add CommodityClassification
593
+ if (itemMetadata.commodityClassification && !itemElement.getElementsByTagName('cac:CommodityClassification')[0]) {
594
+ const classification = doc.createElement('cac:CommodityClassification');
595
+ const code = doc.createElement('cbc:ItemClassificationCode');
596
+ code.textContent = itemMetadata.commodityClassification;
597
+ classification.appendChild(code);
598
+ itemElement.appendChild(classification);
599
+ }
600
+
601
+ // Add additional item properties
602
+ if (itemMetadata.additionalProperties) {
603
+ for (const [propName, propValue] of Object.entries(itemMetadata.additionalProperties)) {
604
+ const additionalProp = doc.createElement('cac:AdditionalItemProperty');
605
+
606
+ const nameElement = doc.createElement('cbc:Name');
607
+ nameElement.textContent = propName;
608
+ additionalProp.appendChild(nameElement);
609
+
610
+ const valueElement = doc.createElement('cbc:Value');
611
+ valueElement.textContent = propValue as string;
612
+ additionalProp.appendChild(valueElement);
613
+
614
+ itemElement.appendChild(additionalProp);
615
+ }
616
+ }
617
+ }
618
+ }
619
+ }