@fin.cx/einvoice 5.0.2 → 5.1.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 (116) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/einvoice.d.ts +4 -8
  3. package/dist_ts/einvoice.js +46 -7
  4. package/dist_ts/formats/converters/xml-to-einvoice.converter.d.ts +21 -0
  5. package/dist_ts/formats/converters/xml-to-einvoice.converter.js +127 -0
  6. package/dist_ts/formats/semantic/bt-bg.model.d.ts +445 -0
  7. package/dist_ts/formats/semantic/bt-bg.model.js +12 -0
  8. package/dist_ts/formats/semantic/semantic.adapter.d.ts +87 -0
  9. package/dist_ts/formats/semantic/semantic.adapter.js +538 -0
  10. package/dist_ts/formats/semantic/semantic.validator.d.ts +48 -0
  11. package/dist_ts/formats/semantic/semantic.validator.js +596 -0
  12. package/dist_ts/formats/utils/currency.calculator.decimal.d.ts +98 -0
  13. package/dist_ts/formats/utils/currency.calculator.decimal.js +212 -0
  14. package/dist_ts/formats/utils/currency.utils.d.ts +99 -0
  15. package/dist_ts/formats/utils/currency.utils.js +250 -0
  16. package/dist_ts/formats/utils/decimal.d.ts +151 -0
  17. package/dist_ts/formats/utils/decimal.js +435 -0
  18. package/dist_ts/formats/validation/codelist.validator.d.ts +49 -0
  19. package/dist_ts/formats/validation/codelist.validator.js +188 -0
  20. package/dist_ts/formats/validation/conformance.harness.d.ts +99 -0
  21. package/dist_ts/formats/validation/conformance.harness.js +471 -0
  22. package/dist_ts/formats/validation/en16931.business-rules.validator.d.ts +58 -0
  23. package/dist_ts/formats/validation/en16931.business-rules.validator.js +459 -0
  24. package/dist_ts/formats/validation/facturx.validator.d.ts +75 -0
  25. package/dist_ts/formats/validation/facturx.validator.js +522 -0
  26. package/dist_ts/formats/validation/integrated.validator.d.ts +82 -0
  27. package/dist_ts/formats/validation/integrated.validator.js +311 -0
  28. package/dist_ts/formats/validation/peppol.validator.d.ts +77 -0
  29. package/dist_ts/formats/validation/peppol.validator.js +529 -0
  30. package/dist_ts/formats/validation/schematron.downloader.d.ts +71 -0
  31. package/dist_ts/formats/validation/schematron.downloader.js +254 -0
  32. package/dist_ts/formats/validation/schematron.integration.d.ts +60 -0
  33. package/dist_ts/formats/validation/schematron.integration.js +230 -0
  34. package/dist_ts/formats/validation/schematron.validator.d.ts +79 -0
  35. package/dist_ts/formats/validation/schematron.validator.js +288 -0
  36. package/dist_ts/formats/validation/schematron.worker.d.ts +50 -0
  37. package/dist_ts/formats/validation/schematron.worker.js +185 -0
  38. package/dist_ts/formats/validation/validation.types.d.ts +163 -0
  39. package/dist_ts/formats/validation/validation.types.js +181 -0
  40. package/dist_ts/formats/validation/vat-categories.validator.d.ts +87 -0
  41. package/dist_ts/formats/validation/vat-categories.validator.js +497 -0
  42. package/dist_ts/formats/validation/xrechnung.validator.d.ts +92 -0
  43. package/dist_ts/formats/validation/xrechnung.validator.js +417 -0
  44. package/dist_ts/interfaces/common.d.ts +1 -0
  45. package/dist_ts/interfaces/en16931-metadata.d.ts +83 -0
  46. package/dist_ts/interfaces/en16931-metadata.js +6 -0
  47. package/dist_ts/plugins.d.ts +3 -1
  48. package/dist_ts/plugins.js +7 -2
  49. package/dist_ts_install/download-schematron.d.ts +6 -0
  50. package/dist_ts_install/download-schematron.js +62 -0
  51. package/dist_ts_install/download-test-samples.d.ts +19 -0
  52. package/dist_ts_install/download-test-samples.js +169 -0
  53. package/dist_ts_install/download-xrechnung-rules.d.ts +7 -0
  54. package/dist_ts_install/download-xrechnung-rules.js +146 -0
  55. package/dist_ts_install/index.d.ts +8 -0
  56. package/dist_ts_install/index.js +246 -0
  57. package/package.json +13 -8
  58. package/readme.md +255 -810
  59. package/ts/00_commitinfo_data.ts +1 -1
  60. package/ts/einvoice.ts +63 -14
  61. package/ts/formats/converters/xml-to-einvoice.converter.ts +142 -0
  62. package/ts/formats/semantic/bt-bg.model.ts +524 -0
  63. package/ts/formats/semantic/semantic.adapter.ts +600 -0
  64. package/ts/formats/semantic/semantic.validator.ts +654 -0
  65. package/ts/formats/utils/currency.calculator.decimal.ts +323 -0
  66. package/ts/formats/utils/currency.utils.ts +299 -0
  67. package/ts/formats/utils/decimal.ts +509 -0
  68. package/ts/formats/validation/codelist.validator.ts +317 -0
  69. package/ts/formats/validation/conformance.harness.ts +591 -0
  70. package/ts/formats/validation/en16931.business-rules.validator.ts +694 -0
  71. package/ts/formats/validation/facturx.validator.ts +579 -0
  72. package/ts/formats/validation/integrated.validator.ts +405 -0
  73. package/ts/formats/validation/peppol.validator.ts +589 -0
  74. package/ts/formats/validation/schematron.downloader.ts +304 -0
  75. package/ts/formats/validation/schematron.integration.ts +285 -0
  76. package/ts/formats/validation/schematron.validator.ts +348 -0
  77. package/ts/formats/validation/schematron.worker.ts +221 -0
  78. package/ts/formats/validation/validation.types.ts +274 -0
  79. package/ts/formats/validation/vat-categories.validator.ts +845 -0
  80. package/ts/formats/validation/xrechnung.validator.ts +494 -0
  81. package/ts/interfaces/common.ts +1 -0
  82. package/ts/interfaces/en16931-metadata.ts +104 -0
  83. package/ts/plugins.ts +8 -0
  84. package/ts/tspublish.json +3 -0
  85. package/dist_ts/classes.decoder.d.ts +0 -40
  86. package/dist_ts/classes.decoder.js +0 -320
  87. package/dist_ts/classes.encoder.d.ts +0 -51
  88. package/dist_ts/classes.encoder.js +0 -293
  89. package/dist_ts/classes.xinvoice.d.ts +0 -136
  90. package/dist_ts/classes.xinvoice.js +0 -352
  91. package/dist_ts/formats/base.decoder.d.ts +0 -19
  92. package/dist_ts/formats/base.decoder.js +0 -130
  93. package/dist_ts/formats/base.validator.d.ts +0 -44
  94. package/dist_ts/formats/base.validator.js +0 -39
  95. package/dist_ts/formats/decoder.factory.d.ts +0 -15
  96. package/dist_ts/formats/decoder.factory.js +0 -46
  97. package/dist_ts/formats/factorx.decoder.d.ts +0 -18
  98. package/dist_ts/formats/factorx.decoder.js +0 -178
  99. package/dist_ts/formats/factorx.encoder.d.ts +0 -51
  100. package/dist_ts/formats/factorx.encoder.js +0 -293
  101. package/dist_ts/formats/facturx.decoder.d.ts +0 -18
  102. package/dist_ts/formats/facturx.decoder.js +0 -210
  103. package/dist_ts/formats/facturx.encoder.d.ts +0 -51
  104. package/dist_ts/formats/facturx.encoder.js +0 -293
  105. package/dist_ts/formats/facturx.validator.d.ts +0 -67
  106. package/dist_ts/formats/facturx.validator.js +0 -266
  107. package/dist_ts/formats/pdf/robust-pdf.extractor.d.ts +0 -40
  108. package/dist_ts/formats/pdf/robust-pdf.extractor.js +0 -324
  109. package/dist_ts/formats/ubl.validator.d.ts +0 -82
  110. package/dist_ts/formats/ubl.validator.js +0 -306
  111. package/dist_ts/formats/validator.factory.d.ts +0 -18
  112. package/dist_ts/formats/validator.factory.js +0 -78
  113. package/dist_ts/formats/xinvoice.decoder.d.ts +0 -28
  114. package/dist_ts/formats/xinvoice.decoder.js +0 -332
  115. package/dist_ts/formats/xinvoice.encoder.d.ts +0 -19
  116. package/dist_ts/formats/xinvoice.encoder.js +0 -282
