@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,267 @@
1
+ import { CIIBaseDecoder } from '../cii.decoder.js';
2
+ import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js';
3
+ import { ZUGFERD_V1_NAMESPACES } from '../cii.types.js';
4
+ import { business, finance } from '../../../plugins.js';
5
+ import { EN16931Validator } from '../../validation/en16931.validator.js';
6
+
7
+ /**
8
+ * Decoder for ZUGFeRD v1 invoice format
9
+ */
10
+ export class ZUGFeRDV1Decoder extends CIIBaseDecoder {
11
+ /**
12
+ * Constructor
13
+ * @param xml XML string to decode
14
+ * @param skipValidation Whether to skip EN16931 validation
15
+ */
16
+ constructor(xml: string, skipValidation: boolean = false) {
17
+ super(xml, skipValidation);
18
+ // Override namespaces for ZUGFeRD v1
19
+ this.namespaces = {
20
+ rsm: ZUGFERD_V1_NAMESPACES.RSM,
21
+ ram: ZUGFERD_V1_NAMESPACES.RAM,
22
+ udt: ZUGFERD_V1_NAMESPACES.UDT
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Decodes a ZUGFeRD v1 credit note
28
+ * @returns Promise resolving to a TCreditNote object
29
+ */
30
+ protected async decodeCreditNote(): Promise<TCreditNote> {
31
+ // Get common invoice data
32
+ const commonData = await this.extractCommonData();
33
+
34
+ // Create a credit note with the common data
35
+ return {
36
+ ...commonData,
37
+ accountingDocType: 'creditnote' as const
38
+ } as unknown as TCreditNote;
39
+ }
40
+
41
+ /**
42
+ * Decodes a ZUGFeRD v1 debit note (invoice)
43
+ * @returns Promise resolving to a TDebitNote object
44
+ */
45
+ protected async decodeDebitNote(): Promise<TDebitNote> {
46
+ // Get common invoice data
47
+ const commonData = await this.extractCommonData();
48
+
49
+ // Create a debit note with the common data
50
+ return {
51
+ ...commonData,
52
+ accountingDocType: 'debitnote' as const
53
+ } as unknown as TDebitNote;
54
+ }
55
+
56
+ /**
57
+ * Extracts common invoice data from ZUGFeRD v1 XML
58
+ * @returns Common invoice data
59
+ */
60
+ private async extractCommonData(): Promise<Partial<TInvoice>> {
61
+ // Extract invoice ID
62
+ const invoiceId = this.getText('//ram:ID');
63
+
64
+ // Extract issue date
65
+ const issueDateStr = this.getText('//ram:IssueDateTime/udt:DateTimeString');
66
+ const issueDateFormat = this.getText('//ram:IssueDateTime/udt:DateTimeString/@format');
67
+ const issueDate = this.parseCIIDate(issueDateStr, issueDateFormat);
68
+
69
+ // Extract seller information
70
+ const seller = this.extractParty('//ram:SellerTradeParty');
71
+
72
+ // Extract buyer information
73
+ const buyer = this.extractParty('//ram:BuyerTradeParty');
74
+
75
+ // Extract items
76
+ const items = this.extractItems();
77
+
78
+ // Extract due date
79
+ const dueDateStr = this.getText('//ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime/udt:DateTimeString');
80
+ const dueDate = dueDateStr ? new Date(dueDateStr).getTime() : Date.now();
81
+ const dueInDays = Math.round((dueDate - issueDate) / (1000 * 60 * 60 * 24));
82
+
83
+ // Extract currency
84
+ const currencyCode = this.getText('//ram:InvoiceCurrencyCode') || 'EUR';
85
+
86
+ // Extract total amount (not used in this implementation but could be useful)
87
+ // const totalAmount = this.getNumber('//ram:GrandTotalAmount');
88
+
89
+ // Extract notes
90
+ const allNotes = this.extractNotes();
91
+
92
+ // Extract subject and notes separately
93
+ // If we have notes, the first one might be the subject
94
+ let subject = `Invoice ${invoiceId}`;
95
+ let notes = [...allNotes];
96
+
97
+ // If the first note doesn't look like a payment term or other standard note,
98
+ // treat it as the subject
99
+ if (allNotes.length > 0 && !allNotes[0].toLowerCase().includes('due in') &&
100
+ !allNotes[0].toLowerCase().includes('payment')) {
101
+ subject = allNotes[0];
102
+ notes = allNotes.slice(1); // Remove subject from notes
103
+ }
104
+
105
+ // Check for reverse charge
106
+ const reverseCharge = this.exists('//ram:SpecifiedTradeAllowanceCharge/ram:ReasonCode[text()="62"]');
107
+
108
+ // Create the common invoice data
109
+ const invoiceData = {
110
+ type: 'accounting-doc' as const,
111
+ accountingDocType: 'invoice' as const,
112
+ id: invoiceId,
113
+ accountingDocId: invoiceId,
114
+ date: issueDate,
115
+ accountingDocStatus: 'issued' as const,
116
+ versionInfo: {
117
+ type: 'final' as const,
118
+ version: '1.0.0'
119
+ },
120
+ language: 'en',
121
+ incidenceId: invoiceId,
122
+ from: seller,
123
+ to: buyer,
124
+ subject: subject,
125
+ items: items,
126
+ dueInDays: dueInDays,
127
+ reverseCharge: reverseCharge,
128
+ currency: currencyCode as finance.TCurrency,
129
+ notes: notes,
130
+ deliveryDate: issueDate,
131
+ objectActions: []
132
+ };
133
+
134
+ return invoiceData;
135
+ }
136
+
137
+ /**
138
+ * Extracts party information from ZUGFeRD v1 XML
139
+ * @param partyXPath XPath to the party node
140
+ * @returns Party information as TContact
141
+ */
142
+ private extractParty(partyXPath: string): business.TContact {
143
+ // Extract name
144
+ const name = this.getText(`${partyXPath}/ram:Name`);
145
+
146
+ // Extract address
147
+ const streetName = this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:LineOne`);
148
+ const city = this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:CityName`);
149
+ const postalCode = this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:PostcodeCode`);
150
+ const country = this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:CountryID`);
151
+
152
+ // Try to extract house number from street if possible
153
+ let houseNumber = '';
154
+ const streetParts = streetName.match(/^(.*?)\s+(\d+.*)$/);
155
+ if (streetParts) {
156
+ // If we can split into street name and house number
157
+ houseNumber = streetParts[2];
158
+ }
159
+
160
+ // Create address object
161
+ const address = {
162
+ streetName: streetName,
163
+ houseNumber: houseNumber,
164
+ city: city,
165
+ postalCode: postalCode,
166
+ countryCode: country
167
+ };
168
+
169
+ // Extract VAT ID
170
+ const vatId = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]`) || '';
171
+
172
+ // Extract registration ID
173
+ const registrationId = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]`) || '';
174
+
175
+ // Create contact object
176
+ return {
177
+ type: 'company',
178
+ name: name,
179
+ description: '',
180
+ address: address,
181
+ status: 'active',
182
+ foundedDate: this.createDefaultDate(),
183
+ registrationDetails: {
184
+ vatId: vatId,
185
+ registrationId: registrationId,
186
+ registrationName: ''
187
+ }
188
+ } as business.TContact;
189
+ }
190
+
191
+ /**
192
+ * Extracts invoice items from ZUGFeRD v1 XML
193
+ * @returns Array of invoice items
194
+ */
195
+ private extractItems(): finance.TAccountingDocItem[] {
196
+ const items: finance.TAccountingDocItem[] = [];
197
+
198
+ // Get all item nodes
199
+ const itemNodes = this.select('//ram:IncludedSupplyChainTradeLineItem', this.doc);
200
+
201
+ // Process each item
202
+ if (Array.isArray(itemNodes)) {
203
+ for (let i = 0; i < itemNodes.length; i++) {
204
+ const itemNode = itemNodes[i];
205
+
206
+ // Extract item data
207
+ const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode);
208
+ const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode);
209
+ const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode);
210
+ const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode) || 'EA';
211
+ const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode);
212
+ const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode);
213
+
214
+ // Create item object
215
+ items.push({
216
+ position: i + 1,
217
+ name: name,
218
+ articleNumber: articleNumber,
219
+ unitType: unitType,
220
+ unitQuantity: unitQuantity,
221
+ unitNetPrice: unitNetPrice,
222
+ vatPercentage: vatPercentage
223
+ });
224
+ }
225
+ }
226
+
227
+ return items;
228
+ }
229
+
230
+ /**
231
+ * Extracts notes from ZUGFeRD v1 XML
232
+ * @returns Array of notes
233
+ */
234
+ private extractNotes(): string[] {
235
+ const notes: string[] = [];
236
+
237
+ // Get all note nodes
238
+ const noteNodes = this.select('//ram:IncludedNote', this.doc);
239
+
240
+ // Process each note
241
+ if (Array.isArray(noteNodes)) {
242
+ for (let i = 0; i < noteNodes.length; i++) {
243
+ const noteNode = noteNodes[i];
244
+ const noteText = this.getText('ram:Content', noteNode);
245
+
246
+ if (noteText) {
247
+ notes.push(noteText);
248
+ }
249
+ }
250
+ }
251
+
252
+ return notes;
253
+ }
254
+
255
+ /**
256
+ * Creates a default date for empty date fields
257
+ * @returns Default date object compatible with TContact
258
+ */
259
+ private createDefaultDate(): any {
260
+ // Create a date object that will be compatible with TContact
261
+ return {
262
+ year: 2000,
263
+ month: 1,
264
+ day: 1
265
+ };
266
+ }
267
+ }
@@ -0,0 +1,31 @@
1
+ import { CIIBaseValidator } from '../cii.validator.js';
2
+
3
+ /**
4
+ * Validator for ZUGFeRD invoice format
5
+ */
6
+ export class ZUGFeRDValidator extends CIIBaseValidator {
7
+ /**
8
+ * Validates ZUGFeRD XML structure
9
+ * @returns True if structure validation passed
10
+ */
11
+ protected validateStructure(): boolean {
12
+ // Check for required elements in ZUGFeRD structure
13
+ const invoiceId = this.getText('//rsm:ExchangedDocument/ram:ID');
14
+ if (!invoiceId) {
15
+ this.addError('ZUGFERD-STRUCT-1', 'Invoice ID is required', '//rsm:ExchangedDocument/ram:ID');
16
+ return false;
17
+ }
18
+
19
+ return true;
20
+ }
21
+
22
+ /**
23
+ * Validates ZUGFeRD XML against business rules
24
+ * @returns True if business validation passed
25
+ */
26
+ protected validateBusinessRules(): boolean {
27
+ // Implement ZUGFeRD-specific business rules
28
+ // For now, we'll just use the base CII validation
29
+ return true;
30
+ }
31
+ }
@@ -0,0 +1,62 @@
1
+ import { BaseDecoder } from '../base/base.decoder.js';
2
+ import { InvoiceFormat } from '../../interfaces/common.js';
3
+ import { FormatDetector } from '../utils/format.detector.js';
4
+
5
+ // Import specific decoders
6
+ import { XRechnungDecoder } from '../ubl/xrechnung/xrechnung.decoder.js';
7
+ import { FacturXDecoder } from '../cii/facturx/facturx.decoder.js';
8
+ import { ZUGFeRDDecoder } from '../cii/zugferd/zugferd.decoder.js';
9
+ import { ZUGFeRDV1Decoder } from '../cii/zugferd/zugferd.v1.decoder.js';
10
+
11
+ /**
12
+ * Factory to create the appropriate decoder based on the XML format
13
+ */
14
+ export class DecoderFactory {
15
+ /**
16
+ * Creates a decoder for the specified XML content
17
+ * @param xml XML content to decode
18
+ * @param skipValidation Whether to skip EN16931 validation
19
+ * @returns Appropriate decoder instance
20
+ */
21
+ public static createDecoder(xml: string, skipValidation: boolean = false): BaseDecoder {
22
+ const format = FormatDetector.detectFormat(xml);
23
+
24
+ switch (format) {
25
+ case InvoiceFormat.UBL:
26
+ case InvoiceFormat.XRECHNUNG:
27
+ return new XRechnungDecoder(xml, skipValidation);
28
+
29
+ case InvoiceFormat.CII:
30
+ // For now, use Factur-X decoder for generic CII
31
+ return new FacturXDecoder(xml, skipValidation);
32
+
33
+ case InvoiceFormat.ZUGFERD:
34
+ // Determine if it's ZUGFeRD v1 or v2 based on root element
35
+ if (xml.includes('CrossIndustryDocument') ||
36
+ xml.includes('urn:ferd:CrossIndustryDocument:invoice:1p0') ||
37
+ (xml.includes('ZUGFeRD') && !xml.includes('CrossIndustryInvoice'))) {
38
+ return new ZUGFeRDV1Decoder(xml, skipValidation);
39
+ } else {
40
+ return new ZUGFeRDDecoder(xml, skipValidation);
41
+ }
42
+
43
+ case InvoiceFormat.FACTURX:
44
+ return new FacturXDecoder(xml, skipValidation);
45
+
46
+ case InvoiceFormat.FATTURAPA:
47
+ // return new FatturaPADecoder(xml);
48
+ throw new Error('FatturaPA decoder not yet implemented');
49
+
50
+ default:
51
+ // If format is unknown but contains CrossIndustryInvoice, try ZUGFeRD decoder
52
+ if (xml.includes('CrossIndustryInvoice')) {
53
+ return new ZUGFeRDDecoder(xml, skipValidation);
54
+ }
55
+ // If format is unknown but contains CrossIndustryDocument, try ZUGFeRD v1 decoder
56
+ if (xml.includes('CrossIndustryDocument')) {
57
+ return new ZUGFeRDV1Decoder(xml, skipValidation);
58
+ }
59
+ throw new Error(`Unsupported invoice format: ${format}`);
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,47 @@
1
+ import { BaseEncoder } from '../base/base.encoder.js';
2
+ import { InvoiceFormat } from '../../interfaces/common.js';
3
+ import type { ExportFormat } from '../../interfaces/common.js';
4
+
5
+ // Import specific encoders
6
+ import { UBLEncoder } from '../ubl/generic/ubl.encoder.js';
7
+ import { XRechnungEncoder } from '../ubl/xrechnung/xrechnung.encoder.js';
8
+ import { FacturXEncoder } from '../cii/facturx/facturx.encoder.js';
9
+ import { ZUGFeRDEncoder } from '../cii/zugferd/zugferd.encoder.js';
10
+
11
+ /**
12
+ * Factory to create the appropriate encoder based on the target format
13
+ */
14
+ export class EncoderFactory {
15
+ /**
16
+ * Creates an encoder for the specified format
17
+ * @param format Target format for encoding
18
+ * @returns Appropriate encoder instance
19
+ */
20
+ public static createEncoder(format: ExportFormat | InvoiceFormat): BaseEncoder {
21
+ switch (format.toLowerCase()) {
22
+ case InvoiceFormat.UBL:
23
+ case 'ubl':
24
+ return new UBLEncoder();
25
+
26
+ case InvoiceFormat.XRECHNUNG:
27
+ case 'xrechnung':
28
+ return new XRechnungEncoder();
29
+
30
+ case InvoiceFormat.CII:
31
+ // For now, use Factur-X encoder for generic CII
32
+ return new FacturXEncoder();
33
+
34
+ case InvoiceFormat.ZUGFERD:
35
+ case 'zugferd':
36
+ // Use dedicated ZUGFeRD encoder
37
+ return new ZUGFeRDEncoder();
38
+
39
+ case InvoiceFormat.FACTURX:
40
+ case 'facturx':
41
+ return new FacturXEncoder();
42
+
43
+ default:
44
+ throw new Error(`Unsupported invoice format for encoding: ${format}`);
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,134 @@
1
+ import { BaseValidator } from '../base/base.validator.js';
2
+ import { InvoiceFormat, ValidationLevel } from '../../interfaces/common.js';
3
+ import type { ValidationResult } from '../../interfaces/common.js';
4
+ import { FormatDetector } from '../utils/format.detector.js';
5
+
6
+ // Import specific validators
7
+ import { EN16931UBLValidator } from '../ubl/en16931.ubl.validator.js';
8
+ import { XRechnungValidator } from '../ubl/xrechnung.validator.js';
9
+ import { FacturXValidator } from '../cii/facturx/facturx.validator.js';
10
+ import { ZUGFeRDValidator } from '../cii/zugferd/zugferd.validator.js';
11
+
12
+ // The EN16931UBLValidator handles all UBL-based formats with proper business rules
13
+ // No need for legacy validator implementations here
14
+
15
+ /**
16
+ * FatturaPA validator implementation
17
+ * Basic implementation for Italian electronic invoices
18
+ */
19
+ class FatturaPAValidator extends BaseValidator {
20
+ validate(level: ValidationLevel = ValidationLevel.SYNTAX): ValidationResult {
21
+ // Reset errors
22
+ this.errors = [];
23
+
24
+ let valid = true;
25
+
26
+ if (level === ValidationLevel.SYNTAX) {
27
+ valid = this.validateSchema();
28
+ } else if (level === ValidationLevel.SEMANTIC || level === ValidationLevel.BUSINESS) {
29
+ valid = this.validateSchema() && this.validateBusinessRules();
30
+ }
31
+
32
+ return {
33
+ valid,
34
+ errors: this.errors,
35
+ level
36
+ };
37
+ }
38
+
39
+ protected validateSchema(): boolean {
40
+ // Basic schema validation for FatturaPA
41
+ if (!this.xml.includes('<FatturaElettronica')) {
42
+ this.addError('FATT-SCHEMA-1', 'Root element must be FatturaElettronica', '/');
43
+ return false;
44
+ }
45
+ return true;
46
+ }
47
+
48
+ protected validateBusinessRules(): boolean {
49
+ // Basic placeholder implementation - would need more detailed rules
50
+ // for a real implementation
51
+ return this.validateSchema();
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Factory to create the appropriate validator based on the XML format
57
+ */
58
+ export class ValidatorFactory {
59
+ /**
60
+ * Creates a validator for the specified XML content
61
+ * @param xml XML content to validate
62
+ * @returns Appropriate validator instance
63
+ */
64
+ public static createValidator(xml: string): BaseValidator {
65
+ try {
66
+ const format = FormatDetector.detectFormat(xml);
67
+
68
+ switch (format) {
69
+ case InvoiceFormat.UBL:
70
+ return new EN16931UBLValidator(xml);
71
+
72
+ case InvoiceFormat.XRECHNUNG:
73
+ return new XRechnungValidator(xml);
74
+
75
+ case InvoiceFormat.CII:
76
+ // For now, use Factur-X validator for generic CII
77
+ return new FacturXValidator(xml);
78
+
79
+ case InvoiceFormat.ZUGFERD:
80
+ return new ZUGFeRDValidator(xml);
81
+
82
+ case InvoiceFormat.FACTURX:
83
+ return new FacturXValidator(xml);
84
+
85
+ case InvoiceFormat.FATTURAPA:
86
+ return new FatturaPAValidator(xml);
87
+
88
+ default:
89
+ // For unknown formats, provide a generic validator that will
90
+ // mark the document as invalid but won't throw an exception
91
+ return new GenericValidator(xml, format);
92
+ }
93
+ } catch (error) {
94
+ // If an error occurs during validator creation, return a generic validator
95
+ // that will provide meaningful error information instead of throwing
96
+ console.error(`Error creating validator: ${error}`);
97
+ return new GenericValidator(xml, 'unknown');
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Generic validator for unknown or unsupported formats
104
+ * Provides meaningful validation errors instead of throwing exceptions
105
+ */
106
+ class GenericValidator extends BaseValidator {
107
+ private format: string;
108
+
109
+ constructor(xml: string, format: string) {
110
+ super(xml);
111
+ this.format = format;
112
+ this.addError(
113
+ 'GEN-1',
114
+ `Unsupported invoice format: ${format}`,
115
+ '/'
116
+ );
117
+ }
118
+
119
+ validate(level: ValidationLevel = ValidationLevel.SYNTAX): ValidationResult {
120
+ return {
121
+ valid: false,
122
+ errors: this.errors,
123
+ level
124
+ };
125
+ }
126
+
127
+ protected validateSchema(): boolean {
128
+ return false;
129
+ }
130
+
131
+ protected validateBusinessRules(): boolean {
132
+ return false;
133
+ }
134
+ }
@@ -0,0 +1,78 @@
1
+ import { PDFDocument, PDFDict, PDFName, PDFRawStream, PDFArray, PDFString } from '../../../plugins.js';
2
+ import { BaseXMLExtractor } from './base.extractor.js';
3
+
4
+ /**
5
+ * Associated files extractor for PDF/A-3 documents
6
+ * Extracts XML from associated files (AF entry in the catalog)
7
+ * Particularly useful for ZUGFeRD v1 and some Factur-X documents
8
+ */
9
+ export class AssociatedFilesExtractor extends BaseXMLExtractor {
10
+ /**
11
+ * Extract XML from a PDF buffer using associated files
12
+ * @param pdfBuffer PDF buffer
13
+ * @returns XML content or null if not found
14
+ */
15
+ public async extractXml(pdfBuffer: Uint8Array | Buffer): Promise<string | null> {
16
+ try {
17
+ const pdfDoc = await PDFDocument.load(pdfBuffer);
18
+
19
+ // Try to find associated files via the AF entry in the catalog
20
+ const afArray = pdfDoc.catalog.lookup(PDFName.of('AF'));
21
+ if (!(afArray instanceof PDFArray)) {
22
+ console.warn('No AF (Associated Files) entry found in PDF catalog');
23
+ return null;
24
+ }
25
+
26
+ // Process each associated file
27
+ for (let i = 0; i < afArray.size(); i++) {
28
+ const fileSpec = afArray.lookup(i);
29
+ if (!(fileSpec instanceof PDFDict)) {
30
+ continue;
31
+ }
32
+
33
+ // Get the file name
34
+ const fileNameObj = fileSpec.lookup(PDFName.of('F')) || fileSpec.lookup(PDFName.of('UF'));
35
+ if (!(fileNameObj instanceof PDFString)) {
36
+ continue;
37
+ }
38
+
39
+ const fileName = fileNameObj.decodeText();
40
+
41
+ // Check if it's a known invoice XML file name
42
+ const isKnownFileName = this.knownFileNames.some(
43
+ knownName => fileName.toLowerCase() === knownName.toLowerCase()
44
+ );
45
+
46
+ // Check if it's any XML file or has invoice-related keywords
47
+ const isXmlFile = fileName.toLowerCase().endsWith('.xml') ||
48
+ fileName.toLowerCase().includes('zugferd') ||
49
+ fileName.toLowerCase().includes('factur-x') ||
50
+ fileName.toLowerCase().includes('xrechnung') ||
51
+ fileName.toLowerCase().includes('invoice');
52
+
53
+ if (isKnownFileName || isXmlFile) {
54
+ // Get the embedded file dictionary
55
+ const efDict = fileSpec.lookup(PDFName.of('EF'));
56
+ if (!(efDict instanceof PDFDict)) {
57
+ continue;
58
+ }
59
+
60
+ // Get the file stream
61
+ const fileStream = efDict.lookup(PDFName.of('F'));
62
+ if (fileStream instanceof PDFRawStream) {
63
+ const xmlContent = await this.extractXmlFromStream(fileStream, fileName);
64
+ if (xmlContent) {
65
+ return xmlContent;
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ console.warn('No valid XML found in associated files');
72
+ return null;
73
+ } catch (error) {
74
+ console.error('Error in associated files extraction:', error);
75
+ return null;
76
+ }
77
+ }
78
+ }