@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,1041 @@
1
+ import { UBLBaseEncoder } from '../ubl.encoder.js';
2
+ import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js';
3
+ import { UBLDocumentType } from '../ubl.types.js';
4
+ import { DOMParser, XMLSerializer } from '../../../plugins.js';
5
+
6
+ /**
7
+ * UBL Encoder implementation
8
+ * Provides encoding functionality for UBL 2.1 invoice and credit note documents
9
+ */
10
+ export class UBLEncoder extends UBLBaseEncoder {
11
+ /**
12
+ * Encodes a credit note into UBL XML
13
+ * @param creditNote Credit note to encode
14
+ * @returns UBL XML string
15
+ */
16
+ protected async encodeCreditNote(creditNote: TCreditNote): Promise<string> {
17
+ // Create XML document from template
18
+ const xmlString = this.createXmlRoot(UBLDocumentType.CREDIT_NOTE);
19
+ const doc = new DOMParser().parseFromString(xmlString, 'application/xml');
20
+
21
+ // Add common document elements
22
+ this.addCommonElements(doc, creditNote as unknown as TInvoice, UBLDocumentType.CREDIT_NOTE);
23
+
24
+ // Add credit note specific data
25
+ this.addCreditNoteSpecificData(doc, creditNote);
26
+
27
+ // Serialize to string
28
+ return new XMLSerializer().serializeToString(doc);
29
+ }
30
+
31
+ /**
32
+ * Encodes a debit note (invoice) into UBL XML
33
+ * @param debitNote Debit note to encode
34
+ * @returns UBL XML string
35
+ */
36
+ protected async encodeDebitNote(debitNote: TDebitNote): Promise<string> {
37
+ // Create XML document from template
38
+ const xmlString = this.createXmlRoot(UBLDocumentType.INVOICE);
39
+ const doc = new DOMParser().parseFromString(xmlString, 'application/xml');
40
+
41
+ // Add common document elements
42
+ this.addCommonElements(doc, debitNote as unknown as TInvoice, UBLDocumentType.INVOICE);
43
+
44
+ // Add invoice specific data
45
+ this.addInvoiceSpecificData(doc, debitNote);
46
+
47
+ // Serialize to string
48
+ return new XMLSerializer().serializeToString(doc);
49
+ }
50
+
51
+ /**
52
+ * Adds common document elements to both invoice and credit note
53
+ * @param doc XML document
54
+ * @param invoice Invoice or credit note data
55
+ * @param documentType Document type (Invoice or CreditNote)
56
+ */
57
+ private addCommonElements(doc: Document, invoice: TInvoice, documentType: UBLDocumentType): void {
58
+ const root = doc.documentElement;
59
+
60
+ // UBL Version ID (2.1 is standard for EN16931)
61
+ this.appendElement(doc, root, 'cbc:UBLVersionID', '2.1');
62
+
63
+ // Customization ID - using generic UBL
64
+ this.appendElement(doc, root, 'cbc:CustomizationID', 'urn:cen.eu:en16931:2017');
65
+
66
+ // Profile ID - standard billing
67
+ this.appendElement(doc, root, 'cbc:ProfileID', 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0');
68
+
69
+ // ID
70
+ this.appendElement(doc, root, 'cbc:ID', invoice.id);
71
+
72
+ // Issue Date
73
+ this.appendElement(doc, root, 'cbc:IssueDate', this.formatDate(invoice.date));
74
+
75
+ // Due Date - ensure invoice.date is a valid timestamp
76
+ const issueTimestamp = typeof invoice.date === 'number' ? invoice.date : Date.now();
77
+ const dueDate = new Date(issueTimestamp);
78
+ dueDate.setDate(dueDate.getDate() + (invoice.dueInDays || 30));
79
+ this.appendElement(doc, root, 'cbc:DueDate', this.formatDate(dueDate.getTime()));
80
+
81
+ // Document Type Code
82
+ const typeCode = documentType === UBLDocumentType.INVOICE ? '380' : '381';
83
+ this.appendElement(doc, root, 'cbc:InvoiceTypeCode', typeCode);
84
+
85
+ // Notes - include subject as first note if it exists
86
+ const allNotes: string[] = [];
87
+ if (invoice.subject && invoice.subject.trim()) {
88
+ allNotes.push(invoice.subject);
89
+ }
90
+ if (invoice.notes && invoice.notes.length > 0) {
91
+ allNotes.push(...invoice.notes);
92
+ }
93
+
94
+ // Write all notes
95
+ for (const note of allNotes) {
96
+ this.appendElement(doc, root, 'cbc:Note', note);
97
+ }
98
+
99
+ // Document Currency Code
100
+ this.appendElement(doc, root, 'cbc:DocumentCurrencyCode', invoice.currency);
101
+
102
+ // Add accounting supplier party (seller)
103
+ this.addParty(doc, root, 'cac:AccountingSupplierParty', invoice.from);
104
+
105
+ // Add accounting customer party (buyer)
106
+ this.addParty(doc, root, 'cac:AccountingCustomerParty', invoice.to);
107
+
108
+ // Add payment terms
109
+ this.addPaymentTerms(doc, root, invoice);
110
+
111
+ // Add tax summary
112
+ this.addTaxTotal(doc, root, invoice);
113
+
114
+ // Add monetary totals
115
+ this.addLegalMonetaryTotal(doc, root, invoice);
116
+
117
+ // Add line items
118
+ this.addInvoiceLines(doc, root, invoice);
119
+
120
+ // Preserve metadata if available
121
+ this.preserveMetadata(doc, root, invoice);
122
+ }
123
+
124
+ /**
125
+ * Adds credit note specific data to the document
126
+ * @param doc XML document
127
+ * @param creditNote Credit note data
128
+ */
129
+ private addCreditNoteSpecificData(doc: Document, creditNote: TCreditNote): void {
130
+ // For now, there's no specific data to add for credit notes
131
+ // If needed, additional credit note specific fields would be added here
132
+ }
133
+
134
+ /**
135
+ * Adds invoice specific data to the document
136
+ * @param doc XML document
137
+ * @param invoice Invoice data
138
+ */
139
+ private addInvoiceSpecificData(doc: Document, invoice: TDebitNote): void {
140
+ // For now, there's no specific data to add for invoices that's not already covered
141
+ // If needed, additional invoice specific fields would be added here
142
+ }
143
+
144
+ /**
145
+ * Adds party information (supplier or customer)
146
+ * @param doc XML document
147
+ * @param parentElement Parent element
148
+ * @param elementName Element name (AccountingSupplierParty or AccountingCustomerParty)
149
+ * @param party Party data
150
+ */
151
+ private addParty(doc: Document, parentElement: Element, elementName: string, party: any): void {
152
+ const partyElement = doc.createElement(elementName);
153
+ parentElement.appendChild(partyElement);
154
+
155
+ const partyNode = doc.createElement('cac:Party');
156
+ partyElement.appendChild(partyNode);
157
+
158
+ // Party name
159
+ const partyNameNode = doc.createElement('cac:PartyName');
160
+ partyNode.appendChild(partyNameNode);
161
+ this.appendElement(doc, partyNameNode, 'cbc:Name', party.name);
162
+
163
+ // Postal address
164
+ const postalAddressNode = doc.createElement('cac:PostalAddress');
165
+ partyNode.appendChild(postalAddressNode);
166
+
167
+ if (party.address.streetName) {
168
+ this.appendElement(doc, postalAddressNode, 'cbc:StreetName', party.address.streetName);
169
+ }
170
+
171
+ if (party.address.houseNumber && party.address.houseNumber !== '0') {
172
+ this.appendElement(doc, postalAddressNode, 'cbc:BuildingNumber', party.address.houseNumber);
173
+ }
174
+
175
+ if (party.address.city) {
176
+ this.appendElement(doc, postalAddressNode, 'cbc:CityName', party.address.city);
177
+ }
178
+
179
+ if (party.address.postalCode) {
180
+ this.appendElement(doc, postalAddressNode, 'cbc:PostalZone', party.address.postalCode);
181
+ }
182
+
183
+ // Country
184
+ if (party.address.country || party.address.countryCode) {
185
+ const countryNode = doc.createElement('cac:Country');
186
+ postalAddressNode.appendChild(countryNode);
187
+
188
+ const countryCode = party.address.countryCode || this.getCountryCode(party.address.country);
189
+ this.appendElement(doc, countryNode, 'cbc:IdentificationCode', countryCode);
190
+
191
+ if (party.address.country) {
192
+ this.appendElement(doc, countryNode, 'cbc:Name', party.address.country);
193
+ }
194
+ }
195
+
196
+ // Party tax scheme (VAT ID)
197
+ if (party.registrationDetails && party.registrationDetails.vatId) {
198
+ const partyTaxSchemeNode = doc.createElement('cac:PartyTaxScheme');
199
+ partyNode.appendChild(partyTaxSchemeNode);
200
+
201
+ this.appendElement(doc, partyTaxSchemeNode, 'cbc:CompanyID', party.registrationDetails.vatId);
202
+
203
+ const taxSchemeNode = doc.createElement('cac:TaxScheme');
204
+ partyTaxSchemeNode.appendChild(taxSchemeNode);
205
+ this.appendElement(doc, taxSchemeNode, 'cbc:ID', 'VAT');
206
+ }
207
+
208
+ // Party legal entity (registration information)
209
+ if (party.registrationDetails) {
210
+ const partyLegalEntityNode = doc.createElement('cac:PartyLegalEntity');
211
+ partyNode.appendChild(partyLegalEntityNode);
212
+
213
+ const registrationName = party.registrationDetails.registrationName || party.name;
214
+ this.appendElement(doc, partyLegalEntityNode, 'cbc:RegistrationName', registrationName);
215
+
216
+ if (party.registrationDetails.registrationId) {
217
+ this.appendElement(doc, partyLegalEntityNode, 'cbc:CompanyID', party.registrationDetails.registrationId);
218
+ }
219
+ }
220
+
221
+ // Contact information
222
+ if (party.contactDetails) {
223
+ const contactNode = doc.createElement('cac:Contact');
224
+ partyNode.appendChild(contactNode);
225
+
226
+ if (party.contactDetails.name) {
227
+ this.appendElement(doc, contactNode, 'cbc:Name', party.contactDetails.name);
228
+ }
229
+
230
+ if (party.contactDetails.telephone) {
231
+ this.appendElement(doc, contactNode, 'cbc:Telephone', party.contactDetails.telephone);
232
+ }
233
+
234
+ if (party.contactDetails.email) {
235
+ this.appendElement(doc, contactNode, 'cbc:ElectronicMail', party.contactDetails.email);
236
+ }
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Adds payment terms information
242
+ * @param doc XML document
243
+ * @param parentElement Parent element
244
+ * @param invoice Invoice data
245
+ */
246
+ private addPaymentTerms(doc: Document, parentElement: Element, invoice: TInvoice): void {
247
+ const paymentTermsNode = doc.createElement('cac:PaymentTerms');
248
+ parentElement.appendChild(paymentTermsNode);
249
+
250
+ // Payment terms note
251
+ this.appendElement(doc, paymentTermsNode, 'cbc:Note', `Due in ${invoice.dueInDays} days`);
252
+
253
+ // Add payment means if available
254
+ if (invoice.paymentOptions) {
255
+ this.addPaymentMeans(doc, parentElement, invoice);
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Adds payment means information
261
+ * @param doc XML document
262
+ * @param parentElement Parent element
263
+ * @param invoice Invoice data
264
+ */
265
+ private addPaymentMeans(doc: Document, parentElement: Element, invoice: TInvoice): void {
266
+ const paymentMeansNode = doc.createElement('cac:PaymentMeans');
267
+ parentElement.appendChild(paymentMeansNode);
268
+
269
+ // Payment means code - default to credit transfer
270
+ this.appendElement(doc, paymentMeansNode, 'cbc:PaymentMeansCode', '30');
271
+
272
+ // Payment due date - ensure invoice.date is a valid timestamp
273
+ const issueTimestamp = typeof invoice.date === 'number' ? invoice.date : Date.now();
274
+ const dueDate = new Date(issueTimestamp);
275
+ dueDate.setDate(dueDate.getDate() + (invoice.dueInDays || 30));
276
+ this.appendElement(doc, paymentMeansNode, 'cbc:PaymentDueDate', this.formatDate(dueDate.getTime()));
277
+
278
+ // Add payment channel code if available
279
+ if (invoice.paymentOptions.description) {
280
+ this.appendElement(doc, paymentMeansNode, 'cbc:InstructionNote', invoice.paymentOptions.description);
281
+ }
282
+
283
+ // Add payment ID information if available - use invoice ID as payment reference
284
+ this.appendElement(doc, paymentMeansNode, 'cbc:PaymentID', invoice.id);
285
+
286
+ // Add bank account information if available
287
+ if (invoice.paymentOptions.sepaConnection && invoice.paymentOptions.sepaConnection.iban) {
288
+ const payeeFinancialAccountNode = doc.createElement('cac:PayeeFinancialAccount');
289
+ paymentMeansNode.appendChild(payeeFinancialAccountNode);
290
+
291
+ this.appendElement(doc, payeeFinancialAccountNode, 'cbc:ID', invoice.paymentOptions.sepaConnection.iban);
292
+
293
+ // Add financial institution information if BIC is available
294
+ if (invoice.paymentOptions.sepaConnection.bic) {
295
+ const financialInstitutionNode = doc.createElement('cac:FinancialInstitutionBranch');
296
+ payeeFinancialAccountNode.appendChild(financialInstitutionNode);
297
+
298
+ this.appendElement(doc, financialInstitutionNode, 'cbc:ID', invoice.paymentOptions.sepaConnection.bic);
299
+ }
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Adds tax total information
305
+ * @param doc XML document
306
+ * @param parentElement Parent element
307
+ * @param invoice Invoice data
308
+ */
309
+ private addTaxTotal(doc: Document, parentElement: Element, invoice: TInvoice): void {
310
+ const taxTotalNode = doc.createElement('cac:TaxTotal');
311
+ parentElement.appendChild(taxTotalNode);
312
+
313
+ // Calculate total tax amount
314
+ let totalTaxAmount = 0;
315
+ const taxCategories = new Map<number, number>(); // Map of VAT rate to net amount
316
+
317
+ // Calculate from items
318
+ if (invoice.items) {
319
+ for (const item of invoice.items) {
320
+ const itemNetAmount = item.unitNetPrice * item.unitQuantity;
321
+ const itemTaxAmount = itemNetAmount * (item.vatPercentage / 100);
322
+ const vatRate = item.vatPercentage;
323
+
324
+ totalTaxAmount += itemTaxAmount;
325
+
326
+ // Aggregate by VAT rate
327
+ const currentAmount = taxCategories.get(vatRate) || 0;
328
+ taxCategories.set(vatRate, currentAmount + itemNetAmount);
329
+ }
330
+ }
331
+
332
+ // Add total tax amount
333
+ const taxAmountElement = doc.createElement('cbc:TaxAmount');
334
+ taxAmountElement.setAttribute('currencyID', invoice.currency);
335
+ taxAmountElement.textContent = totalTaxAmount.toFixed(2);
336
+ taxTotalNode.appendChild(taxAmountElement);
337
+
338
+ // Add tax subtotals
339
+ for (const [rate, baseAmount] of taxCategories.entries()) {
340
+ const taxSubtotalNode = doc.createElement('cac:TaxSubtotal');
341
+ taxTotalNode.appendChild(taxSubtotalNode);
342
+
343
+ // Taxable amount
344
+ const taxableAmountElement = doc.createElement('cbc:TaxableAmount');
345
+ taxableAmountElement.setAttribute('currencyID', invoice.currency);
346
+ taxableAmountElement.textContent = baseAmount.toFixed(2);
347
+ taxSubtotalNode.appendChild(taxableAmountElement);
348
+
349
+ // Tax amount
350
+ const taxAmount = baseAmount * (rate / 100);
351
+ const subtotalTaxAmountElement = doc.createElement('cbc:TaxAmount');
352
+ subtotalTaxAmountElement.setAttribute('currencyID', invoice.currency);
353
+ subtotalTaxAmountElement.textContent = taxAmount.toFixed(2);
354
+ taxSubtotalNode.appendChild(subtotalTaxAmountElement);
355
+
356
+ // Tax category
357
+ const taxCategoryNode = doc.createElement('cac:TaxCategory');
358
+ taxSubtotalNode.appendChild(taxCategoryNode);
359
+
360
+ // Determine tax category ID based on reverse charge
361
+ const categoryId = invoice.reverseCharge ? 'AE' : 'S';
362
+ this.appendElement(doc, taxCategoryNode, 'cbc:ID', categoryId);
363
+
364
+ // Add percent with 2 decimal places
365
+ this.appendElement(doc, taxCategoryNode, 'cbc:Percent', rate.toFixed(2));
366
+
367
+ // Add tax exemption reason if reverse charge
368
+ if (invoice.reverseCharge) {
369
+ this.appendElement(doc, taxCategoryNode, 'cbc:TaxExemptionReasonCode', 'VATEX-EU-IC');
370
+ this.appendElement(doc, taxCategoryNode, 'cbc:TaxExemptionReason', 'Reverse charge');
371
+ }
372
+
373
+ // Add tax scheme
374
+ const taxSchemeNode = doc.createElement('cac:TaxScheme');
375
+ taxCategoryNode.appendChild(taxSchemeNode);
376
+ this.appendElement(doc, taxSchemeNode, 'cbc:ID', 'VAT');
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Adds legal monetary total information
382
+ * @param doc XML document
383
+ * @param parentElement Parent element
384
+ * @param invoice Invoice data
385
+ */
386
+ private addLegalMonetaryTotal(doc: Document, parentElement: Element, invoice: TInvoice): void {
387
+ const legalMonetaryTotalNode = doc.createElement('cac:LegalMonetaryTotal');
388
+ parentElement.appendChild(legalMonetaryTotalNode);
389
+
390
+ // Calculate totals
391
+ let totalNetAmount = 0;
392
+ let totalTaxAmount = 0;
393
+
394
+ // Calculate from items
395
+ if (invoice.items) {
396
+ for (const item of invoice.items) {
397
+ const itemNetAmount = item.unitNetPrice * item.unitQuantity;
398
+ const itemTaxAmount = itemNetAmount * (item.vatPercentage / 100);
399
+
400
+ totalNetAmount += itemNetAmount;
401
+ totalTaxAmount += itemTaxAmount;
402
+ }
403
+ }
404
+
405
+ const totalGrossAmount = totalNetAmount + totalTaxAmount;
406
+
407
+ // Line extension amount (sum of line net amounts)
408
+ const lineExtensionAmountElement = doc.createElement('cbc:LineExtensionAmount');
409
+ lineExtensionAmountElement.setAttribute('currencyID', invoice.currency);
410
+ lineExtensionAmountElement.textContent = totalNetAmount.toFixed(2);
411
+ legalMonetaryTotalNode.appendChild(lineExtensionAmountElement);
412
+
413
+ // Tax exclusive amount
414
+ const taxExclusiveAmountElement = doc.createElement('cbc:TaxExclusiveAmount');
415
+ taxExclusiveAmountElement.setAttribute('currencyID', invoice.currency);
416
+ taxExclusiveAmountElement.textContent = totalNetAmount.toFixed(2);
417
+ legalMonetaryTotalNode.appendChild(taxExclusiveAmountElement);
418
+
419
+ // Tax inclusive amount
420
+ const taxInclusiveAmountElement = doc.createElement('cbc:TaxInclusiveAmount');
421
+ taxInclusiveAmountElement.setAttribute('currencyID', invoice.currency);
422
+ taxInclusiveAmountElement.textContent = totalGrossAmount.toFixed(2);
423
+ legalMonetaryTotalNode.appendChild(taxInclusiveAmountElement);
424
+
425
+ // Payable amount
426
+ const payableAmountElement = doc.createElement('cbc:PayableAmount');
427
+ payableAmountElement.setAttribute('currencyID', invoice.currency);
428
+ payableAmountElement.textContent = totalGrossAmount.toFixed(2);
429
+ legalMonetaryTotalNode.appendChild(payableAmountElement);
430
+ }
431
+
432
+ /**
433
+ * Adds invoice lines
434
+ * @param doc XML document
435
+ * @param parentElement Parent element
436
+ * @param invoice Invoice data
437
+ */
438
+ private addInvoiceLines(doc: Document, parentElement: Element, invoice: TInvoice): void {
439
+ if (!invoice.items) return;
440
+
441
+ for (const item of invoice.items) {
442
+ const invoiceLineNode = doc.createElement('cac:InvoiceLine');
443
+ parentElement.appendChild(invoiceLineNode);
444
+
445
+ // ID
446
+ this.appendElement(doc, invoiceLineNode, 'cbc:ID', item.position.toString());
447
+
448
+ // Invoiced quantity
449
+ const quantityElement = doc.createElement('cbc:InvoicedQuantity');
450
+ quantityElement.setAttribute('unitCode', item.unitType);
451
+ quantityElement.textContent = item.unitQuantity.toString();
452
+ invoiceLineNode.appendChild(quantityElement);
453
+
454
+ // Line extension amount (line net amount)
455
+ const itemNetAmount = item.unitNetPrice * item.unitQuantity;
456
+ const lineExtensionAmountElement = doc.createElement('cbc:LineExtensionAmount');
457
+ lineExtensionAmountElement.setAttribute('currencyID', invoice.currency);
458
+ lineExtensionAmountElement.textContent = itemNetAmount.toFixed(2);
459
+ invoiceLineNode.appendChild(lineExtensionAmountElement);
460
+
461
+ // Item information
462
+ const itemNode = doc.createElement('cac:Item');
463
+ invoiceLineNode.appendChild(itemNode);
464
+
465
+ // Description - use description field if available, otherwise use name
466
+ const description = (item as any).description || item.name;
467
+ this.appendElement(doc, itemNode, 'cbc:Description', description);
468
+ this.appendElement(doc, itemNode, 'cbc:Name', item.name);
469
+
470
+ // Seller's item identification
471
+ if (item.articleNumber) {
472
+ const sellersItemIdentificationNode = doc.createElement('cac:SellersItemIdentification');
473
+ itemNode.appendChild(sellersItemIdentificationNode);
474
+ this.appendElement(doc, sellersItemIdentificationNode, 'cbc:ID', item.articleNumber);
475
+ }
476
+
477
+ // Item tax information
478
+ const classifiedTaxCategoryNode = doc.createElement('cac:ClassifiedTaxCategory');
479
+ itemNode.appendChild(classifiedTaxCategoryNode);
480
+
481
+ // Determine tax category ID based on reverse charge
482
+ const categoryId = invoice.reverseCharge ? 'AE' : 'S';
483
+ this.appendElement(doc, classifiedTaxCategoryNode, 'cbc:ID', categoryId);
484
+
485
+ // Tax percent with 2 decimal places
486
+ this.appendElement(doc, classifiedTaxCategoryNode, 'cbc:Percent', item.vatPercentage.toFixed(2));
487
+
488
+ // Tax scheme
489
+ const taxSchemeNode = doc.createElement('cac:TaxScheme');
490
+ classifiedTaxCategoryNode.appendChild(taxSchemeNode);
491
+ this.appendElement(doc, taxSchemeNode, 'cbc:ID', 'VAT');
492
+
493
+ // Price information
494
+ const priceNode = doc.createElement('cac:Price');
495
+ invoiceLineNode.appendChild(priceNode);
496
+
497
+ // Price amount
498
+ const priceAmountElement = doc.createElement('cbc:PriceAmount');
499
+ priceAmountElement.setAttribute('currencyID', invoice.currency);
500
+ priceAmountElement.textContent = item.unitNetPrice.toFixed(2);
501
+ priceNode.appendChild(priceAmountElement);
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Helper method to append a simple element with text content
507
+ * @param doc XML document
508
+ * @param parentElement Parent element
509
+ * @param elementName Element name
510
+ * @param textContent Text content
511
+ */
512
+ private appendElement(doc: Document, parentElement: Element, elementName: string, textContent: string): void {
513
+ const element = doc.createElement(elementName);
514
+ element.textContent = textContent;
515
+ parentElement.appendChild(element);
516
+ }
517
+
518
+ /**
519
+ * Helper method to get country code from country name
520
+ * Simple implementation that assumes the country name is already a code
521
+ * @param countryName Country name
522
+ * @returns Country code (2-letter ISO code)
523
+ */
524
+ private getCountryCode(countryName: string): string {
525
+ // In a real implementation, this would map country names to ISO codes
526
+ // For now, just return the first 2 characters or "XX" as fallback
527
+ if (!countryName) return 'XX';
528
+ return countryName.length >= 2 ? countryName.substring(0, 2).toUpperCase() : 'XX';
529
+ }
530
+
531
+ /**
532
+ * Preserves metadata from invoice to enhance UBL XML output
533
+ * @param doc XML document
534
+ * @param root Root element
535
+ * @param invoice Invoice data
536
+ */
537
+ private preserveMetadata(doc: Document, root: Element, invoice: TInvoice): void {
538
+ // Extract metadata if available
539
+ const metadata = (invoice as any).metadata?.extensions;
540
+ if (!metadata) return;
541
+
542
+ // Preserve business references
543
+ this.addBusinessReferencesToUBL(doc, root, metadata.businessReferences);
544
+
545
+ // Preserve payment information
546
+ this.enhancePaymentInformationUBL(doc, root, metadata.paymentInformation);
547
+
548
+ // Preserve date information
549
+ this.addDateInformationUBL(doc, root, metadata.dateInformation);
550
+
551
+ // Enhance party information with contact details
552
+ this.enhancePartyInformationUBL(doc, invoice);
553
+
554
+ // Enhance line items with metadata
555
+ this.enhanceLineItemsUBL(doc, invoice);
556
+ }
557
+
558
+ /**
559
+ * Adds business references from metadata to UBL document
560
+ * @param doc XML document
561
+ * @param root Root element
562
+ * @param businessReferences Business references from metadata
563
+ */
564
+ private addBusinessReferencesToUBL(doc: Document, root: Element, businessReferences?: any): void {
565
+ if (!businessReferences) return;
566
+
567
+ // Add BuyerReference
568
+ if (businessReferences.buyerReference && !root.getElementsByTagName('cbc:BuyerReference')[0]) {
569
+ const buyerRef = doc.createElement('cbc:BuyerReference');
570
+ buyerRef.textContent = businessReferences.buyerReference;
571
+
572
+ // Insert after DocumentCurrencyCode
573
+ const currencyCode = root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
574
+ if (currencyCode && currencyCode.parentNode) {
575
+ currencyCode.parentNode.insertBefore(buyerRef, currencyCode.nextSibling);
576
+ }
577
+ }
578
+
579
+ // Add OrderReference
580
+ if (businessReferences.orderReference && !root.getElementsByTagName('cac:OrderReference')[0]) {
581
+ const orderRef = doc.createElement('cac:OrderReference');
582
+ const orderId = doc.createElement('cbc:ID');
583
+ orderId.textContent = businessReferences.orderReference;
584
+ orderRef.appendChild(orderId);
585
+
586
+ // Insert after BuyerReference or DocumentCurrencyCode
587
+ const buyerRef = root.getElementsByTagName('cbc:BuyerReference')[0];
588
+ const currencyCode = root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
589
+ const insertAfter = buyerRef || currencyCode;
590
+ if (insertAfter && insertAfter.parentNode) {
591
+ insertAfter.parentNode.insertBefore(orderRef, insertAfter.nextSibling);
592
+ }
593
+ }
594
+
595
+ // Add ContractDocumentReference
596
+ if (businessReferences.contractReference && !root.getElementsByTagName('cac:ContractDocumentReference')[0]) {
597
+ const contractRef = doc.createElement('cac:ContractDocumentReference');
598
+ const contractId = doc.createElement('cbc:ID');
599
+ contractId.textContent = businessReferences.contractReference;
600
+ contractRef.appendChild(contractId);
601
+
602
+ // Insert after OrderReference or DocumentCurrencyCode
603
+ const orderRef = root.getElementsByTagName('cac:OrderReference')[0];
604
+ const insertAfter = orderRef || root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
605
+ if (insertAfter && insertAfter.parentNode) {
606
+ insertAfter.parentNode.insertBefore(contractRef, insertAfter.nextSibling);
607
+ }
608
+ }
609
+
610
+ // Add ProjectReference
611
+ if (businessReferences.projectReference && !root.getElementsByTagName('cac:ProjectReference')[0]) {
612
+ const projectRef = doc.createElement('cac:ProjectReference');
613
+ const projectId = doc.createElement('cbc:ID');
614
+ projectId.textContent = businessReferences.projectReference;
615
+ projectRef.appendChild(projectId);
616
+
617
+ // Insert after ContractDocumentReference or other refs
618
+ const contractRef = root.getElementsByTagName('cac:ContractDocumentReference')[0];
619
+ const orderRef = root.getElementsByTagName('cac:OrderReference')[0];
620
+ const insertAfter = contractRef || orderRef || root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
621
+ if (insertAfter && insertAfter.parentNode) {
622
+ insertAfter.parentNode.insertBefore(projectRef, insertAfter.nextSibling);
623
+ }
624
+ }
625
+ }
626
+
627
+ /**
628
+ * Enhances payment information from metadata in UBL document
629
+ * @param doc XML document
630
+ * @param root Root element
631
+ * @param paymentInfo Payment information from metadata
632
+ */
633
+ private enhancePaymentInformationUBL(doc: Document, root: Element, paymentInfo?: any): void {
634
+ if (!paymentInfo) return;
635
+
636
+ let paymentMeans = root.getElementsByTagName('cac:PaymentMeans')[0];
637
+
638
+ // Create PaymentMeans if it doesn't exist
639
+ if (!paymentMeans) {
640
+ paymentMeans = doc.createElement('cac:PaymentMeans');
641
+ // Insert before TaxTotal
642
+ const taxTotal = root.getElementsByTagName('cac:TaxTotal')[0];
643
+ if (taxTotal && taxTotal.parentNode) {
644
+ taxTotal.parentNode.insertBefore(paymentMeans, taxTotal);
645
+ }
646
+ }
647
+
648
+ // Add PaymentMeansCode
649
+ if (paymentInfo.paymentMeansCode && !paymentMeans.getElementsByTagName('cbc:PaymentMeansCode')[0]) {
650
+ const meansCode = doc.createElement('cbc:PaymentMeansCode');
651
+ meansCode.textContent = paymentInfo.paymentMeansCode;
652
+ paymentMeans.appendChild(meansCode);
653
+ }
654
+
655
+ // Add PaymentID
656
+ if (paymentInfo.paymentID && !paymentMeans.getElementsByTagName('cbc:PaymentID')[0]) {
657
+ const paymentId = doc.createElement('cbc:PaymentID');
658
+ paymentId.textContent = paymentInfo.paymentID;
659
+ paymentMeans.appendChild(paymentId);
660
+ }
661
+
662
+ // Add PaymentDueDate
663
+ if (paymentInfo.paymentDueDate && !paymentMeans.getElementsByTagName('cbc:PaymentDueDate')[0]) {
664
+ const dueDate = doc.createElement('cbc:PaymentDueDate');
665
+ dueDate.textContent = paymentInfo.paymentDueDate;
666
+ paymentMeans.appendChild(dueDate);
667
+ }
668
+
669
+ // Add IBAN and BIC
670
+ if (paymentInfo.iban || paymentInfo.bic) {
671
+ let payeeAccount = paymentMeans.getElementsByTagName('cac:PayeeFinancialAccount')[0];
672
+ if (!payeeAccount) {
673
+ payeeAccount = doc.createElement('cac:PayeeFinancialAccount');
674
+ paymentMeans.appendChild(payeeAccount);
675
+ }
676
+
677
+ // Add IBAN
678
+ if (paymentInfo.iban && !payeeAccount.getElementsByTagName('cbc:ID')[0]) {
679
+ const iban = doc.createElement('cbc:ID');
680
+ iban.textContent = paymentInfo.iban;
681
+ payeeAccount.appendChild(iban);
682
+ }
683
+
684
+ // Add account name (must come after ID but before FinancialInstitutionBranch)
685
+ if (paymentInfo.accountName && !payeeAccount.getElementsByTagName('cbc:Name')[0]) {
686
+ const accountName = doc.createElement('cbc:Name');
687
+ accountName.textContent = paymentInfo.accountName;
688
+ // Insert after ID but before FinancialInstitutionBranch
689
+ const id = payeeAccount.getElementsByTagName('cbc:ID')[0];
690
+ const finInstBranch = payeeAccount.getElementsByTagName('cac:FinancialInstitutionBranch')[0];
691
+ if (finInstBranch) {
692
+ payeeAccount.insertBefore(accountName, finInstBranch);
693
+ } else if (id && id.nextSibling) {
694
+ payeeAccount.insertBefore(accountName, id.nextSibling);
695
+ } else {
696
+ payeeAccount.appendChild(accountName);
697
+ }
698
+ }
699
+
700
+ // Add BIC and bank name
701
+ if (paymentInfo.bic || paymentInfo.bankName) {
702
+ let finInstBranch = payeeAccount.getElementsByTagName('cac:FinancialInstitutionBranch')[0];
703
+ if (!finInstBranch) {
704
+ finInstBranch = doc.createElement('cac:FinancialInstitutionBranch');
705
+ payeeAccount.appendChild(finInstBranch);
706
+ }
707
+
708
+ // Add BIC as branch ID
709
+ if (paymentInfo.bic && !finInstBranch.getElementsByTagName('cbc:ID')[0]) {
710
+ const bicElement = doc.createElement('cbc:ID');
711
+ bicElement.textContent = paymentInfo.bic;
712
+ finInstBranch.appendChild(bicElement);
713
+ }
714
+
715
+ // Add bank name
716
+ if (paymentInfo.bankName && !finInstBranch.getElementsByTagName('cbc:Name')[0]) {
717
+ const bankNameElement = doc.createElement('cbc:Name');
718
+ bankNameElement.textContent = paymentInfo.bankName;
719
+ finInstBranch.appendChild(bankNameElement);
720
+ }
721
+ }
722
+ }
723
+
724
+ // Add payment terms with discount if available
725
+ if (paymentInfo.paymentTermsNote && paymentInfo.paymentTermsNote.includes('early payment')) {
726
+ let paymentTerms = root.getElementsByTagName('cac:PaymentTerms')[0];
727
+ if (!paymentTerms) {
728
+ paymentTerms = doc.createElement('cac:PaymentTerms');
729
+ // Insert before PaymentMeans
730
+ const paymentMeans = root.getElementsByTagName('cac:PaymentMeans')[0];
731
+ if (paymentMeans && paymentMeans.parentNode) {
732
+ paymentMeans.parentNode.insertBefore(paymentTerms, paymentMeans);
733
+ }
734
+ }
735
+
736
+ // Update or add note
737
+ let note = paymentTerms.getElementsByTagName('cbc:Note')[0];
738
+ if (!note) {
739
+ note = doc.createElement('cbc:Note');
740
+ paymentTerms.appendChild(note);
741
+ }
742
+ note.textContent = paymentInfo.paymentTermsNote;
743
+
744
+ // Add discount percent if available
745
+ if (paymentInfo.discountPercent && !paymentTerms.getElementsByTagName('cbc:SettlementDiscountPercent')[0]) {
746
+ const discountElement = doc.createElement('cbc:SettlementDiscountPercent');
747
+ discountElement.textContent = paymentInfo.discountPercent;
748
+ paymentTerms.appendChild(discountElement);
749
+ }
750
+ }
751
+ }
752
+
753
+ /**
754
+ * Adds date information from metadata to UBL document
755
+ * @param doc XML document
756
+ * @param root Root element
757
+ * @param dateInfo Date information from metadata
758
+ */
759
+ private addDateInformationUBL(doc: Document, root: Element, dateInfo?: any): void {
760
+ if (!dateInfo) return;
761
+
762
+ // Add InvoicePeriod
763
+ if ((dateInfo.periodStart || dateInfo.periodEnd) && !root.getElementsByTagName('cac:InvoicePeriod')[0]) {
764
+ const invoicePeriod = doc.createElement('cac:InvoicePeriod');
765
+
766
+ if (dateInfo.periodStart) {
767
+ const startDate = doc.createElement('cbc:StartDate');
768
+ startDate.textContent = dateInfo.periodStart;
769
+ invoicePeriod.appendChild(startDate);
770
+ }
771
+
772
+ if (dateInfo.periodEnd) {
773
+ const endDate = doc.createElement('cbc:EndDate');
774
+ endDate.textContent = dateInfo.periodEnd;
775
+ invoicePeriod.appendChild(endDate);
776
+ }
777
+
778
+ // Insert after business references or DocumentCurrencyCode
779
+ const projectRef = root.getElementsByTagName('cac:ProjectReference')[0];
780
+ const contractRef = root.getElementsByTagName('cac:ContractDocumentReference')[0];
781
+ const orderRef = root.getElementsByTagName('cac:OrderReference')[0];
782
+ const insertAfter = projectRef || contractRef || orderRef || root.getElementsByTagName('cbc:DocumentCurrencyCode')[0];
783
+ if (insertAfter && insertAfter.parentNode) {
784
+ insertAfter.parentNode.insertBefore(invoicePeriod, insertAfter.nextSibling);
785
+ }
786
+ }
787
+
788
+ // Add Delivery with ActualDeliveryDate
789
+ if (dateInfo.deliveryDate && !root.getElementsByTagName('cac:Delivery')[0]) {
790
+ const delivery = doc.createElement('cac:Delivery');
791
+ const deliveryDate = doc.createElement('cbc:ActualDeliveryDate');
792
+ deliveryDate.textContent = dateInfo.deliveryDate;
793
+ delivery.appendChild(deliveryDate);
794
+
795
+ // Insert before PaymentMeans
796
+ const paymentMeans = root.getElementsByTagName('cac:PaymentMeans')[0];
797
+ if (paymentMeans && paymentMeans.parentNode) {
798
+ paymentMeans.parentNode.insertBefore(delivery, paymentMeans);
799
+ }
800
+ }
801
+ }
802
+
803
+ /**
804
+ * Enhances party information with contact details from metadata
805
+ * @param doc XML document
806
+ * @param invoice Invoice data
807
+ */
808
+ private enhancePartyInformationUBL(doc: Document, invoice: TInvoice): void {
809
+ // Enhance supplier party
810
+ this.enhancePartyUBL(doc, 'cac:AccountingSupplierParty', invoice.from);
811
+
812
+ // Enhance customer party
813
+ this.enhancePartyUBL(doc, 'cac:AccountingCustomerParty', invoice.to);
814
+ }
815
+
816
+ /**
817
+ * Enhances a party with GLN, additional identifiers, and contact info
818
+ * @param doc XML document
819
+ * @param partySelector Party selector
820
+ * @param partyData Party data
821
+ */
822
+ private enhancePartyUBL(doc: Document, partySelector: string, partyData: any): void {
823
+ if (!partyData) return;
824
+
825
+ const partyContainer = doc.getElementsByTagName(partySelector)[0];
826
+ if (!partyContainer) return;
827
+
828
+ const party = partyContainer.getElementsByTagName('cac:Party')[0];
829
+ if (!party) return;
830
+
831
+ // Add GLN if available
832
+ if (partyData.gln && !party.getElementsByTagName('cbc:EndpointID')[0]) {
833
+ const endpointNode = doc.createElement('cbc:EndpointID');
834
+ endpointNode.setAttribute('schemeID', '0088'); // GLN scheme ID
835
+ endpointNode.textContent = partyData.gln;
836
+
837
+ // Insert as first child
838
+ if (party.firstChild) {
839
+ party.insertBefore(endpointNode, party.firstChild);
840
+ } else {
841
+ party.appendChild(endpointNode);
842
+ }
843
+ }
844
+
845
+ // Add additional identifiers
846
+ if (partyData.additionalIdentifiers && Array.isArray(partyData.additionalIdentifiers)) {
847
+ for (const identifier of partyData.additionalIdentifiers) {
848
+ const partyId = doc.createElement('cac:PartyIdentification');
849
+ const id = doc.createElement('cbc:ID');
850
+ if (identifier.scheme) {
851
+ id.setAttribute('schemeID', identifier.scheme);
852
+ }
853
+ id.textContent = identifier.value;
854
+ partyId.appendChild(id);
855
+
856
+ // Insert after EndpointID or at beginning
857
+ const endpoint = party.getElementsByTagName('cbc:EndpointID')[0];
858
+ if (endpoint && endpoint.nextSibling) {
859
+ party.insertBefore(partyId, endpoint.nextSibling);
860
+ } else if (party.firstChild) {
861
+ party.insertBefore(partyId, party.firstChild);
862
+ } else {
863
+ party.appendChild(partyId);
864
+ }
865
+ }
866
+ }
867
+
868
+ // Add contact information from metadata if not already present
869
+ const contactInfo = partyData.metadata?.contactInformation;
870
+ if (contactInfo) {
871
+ this.addContactToPartyUBL(doc, partySelector, contactInfo);
872
+ }
873
+ }
874
+
875
+ /**
876
+ * Adds contact information to a party in UBL document
877
+ * @param doc XML document
878
+ * @param partySelector Party selector
879
+ * @param contactInfo Contact information from metadata
880
+ */
881
+ private addContactToPartyUBL(doc: Document, partySelector: string, contactInfo?: any): void {
882
+ if (!contactInfo) return;
883
+
884
+ const partyContainer = doc.getElementsByTagName(partySelector)[0];
885
+ if (!partyContainer) return;
886
+
887
+ const party = partyContainer.getElementsByTagName('cac:Party')[0];
888
+ if (!party) return;
889
+
890
+ // Check if Contact already exists
891
+ let contact = party.getElementsByTagName('cac:Contact')[0];
892
+ if (!contact && (contactInfo.name || contactInfo.phone || contactInfo.email)) {
893
+ contact = doc.createElement('cac:Contact');
894
+
895
+ // Insert after PartyName
896
+ const partyName = party.getElementsByTagName('cac:PartyName')[0];
897
+ if (partyName && partyName.parentNode) {
898
+ partyName.parentNode.insertBefore(contact, partyName.nextSibling);
899
+ } else {
900
+ party.appendChild(contact);
901
+ }
902
+ }
903
+
904
+ if (contact) {
905
+ // Add contact name
906
+ if (contactInfo.name && !contact.getElementsByTagName('cbc:Name')[0]) {
907
+ const name = doc.createElement('cbc:Name');
908
+ name.textContent = contactInfo.name;
909
+ contact.appendChild(name);
910
+ }
911
+
912
+ // Add telephone
913
+ if (contactInfo.phone && !contact.getElementsByTagName('cbc:Telephone')[0]) {
914
+ const phone = doc.createElement('cbc:Telephone');
915
+ phone.textContent = contactInfo.phone;
916
+ contact.appendChild(phone);
917
+ }
918
+
919
+ // Add email
920
+ if (contactInfo.email && !contact.getElementsByTagName('cbc:ElectronicMail')[0]) {
921
+ const email = doc.createElement('cbc:ElectronicMail');
922
+ email.textContent = contactInfo.email;
923
+ contact.appendChild(email);
924
+ }
925
+ }
926
+ }
927
+
928
+ /**
929
+ * Enhances line items with metadata in UBL document
930
+ * @param doc XML document
931
+ * @param invoice Invoice data
932
+ */
933
+ private enhanceLineItemsUBL(doc: Document, invoice: TInvoice): void {
934
+ const invoiceLines = doc.getElementsByTagName('cac:InvoiceLine');
935
+
936
+ for (let i = 0; i < invoiceLines.length && i < invoice.items.length; i++) {
937
+ const line = invoiceLines[i];
938
+ const item = invoice.items[i];
939
+ const itemMetadata = (item as any).metadata;
940
+
941
+ if (!itemMetadata) continue;
942
+
943
+ // Add OrderLineReference if available
944
+ if (itemMetadata.orderLineReference && !line.getElementsByTagName('cac:OrderLineReference')[0]) {
945
+ const orderLineRef = doc.createElement('cac:OrderLineReference');
946
+ const lineId = doc.createElement('cbc:LineID');
947
+ lineId.textContent = itemMetadata.orderLineReferenceId || '1';
948
+ orderLineRef.appendChild(lineId);
949
+
950
+ if (itemMetadata.orderLineReference) {
951
+ const orderRef = doc.createElement('cac:OrderReference');
952
+ const orderId = doc.createElement('cbc:ID');
953
+ orderId.textContent = itemMetadata.orderLineReference;
954
+ orderRef.appendChild(orderId);
955
+ orderLineRef.appendChild(orderRef);
956
+ }
957
+
958
+ // Insert after ID
959
+ const invoiceLineId = line.getElementsByTagName('cbc:ID')[0];
960
+ if (invoiceLineId && invoiceLineId.nextSibling) {
961
+ line.insertBefore(orderLineRef, invoiceLineId.nextSibling);
962
+ } else {
963
+ // Insert before InvoicedQuantity
964
+ const quantity = line.getElementsByTagName('cbc:InvoicedQuantity')[0];
965
+ if (quantity) {
966
+ line.insertBefore(orderLineRef, quantity);
967
+ }
968
+ }
969
+ }
970
+
971
+ const itemElement = line.getElementsByTagName('cac:Item')[0];
972
+ if (!itemElement) continue;
973
+
974
+ // Add item description
975
+ if (itemMetadata.description && !itemElement.getElementsByTagName('cbc:Description')[0]) {
976
+ const desc = doc.createElement('cbc:Description');
977
+ desc.textContent = itemMetadata.description;
978
+ // Insert before Name
979
+ const name = itemElement.getElementsByTagName('cbc:Name')[0];
980
+ if (name && name.parentNode) {
981
+ name.parentNode.insertBefore(desc, name);
982
+ } else {
983
+ itemElement.appendChild(desc);
984
+ }
985
+ }
986
+
987
+ // Add SellersItemIdentification
988
+ if (item.articleNumber && !itemElement.getElementsByTagName('cac:SellersItemIdentification')[0]) {
989
+ const sellerId = doc.createElement('cac:SellersItemIdentification');
990
+ const id = doc.createElement('cbc:ID');
991
+ id.textContent = item.articleNumber;
992
+ sellerId.appendChild(id);
993
+ itemElement.appendChild(sellerId);
994
+ }
995
+
996
+ // Add BuyersItemIdentification
997
+ if (itemMetadata.buyerItemID && !itemElement.getElementsByTagName('cac:BuyersItemIdentification')[0]) {
998
+ const buyerId = doc.createElement('cac:BuyersItemIdentification');
999
+ const id = doc.createElement('cbc:ID');
1000
+ id.textContent = itemMetadata.buyerItemID;
1001
+ buyerId.appendChild(id);
1002
+ itemElement.appendChild(buyerId);
1003
+ }
1004
+
1005
+ // Add StandardItemIdentification
1006
+ if (itemMetadata.standardItemID && !itemElement.getElementsByTagName('cac:StandardItemIdentification')[0]) {
1007
+ const standardId = doc.createElement('cac:StandardItemIdentification');
1008
+ const id = doc.createElement('cbc:ID');
1009
+ id.textContent = itemMetadata.standardItemID;
1010
+ standardId.appendChild(id);
1011
+ itemElement.appendChild(standardId);
1012
+ }
1013
+
1014
+ // Add CommodityClassification
1015
+ if (itemMetadata.commodityClassification && !itemElement.getElementsByTagName('cac:CommodityClassification')[0]) {
1016
+ const classification = doc.createElement('cac:CommodityClassification');
1017
+ const code = doc.createElement('cbc:ItemClassificationCode');
1018
+ code.textContent = itemMetadata.commodityClassification;
1019
+ classification.appendChild(code);
1020
+ itemElement.appendChild(classification);
1021
+ }
1022
+
1023
+ // Add additional item properties
1024
+ if (itemMetadata.additionalProperties) {
1025
+ for (const [propName, propValue] of Object.entries(itemMetadata.additionalProperties)) {
1026
+ const additionalProp = doc.createElement('cac:AdditionalItemProperty');
1027
+
1028
+ const nameElement = doc.createElement('cbc:Name');
1029
+ nameElement.textContent = propName;
1030
+ additionalProp.appendChild(nameElement);
1031
+
1032
+ const valueElement = doc.createElement('cbc:Value');
1033
+ valueElement.textContent = propValue as string;
1034
+ additionalProp.appendChild(valueElement);
1035
+
1036
+ itemElement.appendChild(additionalProp);
1037
+ }
1038
+ }
1039
+ }
1040
+ }
1041
+ }