@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.
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.decoder.d.ts +40 -0
- package/dist_ts/classes.decoder.js +320 -0
- package/dist_ts/classes.encoder.d.ts +51 -0
- package/dist_ts/classes.encoder.js +293 -0
- package/dist_ts/classes.xinvoice.d.ts +136 -0
- package/dist_ts/classes.xinvoice.js +352 -0
- package/dist_ts/einvoice.d.ts +217 -0
- package/dist_ts/einvoice.js +505 -0
- package/dist_ts/errors.d.ts +121 -0
- package/dist_ts/errors.js +241 -0
- package/dist_ts/formats/base/base.decoder.d.ts +32 -0
- package/dist_ts/formats/base/base.decoder.js +52 -0
- package/dist_ts/formats/base/base.encoder.d.ts +13 -0
- package/dist_ts/formats/base/base.encoder.js +7 -0
- package/dist_ts/formats/base/base.validator.d.ts +44 -0
- package/dist_ts/formats/base/base.validator.js +39 -0
- package/dist_ts/formats/base.decoder.d.ts +19 -0
- package/dist_ts/formats/base.decoder.js +130 -0
- package/dist_ts/formats/base.validator.d.ts +44 -0
- package/dist_ts/formats/base.validator.js +39 -0
- package/dist_ts/formats/cii/cii.decoder.d.ts +61 -0
- package/dist_ts/formats/cii/cii.decoder.js +111 -0
- package/dist_ts/formats/cii/cii.encoder.d.ts +43 -0
- package/dist_ts/formats/cii/cii.encoder.js +48 -0
- package/dist_ts/formats/cii/cii.types.d.ts +31 -0
- package/dist_ts/formats/cii/cii.types.js +40 -0
- package/dist_ts/formats/cii/cii.validator.d.ts +56 -0
- package/dist_ts/formats/cii/cii.validator.js +146 -0
- package/dist_ts/formats/cii/facturx/facturx.decoder.d.ts +43 -0
- package/dist_ts/formats/cii/facturx/facturx.decoder.js +207 -0
- package/dist_ts/formats/cii/facturx/facturx.encoder.d.ts +82 -0
- package/dist_ts/formats/cii/facturx/facturx.encoder.js +400 -0
- package/dist_ts/formats/cii/facturx/facturx.types.d.ts +10 -0
- package/dist_ts/formats/cii/facturx/facturx.types.js +15 -0
- package/dist_ts/formats/cii/facturx/facturx.validator.d.ts +32 -0
- package/dist_ts/formats/cii/facturx/facturx.validator.js +138 -0
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.d.ts +43 -0
- package/dist_ts/formats/cii/zugferd/zugferd.decoder.js +218 -0
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.d.ts +97 -0
- package/dist_ts/formats/cii/zugferd/zugferd.encoder.js +550 -0
- package/dist_ts/formats/cii/zugferd/zugferd.types.d.ts +10 -0
- package/dist_ts/formats/cii/zugferd/zugferd.types.js +15 -0
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.d.ts +49 -0
- package/dist_ts/formats/cii/zugferd/zugferd.v1.decoder.js +229 -0
- package/dist_ts/formats/cii/zugferd/zugferd.validator.d.ts +16 -0
- package/dist_ts/formats/cii/zugferd/zugferd.validator.js +29 -0
- package/dist_ts/formats/decoder.factory.d.ts +15 -0
- package/dist_ts/formats/decoder.factory.js +46 -0
- package/dist_ts/formats/factories/decoder.factory.d.ts +13 -0
- package/dist_ts/formats/factories/decoder.factory.js +56 -0
- package/dist_ts/formats/factories/encoder.factory.d.ts +14 -0
- package/dist_ts/formats/factories/encoder.factory.js +40 -0
- package/dist_ts/formats/factories/validator.factory.d.ts +12 -0
- package/dist_ts/formats/factories/validator.factory.js +110 -0
- package/dist_ts/formats/factorx.decoder.d.ts +18 -0
- package/dist_ts/formats/factorx.decoder.js +178 -0
- package/dist_ts/formats/factorx.encoder.d.ts +51 -0
- package/dist_ts/formats/factorx.encoder.js +293 -0
- package/dist_ts/formats/facturx.decoder.d.ts +18 -0
- package/dist_ts/formats/facturx.decoder.js +210 -0
- package/dist_ts/formats/facturx.encoder.d.ts +51 -0
- package/dist_ts/formats/facturx.encoder.js +293 -0
- package/dist_ts/formats/facturx.validator.d.ts +67 -0
- package/dist_ts/formats/facturx.validator.js +266 -0
- package/dist_ts/formats/pdf/extractors/associated.extractor.d.ts +14 -0
- package/dist_ts/formats/pdf/extractors/associated.extractor.js +68 -0
- package/dist_ts/formats/pdf/extractors/base.extractor.d.ts +92 -0
- package/dist_ts/formats/pdf/extractors/base.extractor.js +319 -0
- package/dist_ts/formats/pdf/extractors/index.d.ts +4 -0
- package/dist_ts/formats/pdf/extractors/index.js +5 -0
- package/dist_ts/formats/pdf/extractors/standard.extractor.d.ts +13 -0
- package/dist_ts/formats/pdf/extractors/standard.extractor.js +74 -0
- package/dist_ts/formats/pdf/extractors/text.extractor.d.ts +45 -0
- package/dist_ts/formats/pdf/extractors/text.extractor.js +151 -0
- package/dist_ts/formats/pdf/pdf.embedder.d.ts +69 -0
- package/dist_ts/formats/pdf/pdf.embedder.js +183 -0
- package/dist_ts/formats/pdf/pdf.extractor.d.ts +49 -0
- package/dist_ts/formats/pdf/pdf.extractor.js +98 -0
- package/dist_ts/formats/pdf/robust-pdf.extractor.d.ts +40 -0
- package/dist_ts/formats/pdf/robust-pdf.extractor.js +324 -0
- package/dist_ts/formats/ubl/en16931.ubl.validator.d.ts +18 -0
- package/dist_ts/formats/ubl/en16931.ubl.validator.js +169 -0
- package/dist_ts/formats/ubl/generic/ubl.encoder.d.ts +151 -0
- package/dist_ts/formats/ubl/generic/ubl.encoder.js +892 -0
- package/dist_ts/formats/ubl/ubl.decoder.d.ts +61 -0
- package/dist_ts/formats/ubl/ubl.decoder.js +95 -0
- package/dist_ts/formats/ubl/ubl.encoder.d.ts +38 -0
- package/dist_ts/formats/ubl/ubl.encoder.js +46 -0
- package/dist_ts/formats/ubl/ubl.types.d.ts +16 -0
- package/dist_ts/formats/ubl/ubl.types.js +21 -0
- package/dist_ts/formats/ubl/ubl.validator.d.ts +50 -0
- package/dist_ts/formats/ubl/ubl.validator.js +112 -0
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.d.ts +34 -0
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +424 -0
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.d.ts +75 -0
- package/dist_ts/formats/ubl/xrechnung/xrechnung.encoder.js +555 -0
- package/dist_ts/formats/ubl/xrechnung.validator.d.ts +15 -0
- package/dist_ts/formats/ubl/xrechnung.validator.js +107 -0
- package/dist_ts/formats/ubl.validator.d.ts +82 -0
- package/dist_ts/formats/ubl.validator.js +306 -0
- package/dist_ts/formats/utils/format.detector.d.ts +62 -0
- package/dist_ts/formats/utils/format.detector.js +249 -0
- package/dist_ts/formats/validation/en16931.validator.d.ts +23 -0
- package/dist_ts/formats/validation/en16931.validator.js +119 -0
- package/dist_ts/formats/validator.factory.d.ts +18 -0
- package/dist_ts/formats/validator.factory.js +78 -0
- package/dist_ts/formats/xinvoice.decoder.d.ts +28 -0
- package/dist_ts/formats/xinvoice.decoder.js +332 -0
- package/dist_ts/formats/xinvoice.encoder.d.ts +19 -0
- package/dist_ts/formats/xinvoice.encoder.js +282 -0
- package/dist_ts/index.d.ts +51 -0
- package/dist_ts/index.js +90 -0
- package/dist_ts/interfaces/common.d.ts +79 -0
- package/dist_ts/interfaces/common.js +24 -0
- package/dist_ts/interfaces.d.ts +87 -0
- package/dist_ts/interfaces.js +23 -0
- package/dist_ts/plugins.d.ts +14 -0
- package/dist_ts/plugins.js +31 -0
- package/npmextra.json +35 -0
- package/package.json +71 -0
- package/readme.hints.md +1107 -0
- package/readme.howtofixtests.md +38 -0
- package/readme.literature.md +1 -0
- package/readme.md +998 -0
- package/readme.plan.md +481 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/einvoice.ts +603 -0
- package/ts/errors.ts +341 -0
- package/ts/formats/base/base.decoder.ts +68 -0
- package/ts/formats/base/base.encoder.ts +14 -0
- package/ts/formats/base/base.validator.ts +64 -0
- package/ts/formats/cii/cii.decoder.ts +139 -0
- package/ts/formats/cii/cii.encoder.ts +64 -0
- package/ts/formats/cii/cii.types.ts +44 -0
- package/ts/formats/cii/cii.validator.ts +171 -0
- package/ts/formats/cii/facturx/facturx.decoder.ts +243 -0
- package/ts/formats/cii/facturx/facturx.encoder.ts +483 -0
- package/ts/formats/cii/facturx/facturx.types.ts +18 -0
- package/ts/formats/cii/facturx/facturx.validator.ts +180 -0
- package/ts/formats/cii/zugferd/zugferd.decoder.ts +256 -0
- package/ts/formats/cii/zugferd/zugferd.encoder.ts +661 -0
- package/ts/formats/cii/zugferd/zugferd.types.ts +18 -0
- package/ts/formats/cii/zugferd/zugferd.v1.decoder.ts +267 -0
- package/ts/formats/cii/zugferd/zugferd.validator.ts +31 -0
- package/ts/formats/factories/decoder.factory.ts +62 -0
- package/ts/formats/factories/encoder.factory.ts +47 -0
- package/ts/formats/factories/validator.factory.ts +134 -0
- package/ts/formats/pdf/extractors/associated.extractor.ts +78 -0
- package/ts/formats/pdf/extractors/base.extractor.ts +355 -0
- package/ts/formats/pdf/extractors/index.ts +4 -0
- package/ts/formats/pdf/extractors/standard.extractor.ts +86 -0
- package/ts/formats/pdf/extractors/text.extractor.ts +162 -0
- package/ts/formats/pdf/pdf.embedder.ts +242 -0
- package/ts/formats/pdf/pdf.extractor.ts +141 -0
- package/ts/formats/ubl/en16931.ubl.validator.ts +216 -0
- package/ts/formats/ubl/generic/ubl.encoder.ts +1041 -0
- package/ts/formats/ubl/ubl.decoder.ts +121 -0
- package/ts/formats/ubl/ubl.encoder.ts +63 -0
- package/ts/formats/ubl/ubl.types.ts +22 -0
- package/ts/formats/ubl/ubl.validator.ts +133 -0
- package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +471 -0
- package/ts/formats/ubl/xrechnung/xrechnung.encoder.ts +619 -0
- package/ts/formats/ubl/xrechnung.validator.ts +185 -0
- package/ts/formats/utils/format.detector.ts +306 -0
- package/ts/formats/validation/en16931.validator.ts +135 -0
- package/ts/index.ts +164 -0
- package/ts/interfaces/common.ts +90 -0
- package/ts/interfaces.ts +98 -0
- 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
|
+
}
|