@@ -0,0 +1,600 @@
1
+ /**
2
+ * Adapter for converting between EInvoice and EN16931 Semantic Model
3
+ * Provides bidirectional conversion capabilities
4
+ */
5
+
6
+ import { EInvoice } from '../../einvoice.js';
7
+ import type {
8
+ EN16931SemanticModel,
9
+ Seller,
10
+ Buyer,
11
+ PostalAddress,
12
+ Contact,
13
+ InvoiceLine,
14
+ VATBreakdown,
15
+ DocumentTotals,
16
+ PaymentInstructions,
17
+ Allowance,
18
+ Charge,
19
+ Period,
20
+ DeliveryInformation,
21
+ PriceDetails,
22
+ VATInformation,
23
+ ItemInformation
24
+ } from './bt-bg.model.js';
25
+
26
+ /**
27
+ * Adapter for converting between EInvoice and EN16931 Semantic Model
28
+ */
29
+ export class SemanticModelAdapter {
30
+ /**
31
+ * Convert EInvoice to EN16931 Semantic Model
32
+ */
33
+ public toSemanticModel(invoice: EInvoice): EN16931SemanticModel {
34
+ return {
35
+ // Core document information
36
+ documentInformation: {
37
+ invoiceNumber: invoice.accountingDocId,
38
+ issueDate: invoice.issueDate,
39
+ typeCode: this.mapInvoiceType(invoice.accountingDocType),
40
+ currencyCode: invoice.currency,
41
+ notes: invoice.notes ? this.mapNotes(invoice.notes) : undefined
42
+ },
43
+
44
+ // Process metadata
45
+ processControl: invoice.metadata?.profileId ? {
46
+ businessProcessType: invoice.metadata?.extensions?.businessProcessId,
47
+ specificationIdentifier: invoice.metadata.profileId
48
+ } : undefined,
49
+
50
+ // References
51
+ references: {
52
+ buyerReference: invoice.metadata?.buyerReference,
53
+ projectReference: invoice.metadata?.extensions?.projectReference,
54
+ contractReference: invoice.metadata?.extensions?.contractReference,
55
+ purchaseOrderReference: invoice.metadata?.extensions?.purchaseOrderReference,
56
+ salesOrderReference: invoice.metadata?.extensions?.salesOrderReference,
57
+ precedingInvoices: invoice.metadata?.extensions?.precedingInvoices
58
+ },
59
+
60
+ // Seller
61
+ seller: {
62
+ ...this.mapSeller(invoice.from),
63
+ postalAddress: this.mapAddress(invoice.from),
64
+ contact: this.mapContact(invoice.from)
65
+ },
66
+
67
+ // Buyer
68
+ buyer: {
69
+ ...this.mapBuyer(invoice.to),
70
+ postalAddress: this.mapAddress(invoice.to),
71
+ contact: this.mapContact(invoice.to)
72
+ },
73
+
74
+ // Payee (if different from seller)
75
+ payee: invoice.metadata?.extensions?.payee,
76
+
77
+ // Tax representative
78
+ taxRepresentative: invoice.metadata?.extensions?.taxRepresentative,
79
+
80
+ // Delivery
81
+ delivery: this.mapDelivery(invoice),
82
+
83
+ // Invoice period
84
+ invoicingPeriod: invoice.metadata?.extensions?.invoicingPeriod ? {
85
+ startDate: invoice.metadata.extensions.invoicingPeriod.startDate,
86
+ endDate: invoice.metadata.extensions.invoicingPeriod.endDate,
87
+ descriptionCode: invoice.metadata.extensions.invoicingPeriod.descriptionCode
88
+ } : undefined,
89
+
90
+ // Payment instructions
91
+ paymentInstructions: this.mapPaymentInstructions(invoice),
92
+
93
+ // Payment card info
94
+ paymentCardInfo: invoice.metadata?.extensions?.paymentCard,
95
+
96
+ // Direct debit
97
+ directDebit: invoice.metadata?.extensions?.directDebit,
98
+
99
+ // Payment terms
100
+ paymentTerms: invoice.dueInDays !== undefined ? {
101
+ note: `Payment due in ${invoice.dueInDays} days`
102
+ } : undefined,
103
+
104
+ // Document level allowances and charges
105
+ documentLevelAllowances: invoice.metadata?.extensions?.documentAllowances,
106
+ documentLevelCharges: invoice.metadata?.extensions?.documentCharges,
107
+
108
+ // Document totals
109
+ documentTotals: this.mapDocumentTotals(invoice),
110
+
111
+ // VAT breakdown
112
+ vatBreakdown: this.mapVATBreakdown(invoice),
113
+
114
+ // Additional documents
115
+ additionalDocuments: invoice.metadata?.extensions?.supportingDocuments,
116
+
117
+ // Invoice lines
118
+ invoiceLines: this.mapInvoiceLines(invoice.items || [])
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Convert EN16931 Semantic Model to EInvoice
124
+ */
125
+ public fromSemanticModel(model: EN16931SemanticModel): EInvoice {
126
+ const invoice = new EInvoice();
127
+ invoice.accountingDocId = model.documentInformation.invoiceNumber;
128
+ invoice.issueDate = model.documentInformation.issueDate;
129
+ invoice.accountingDocType = this.reverseMapInvoiceType(model.documentInformation.typeCode) as 'invoice';
130
+ invoice.currency = model.documentInformation.currencyCode as any;
131
+ invoice.from = this.reverseMapSeller(model.seller);
132
+ invoice.to = this.reverseMapBuyer(model.buyer);
133
+ invoice.items = this.reverseMapInvoiceLines(model.invoiceLines);
134
+
135
+ // Set metadata
136
+ if (model.processControl) {
137
+ invoice.metadata = {
138
+ ...invoice.metadata,
139
+ profileId: model.processControl.specificationIdentifier,
140
+ extensions: {
141
+ ...invoice.metadata?.extensions,
142
+ businessProcessId: model.processControl.businessProcessType
143
+ }
144
+ };
145
+ }
146
+
147
+ // Set references
148
+ if (model.references) {
149
+ invoice.metadata = {
150
+ ...invoice.metadata,
151
+ buyerReference: model.references.buyerReference,
152
+ extensions: {
153
+ ...invoice.metadata?.extensions,
154
+ contractReference: model.references.contractReference,
155
+ purchaseOrderReference: model.references.purchaseOrderReference,
156
+ salesOrderReference: model.references.salesOrderReference,
157
+ precedingInvoices: model.references.precedingInvoices,
158
+ projectReference: model.references.projectReference
159
+ }
160
+ };
161
+ }
162
+
163
+ // Set payment terms
164
+ if (model.paymentTerms?.note) {
165
+ const daysMatch = model.paymentTerms.note.match(/(\d+) days/);
166
+ if (daysMatch) {
167
+ invoice.dueInDays = parseInt(daysMatch[1], 10);
168
+ }
169
+ }
170
+
171
+ // Set payment options
172
+ if (model.paymentInstructions.paymentAccountIdentifier) {
173
+ invoice.paymentOptions = {
174
+ sepa: {
175
+ iban: model.paymentInstructions.paymentAccountIdentifier,
176
+ bic: model.paymentInstructions.paymentServiceProviderIdentifier
177
+ },
178
+ bankInfo: {
179
+ accountHolder: model.paymentInstructions.paymentAccountName || '',
180
+ institutionName: model.paymentInstructions.paymentServiceProviderIdentifier || ''
181
+ }
182
+ } as any;
183
+ }
184
+
185
+ // Set extensions
186
+ if (model.payee || model.taxRepresentative || model.documentLevelAllowances) {
187
+ invoice.metadata = {
188
+ ...invoice.metadata,
189
+ extensions: {
190
+ ...invoice.metadata?.extensions,
191
+ payee: model.payee,
192
+ taxRepresentative: model.taxRepresentative,
193
+ documentAllowances: model.documentLevelAllowances,
194
+ documentCharges: model.documentLevelCharges,
195
+ supportingDocuments: model.additionalDocuments,
196
+ paymentCard: model.paymentCardInfo,
197
+ directDebit: model.directDebit,
198
+ taxDetails: model.vatBreakdown
199
+ }
200
+ };
201
+ }
202
+
203
+ return invoice;
204
+ }
205
+
206
+ /**
207
+ * Map invoice type code
208
+ */
209
+ private mapInvoiceType(type: string): string {
210
+ const typeMap: Record<string, string> = {
211
+ 'invoice': '380',
212
+ 'creditNote': '381',
213
+ 'debitNote': '383',
214
+ 'correctedInvoice': '384',
215
+ 'prepaymentInvoice': '386',
216
+ 'selfBilledInvoice': '389',
217
+ 'invoice_380': '380',
218
+ 'credit_note_381': '381'
219
+ };
220
+ return typeMap[type] || '380';
221
+ }
222
+
223
+ /**
224
+ * Reverse map invoice type code
225
+ */
226
+ private reverseMapInvoiceType(code: string): string {
227
+ const typeMap: Record<string, string> = {
228
+ '380': 'invoice',
229
+ '381': 'creditNote',
230
+ '383': 'debitNote',
231
+ '384': 'correctedInvoice',
232
+ '386': 'prepaymentInvoice',
233
+ '389': 'selfBilledInvoice'
234
+ };
235
+ return typeMap[code] || 'invoice';
236
+ }
237
+
238
+ /**
239
+ * Map notes
240
+ */
241
+ private mapNotes(notes: string | string[]): Array<{ noteContent: string }> {
242
+ const notesArray = Array.isArray(notes) ? notes : [notes];
243
+ return notesArray.map(note => ({ noteContent: note }));
244
+ }
245
+
246
+ /**
247
+ * Map seller information
248
+ */
249
+ private mapSeller(from: EInvoice['from']): Seller {
250
+ const contact = from as any;
251
+ if (contact.type === 'company') {
252
+ return {
253
+ name: contact.name || '',
254
+ tradingName: contact.tradingName,
255
+ identifier: contact.registrationDetails?.registrationId,
256
+ legalRegistrationIdentifier: contact.registrationDetails?.registrationId,
257
+ vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
258
+ taxRegistrationIdentifier: contact.taxId,
259
+ additionalLegalInfo: contact.description,
260
+ electronicAddress: contact.email || contact.contact?.email
261
+ };
262
+ } else {
263
+ return {
264
+ name: contact.name || `${contact.firstName || ''} ${contact.lastName || ''}`.trim(),
265
+ identifier: contact.registrationDetails?.registrationId,
266
+ vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
267
+ electronicAddress: contact.email
268
+ };
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Map buyer information
274
+ */
275
+ private mapBuyer(to: EInvoice['to']): Buyer {
276
+ const contact = to as any;
277
+ if (contact.type === 'company') {
278
+ return {
279
+ name: contact.name || '',
280
+ tradingName: contact.tradingName,
281
+ identifier: contact.registrationDetails?.registrationId,
282
+ legalRegistrationIdentifier: contact.registrationDetails?.registrationId,
283
+ vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
284
+ electronicAddress: contact.email || contact.contact?.email
285
+ };
286
+ } else {
287
+ return {
288
+ name: contact.name || `${contact.firstName || ''} ${contact.lastName || ''}`.trim(),
289
+ identifier: contact.registrationDetails?.registrationId,
290
+ vatIdentifier: contact.registrationDetails?.vatId || contact.vatNumber,
291
+ electronicAddress: contact.email
292
+ };
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Map address
298
+ */
299
+ private mapAddress(party: EInvoice['from'] | EInvoice['to']): PostalAddress {
300
+ const contact = party as any;
301
+ const address: PostalAddress = {
302
+ countryCode: contact.address?.country || contact.country || ''
303
+ };
304
+
305
+ if (contact.address) {
306
+ if (typeof contact.address === 'string') {
307
+ const addressParts = contact.address.split(',').map((s: string) => s.trim());
308
+ address.addressLine1 = addressParts[0];
309
+ if (addressParts.length > 1) address.addressLine2 = addressParts[1];
310
+ } else if (typeof contact.address === 'object') {
311
+ address.addressLine1 = [contact.address.streetName, contact.address.houseNumber].filter(Boolean).join(' ');
312
+ address.city = contact.address.city;
313
+ address.postCode = contact.address.postalCode;
314
+ address.countryCode = contact.address.country || address.countryCode;
315
+ }
316
+ }
317
+
318
+ // Support both nested and flat structures
319
+ if (!address.city) address.city = contact.city;
320
+ if (!address.postCode) address.postCode = contact.postalCode;
321
+
322
+ return address;
323
+ }
324
+
325
+ /**
326
+ * Map contact information
327
+ */
328
+ private mapContact(party: EInvoice['from'] | EInvoice['to']): Contact | undefined {
329
+ const contact = party as any;
330
+ if (contact.type === 'company' && contact.contact) {
331
+ return {
332
+ contactPoint: contact.contact.name,
333
+ telephoneNumber: contact.contact.phone,
334
+ emailAddress: contact.contact.email
335
+ };
336
+ } else if (contact.type === 'person') {
337
+ return {
338
+ contactPoint: contact.name || `${contact.firstName || ''} ${contact.lastName || ''}`.trim(),
339
+ telephoneNumber: contact.phone,
340
+ emailAddress: contact.email
341
+ };
342
+ } else if (contact.email || contact.phone) {
343
+ // Fallback for any contact with email or phone
344
+ return {
345
+ contactPoint: contact.name,
346
+ telephoneNumber: contact.phone,
347
+ emailAddress: contact.email
348
+ };
349
+ }
350
+ return undefined;
351
+ }
352
+
353
+ /**
354
+ * Map delivery information
355
+ */
356
+ private mapDelivery(invoice: EInvoice): DeliveryInformation | undefined {
357
+ const delivery = invoice.metadata?.extensions?.delivery;
358
+ if (!delivery) return undefined;
359
+
360
+ return {
361
+ name: delivery.name,
362
+ locationIdentifier: delivery.locationId,
363
+ actualDeliveryDate: delivery.actualDate,
364
+ deliveryAddress: delivery.address ? {
365
+ addressLine1: delivery.address.line1,
366
+ addressLine2: delivery.address.line2,
367
+ city: delivery.address.city,
368
+ postCode: delivery.address.postCode,
369
+ countryCode: delivery.address.countryCode
370
+ } : undefined
371
+ };
372
+ }
373
+
374
+ /**
375
+ * Map payment instructions
376
+ */
377
+ private mapPaymentInstructions(invoice: EInvoice): PaymentInstructions {
378
+ const paymentMeans = invoice.metadata?.extensions?.paymentMeans;
379
+ const paymentAccount = invoice.metadata?.extensions?.paymentAccount;
380
+
381
+ return {
382
+ paymentMeansTypeCode: paymentMeans?.paymentMeansCode || '30', // Default to credit transfer
383
+ paymentMeansText: paymentMeans?.paymentMeansText,
384
+ remittanceInformation: paymentMeans?.remittanceInformation,
385
+ paymentAccountIdentifier: paymentAccount?.iban,
386
+ paymentAccountName: paymentAccount?.accountName,
387
+ paymentServiceProviderIdentifier: paymentAccount?.bic || paymentAccount?.institutionName
388
+ };
389
+ }
390
+
391
+ /**
392
+ * Map document totals
393
+ */
394
+ private mapDocumentTotals(invoice: EInvoice): DocumentTotals {
395
+ return {
396
+ lineExtensionAmount: invoice.totalNet,
397
+ taxExclusiveAmount: invoice.totalNet,
398
+ taxInclusiveAmount: invoice.totalGross,
399
+ allowanceTotalAmount: invoice.metadata?.extensions?.documentAllowances?.reduce(
400
+ (sum, a) => sum + a.amount, 0
401
+ ),
402
+ chargeTotalAmount: invoice.metadata?.extensions?.documentCharges?.reduce(
403
+ (sum, c) => sum + c.amount, 0
404
+ ),
405
+ prepaidAmount: invoice.metadata?.extensions?.prepaidAmount,
406
+ roundingAmount: invoice.metadata?.extensions?.roundingAmount,
407
+ payableAmount: invoice.totalGross
408
+ };
409
+ }
410
+
411
+ /**
412
+ * Map VAT breakdown
413
+ */
414
+ private mapVATBreakdown(invoice: EInvoice): VATBreakdown[] | undefined {
415
+ const taxDetails = invoice.metadata?.extensions?.taxDetails;
416
+ if (!taxDetails) {
417
+ // Create default VAT breakdown from invoice totals
418
+ if (invoice.totalVat > 0) {
419
+ return [{
420
+ vatCategoryTaxableAmount: invoice.totalNet,
421
+ vatCategoryTaxAmount: invoice.totalVat,
422
+ vatCategoryCode: 'S', // Standard rate
423
+ vatCategoryRate: (invoice.totalVat / invoice.totalNet) * 100
424
+ }];
425
+ }
426
+ return undefined;
427
+ }
428
+
429
+ return taxDetails as VATBreakdown[];
430
+ }
431
+
432
+ /**
433
+ * Map invoice lines
434
+ */
435
+ private mapInvoiceLines(items: EInvoice['items']): InvoiceLine[] {
436
+ if (!items) return [];
437
+
438
+ return items.map((item, index) => ({
439
+ identifier: (index + 1).toString(),
440
+ note: (item as any).description || (item as any).text || '',
441
+ invoicedQuantity: item.unitQuantity,
442
+ invoicedQuantityUnitOfMeasureCode: item.unitType || 'C62',
443
+ lineExtensionAmount: item.unitNetPrice * item.unitQuantity,
444
+ purchaseOrderLineReference: (item as any).purchaseOrderLineRef,
445
+ buyerAccountingReference: (item as any).buyerAccountingRef,
446
+ period: (item as any).period,
447
+ allowances: (item as any).allowances,
448
+ charges: (item as any).charges,
449
+ priceDetails: {
450
+ itemNetPrice: item.unitNetPrice,
451
+ itemPriceDiscount: (item as any).priceDiscount,
452
+ itemGrossPrice: (item as any).grossPrice,
453
+ itemPriceBaseQuantity: (item as any).priceBaseQuantity || 1
454
+ },
455
+ vatInformation: {
456
+ categoryCode: this.mapVATCategory(item.vatPercentage),
457
+ rate: item.vatPercentage
458
+ },
459
+ itemInformation: {
460
+ name: item.name,
461
+ description: (item as any).description || (item as any).text || '',
462
+ sellersIdentifier: item.articleNumber,
463
+ buyersIdentifier: (item as any).buyersItemId,
464
+ standardIdentifier: (item as any).gtin || (item as any).ean,
465
+ classificationIdentifier: (item as any).unspsc,
466
+ originCountryCode: (item as any).originCountry,
467
+ attributes: (item as any).attributes
468
+ }
469
+ }));
470
+ }
471
+
472
+ /**
473
+ * Map VAT category from percentage
474
+ */
475
+ private mapVATCategory(percentage?: number): string {
476
+ if (percentage === undefined || percentage === null) return 'S';
477
+ if (percentage === 0) return 'Z';
478
+ if (percentage > 0) return 'S';
479
+ return 'E'; // Exempt
480
+ }
481
+
482
+ /**
483
+ * Reverse map seller
484
+ */
485
+ private reverseMapSeller(seller: Seller & { postalAddress: PostalAddress }): EInvoice['from'] {
486
+ const isCompany = seller.legalRegistrationIdentifier || seller.tradingName;
487
+
488
+ return {
489
+ type: isCompany ? 'company' : 'person',
490
+ name: seller.name,
491
+ description: seller.additionalLegalInfo || '',
492
+ address: {
493
+ streetName: seller.postalAddress.addressLine1 || '',
494
+ houseNumber: '',
495
+ city: seller.postalAddress.city || '',
496
+ postalCode: seller.postalAddress.postCode || '',
497
+ country: seller.postalAddress.countryCode || ''
498
+ },
499
+ registrationDetails: {
500
+ vatId: seller.vatIdentifier || '',
501
+ registrationId: seller.identifier || seller.legalRegistrationIdentifier || '',
502
+ registrationName: seller.name
503
+ },
504
+ status: 'active',
505
+ foundedDate: {
506
+ year: new Date().getFullYear(),
507
+ month: new Date().getMonth() + 1,
508
+ day: new Date().getDate()
509
+ }
510
+ } as any;
511
+ }
512
+
513
+ /**
514
+ * Reverse map buyer
515
+ */
516
+ private reverseMapBuyer(buyer: Buyer & { postalAddress: PostalAddress }): EInvoice['to'] {
517
+ const isCompany = buyer.legalRegistrationIdentifier || buyer.tradingName;
518
+
519
+ return {
520
+ type: isCompany ? 'company' : 'person',
521
+ name: buyer.name,
522
+ description: '',
523
+ address: {
524
+ streetName: buyer.postalAddress.addressLine1 || '',
525
+ houseNumber: '',
526
+ city: buyer.postalAddress.city || '',
527
+ postalCode: buyer.postalAddress.postCode || '',
528
+ country: buyer.postalAddress.countryCode || ''
529
+ },
530
+ registrationDetails: {
531
+ vatId: buyer.vatIdentifier || '',
532
+ registrationId: buyer.identifier || buyer.legalRegistrationIdentifier || '',
533
+ registrationName: buyer.name
534
+ },
535
+ status: 'active',
536
+ foundedDate: {
537
+ year: new Date().getFullYear(),
538
+ month: new Date().getMonth() + 1,
539
+ day: new Date().getDate()
540
+ }
541
+ } as any;
542
+ }
543
+
544
+ /**
545
+ * Reverse map invoice lines
546
+ */
547
+ private reverseMapInvoiceLines(lines: InvoiceLine[]): EInvoice['items'] {
548
+ return lines.map((line, index) => ({
549
+ position: index + 1,
550
+ name: line.itemInformation.name,
551
+ description: line.itemInformation.description || '',
552
+ unitQuantity: line.invoicedQuantity,
553
+ unitType: line.invoicedQuantityUnitOfMeasureCode,
554
+ unitNetPrice: line.priceDetails.itemNetPrice,
555
+ vatPercentage: line.vatInformation.rate || 0,
556
+ articleNumber: line.itemInformation.sellersIdentifier || ''
557
+ }));
558
+ }
559
+
560
+ /**
561
+ * Validate semantic model completeness
562
+ */
563
+ public validateSemanticModel(model: EN16931SemanticModel): string[] {
564
+ const errors: string[] = [];
565
+
566
+ // Check mandatory fields
567
+ if (!model.documentInformation.invoiceNumber) {
568
+ errors.push('BT-1: Invoice number is mandatory');
569
+ }
570
+ if (!model.documentInformation.issueDate) {
571
+ errors.push('BT-2: Invoice issue date is mandatory');
572
+ }
573
+ if (!model.documentInformation.typeCode) {
574
+ errors.push('BT-3: Invoice type code is mandatory');
575
+ }
576
+ if (!model.documentInformation.currencyCode) {
577
+ errors.push('BT-5: Invoice currency code is mandatory');
578
+ }
579
+ if (!model.seller?.name) {
580
+ errors.push('BT-27: Seller name is mandatory');
581
+ }
582
+ if (!model.seller?.postalAddress?.countryCode) {
583
+ errors.push('BT-40: Seller country code is mandatory');
584
+ }
585
+ if (!model.buyer?.name) {
586
+ errors.push('BT-44: Buyer name is mandatory');
587
+ }
588
+ if (!model.buyer?.postalAddress?.countryCode) {
589
+ errors.push('BT-55: Buyer country code is mandatory');
590
+ }
591
+ if (!model.documentTotals) {
592
+ errors.push('BG-22: Document totals are mandatory');
593
+ }
594
+ if (!model.invoiceLines || model.invoiceLines.length === 0) {
595
+ errors.push('BG-25: At least one invoice line is mandatory');
596
+ }
597
+
598
+ return errors;
599
+ }
600
+ }