@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
package/ts/einvoice.ts
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
import { business, finance } from './plugins.js';
|
|
4
|
+
import type { TInvoice, TAccountingDocItem } from '@tsclass/tsclass/dist_ts/finance/index.js';
|
|
5
|
+
import { InvoiceFormat, ValidationLevel } from './interfaces/common.js';
|
|
6
|
+
import type { ValidationResult, ValidationError, EInvoiceOptions, IPdf, ExportFormat } from './interfaces/common.js';
|
|
7
|
+
|
|
8
|
+
// Import error classes
|
|
9
|
+
import {
|
|
10
|
+
EInvoiceError,
|
|
11
|
+
EInvoiceParsingError,
|
|
12
|
+
EInvoiceValidationError,
|
|
13
|
+
EInvoicePDFError,
|
|
14
|
+
EInvoiceFormatError,
|
|
15
|
+
ErrorContext
|
|
16
|
+
} from './errors.js';
|
|
17
|
+
|
|
18
|
+
// Import factories
|
|
19
|
+
import { DecoderFactory } from './formats/factories/decoder.factory.js';
|
|
20
|
+
import { EncoderFactory } from './formats/factories/encoder.factory.js';
|
|
21
|
+
import { ValidatorFactory } from './formats/factories/validator.factory.js';
|
|
22
|
+
|
|
23
|
+
// Import PDF utilities
|
|
24
|
+
import { PDFEmbedder } from './formats/pdf/pdf.embedder.js';
|
|
25
|
+
import { PDFExtractor } from './formats/pdf/pdf.extractor.js';
|
|
26
|
+
|
|
27
|
+
// Import format detector
|
|
28
|
+
import { FormatDetector } from './formats/utils/format.detector.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Main class for working with electronic invoices.
|
|
32
|
+
* Supports various invoice formats including Factur-X, ZUGFeRD, UBL, and XRechnung
|
|
33
|
+
* Extends the TInvoice interface for seamless integration with existing systems
|
|
34
|
+
*/
|
|
35
|
+
export class EInvoice implements TInvoice {
|
|
36
|
+
/**
|
|
37
|
+
* Creates an EInvoice instance from XML string
|
|
38
|
+
* @param xmlString XML string to parse
|
|
39
|
+
* @returns EInvoice instance
|
|
40
|
+
*/
|
|
41
|
+
public static async fromXml(xmlString: string): Promise<EInvoice> {
|
|
42
|
+
const invoice = new EInvoice();
|
|
43
|
+
await invoice.fromXmlString(xmlString);
|
|
44
|
+
return invoice;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates an EInvoice instance from file
|
|
49
|
+
* @param filePath Path to the file
|
|
50
|
+
* @returns EInvoice instance
|
|
51
|
+
*/
|
|
52
|
+
public static async fromFile(filePath: string): Promise<EInvoice> {
|
|
53
|
+
const invoice = new EInvoice();
|
|
54
|
+
await invoice.fromFile(filePath);
|
|
55
|
+
return invoice;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates an EInvoice instance from PDF
|
|
60
|
+
* @param pdfBuffer PDF buffer
|
|
61
|
+
* @returns EInvoice instance
|
|
62
|
+
*/
|
|
63
|
+
public static async fromPdf(pdfBuffer: Buffer | string): Promise<EInvoice> {
|
|
64
|
+
const invoice = new EInvoice();
|
|
65
|
+
if (typeof pdfBuffer === 'string') {
|
|
66
|
+
// If given a file path
|
|
67
|
+
await invoice.fromPdfFile(pdfBuffer);
|
|
68
|
+
} else {
|
|
69
|
+
// If given a buffer, extract XML and parse it
|
|
70
|
+
const extractResult = await invoice.pdfExtractor.extractXml(pdfBuffer);
|
|
71
|
+
if (!extractResult.success || !extractResult.xml) {
|
|
72
|
+
throw new EInvoicePDFError('No invoice XML found in PDF', 'extract');
|
|
73
|
+
}
|
|
74
|
+
await invoice.fromXmlString(extractResult.xml);
|
|
75
|
+
}
|
|
76
|
+
return invoice;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// TInvoice interface properties - accounting document structure
|
|
80
|
+
public type: 'accounting-doc' = 'accounting-doc';
|
|
81
|
+
public accountingDocType: 'invoice' = 'invoice';
|
|
82
|
+
public accountingDocId: string = '';
|
|
83
|
+
public accountingDocStatus: 'draft' | 'issued' | 'paid' | 'canceled' | 'refunded' = 'issued';
|
|
84
|
+
|
|
85
|
+
// Business envelope properties
|
|
86
|
+
public id: string = '';
|
|
87
|
+
public date = Date.now();
|
|
88
|
+
public status: 'draft' | 'issued' | 'paid' | 'canceled' | 'refunded' = 'issued';
|
|
89
|
+
public subject: string = '';
|
|
90
|
+
public versionInfo: business.TDocumentEnvelope<string, any>['versionInfo'] = {
|
|
91
|
+
type: 'draft',
|
|
92
|
+
version: '1.0.0'
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Contact information
|
|
96
|
+
public from: business.TContact;
|
|
97
|
+
public to: business.TContact;
|
|
98
|
+
public legalContact?: business.TContact;
|
|
99
|
+
|
|
100
|
+
// Additional envelope properties
|
|
101
|
+
public incidenceId: string = '';
|
|
102
|
+
public language: string = 'en';
|
|
103
|
+
public objectActions: any[] = [];
|
|
104
|
+
public pdf: IPdf | null = null;
|
|
105
|
+
public pdfAttachments: IPdf[] | null = null;
|
|
106
|
+
public accentColor: string | null = null;
|
|
107
|
+
public logoUrl: string | null = null;
|
|
108
|
+
|
|
109
|
+
// Accounting document specific properties
|
|
110
|
+
public items: TAccountingDocItem[] = [];
|
|
111
|
+
public dueInDays: number = 30;
|
|
112
|
+
public reverseCharge: boolean = false;
|
|
113
|
+
public currency: finance.TCurrency = 'EUR';
|
|
114
|
+
public notes: string[] = [];
|
|
115
|
+
public periodOfPerformance?: { from: number; to: number };
|
|
116
|
+
public deliveryDate?: number;
|
|
117
|
+
public buyerReference?: string;
|
|
118
|
+
public electronicAddress?: { scheme: string; value: string };
|
|
119
|
+
public paymentOptions?: finance.IPaymentOptionInfo;
|
|
120
|
+
public relatedDocuments?: Array<{
|
|
121
|
+
relationType: 'corrects' | 'replaces' | 'references';
|
|
122
|
+
documentId: string;
|
|
123
|
+
issueDate?: number;
|
|
124
|
+
}>;
|
|
125
|
+
public printResult?: {
|
|
126
|
+
pdfBufferString: string;
|
|
127
|
+
totalNet: number;
|
|
128
|
+
totalGross: number;
|
|
129
|
+
vatGroups: {
|
|
130
|
+
percentage: number;
|
|
131
|
+
items: TAccountingDocItem[];
|
|
132
|
+
}[];
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Backward compatibility properties
|
|
136
|
+
public get invoiceId(): string { return this.accountingDocId; }
|
|
137
|
+
public set invoiceId(value: string) { this.accountingDocId = value; }
|
|
138
|
+
|
|
139
|
+
public get invoiceType(): 'invoice' | 'creditnote' | 'debitnote' {
|
|
140
|
+
return this.accountingDocType === 'invoice' ? 'invoice' :
|
|
141
|
+
this.accountingDocType === 'creditnote' ? 'creditnote' : 'debitnote';
|
|
142
|
+
}
|
|
143
|
+
public set invoiceType(value: 'invoice' | 'creditnote' | 'debitnote') {
|
|
144
|
+
this.accountingDocType = 'invoice'; // Always set to invoice for TInvoice type
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Computed properties for convenience
|
|
148
|
+
public get issueDate(): Date {
|
|
149
|
+
return new Date(this.date);
|
|
150
|
+
}
|
|
151
|
+
public set issueDate(value: Date) {
|
|
152
|
+
this.date = value.getTime();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public get totalNet(): number {
|
|
156
|
+
return this.calculateTotalNet();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public get totalVat(): number {
|
|
160
|
+
return this.calculateTotalVat();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public get totalGross(): number {
|
|
164
|
+
return this.totalNet + this.totalVat;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public get taxBreakdown(): Array<{ taxPercent: number; netAmount: number; taxAmount: number }> {
|
|
168
|
+
return this.calculateTaxBreakdown();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// EInvoice specific properties
|
|
172
|
+
public metadata?: {
|
|
173
|
+
format?: InvoiceFormat;
|
|
174
|
+
version?: string;
|
|
175
|
+
profile?: string;
|
|
176
|
+
customizationId?: string;
|
|
177
|
+
extensions?: Record<string, any>;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
private xmlString: string = '';
|
|
181
|
+
private detectedFormat: InvoiceFormat = InvoiceFormat.UNKNOWN;
|
|
182
|
+
private validationErrors: ValidationError[] = [];
|
|
183
|
+
private options: EInvoiceOptions = {
|
|
184
|
+
validateOnLoad: false,
|
|
185
|
+
validationLevel: ValidationLevel.SYNTAX
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// PDF utilities
|
|
189
|
+
private pdfEmbedder = new PDFEmbedder();
|
|
190
|
+
private pdfExtractor = new PDFExtractor();
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Creates a new EInvoice instance
|
|
194
|
+
* @param options Configuration options
|
|
195
|
+
*/
|
|
196
|
+
constructor(options?: EInvoiceOptions) {
|
|
197
|
+
// Initialize empty contact objects
|
|
198
|
+
this.from = this.createEmptyContact();
|
|
199
|
+
this.to = this.createEmptyContact();
|
|
200
|
+
|
|
201
|
+
// Apply options if provided
|
|
202
|
+
if (options) {
|
|
203
|
+
this.options = { ...this.options, ...options };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Creates an empty TContact object
|
|
209
|
+
*/
|
|
210
|
+
private createEmptyContact(): business.TContact {
|
|
211
|
+
return {
|
|
212
|
+
type: 'company',
|
|
213
|
+
name: '',
|
|
214
|
+
description: '',
|
|
215
|
+
address: {
|
|
216
|
+
streetName: '',
|
|
217
|
+
houseNumber: '',
|
|
218
|
+
city: '',
|
|
219
|
+
postalCode: '',
|
|
220
|
+
country: ''
|
|
221
|
+
},
|
|
222
|
+
registrationDetails: {
|
|
223
|
+
vatId: '',
|
|
224
|
+
registrationId: '',
|
|
225
|
+
registrationName: ''
|
|
226
|
+
},
|
|
227
|
+
status: 'active',
|
|
228
|
+
foundedDate: {
|
|
229
|
+
year: new Date().getFullYear(),
|
|
230
|
+
month: new Date().getMonth() + 1,
|
|
231
|
+
day: new Date().getDate()
|
|
232
|
+
}
|
|
233
|
+
} as business.TCompany;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Exports the invoice as XML in the specified format
|
|
238
|
+
* @param format The export format
|
|
239
|
+
* @returns XML string
|
|
240
|
+
*/
|
|
241
|
+
public async exportXml(format: ExportFormat): Promise<string> {
|
|
242
|
+
return this.toXmlString(format);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Loads invoice data from XML (alias for fromXmlString)
|
|
247
|
+
* @param xmlString The XML string to parse
|
|
248
|
+
* @returns The EInvoice instance for chaining
|
|
249
|
+
*/
|
|
250
|
+
public async loadXml(xmlString: string): Promise<EInvoice> {
|
|
251
|
+
return this.fromXmlString(xmlString);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Loads invoice data from an XML string
|
|
256
|
+
* @param xmlString The XML string to parse
|
|
257
|
+
* @returns The EInvoice instance for chaining
|
|
258
|
+
*/
|
|
259
|
+
public async fromXmlString(xmlString: string): Promise<EInvoice> {
|
|
260
|
+
try {
|
|
261
|
+
this.xmlString = xmlString;
|
|
262
|
+
|
|
263
|
+
// Detect format
|
|
264
|
+
this.detectedFormat = FormatDetector.detectFormat(xmlString);
|
|
265
|
+
if (this.detectedFormat === InvoiceFormat.UNKNOWN) {
|
|
266
|
+
throw new EInvoiceFormatError('Unknown invoice format', { sourceFormat: 'unknown' });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Get appropriate decoder
|
|
270
|
+
const decoder = DecoderFactory.createDecoder(xmlString, !this.options.validateOnLoad);
|
|
271
|
+
const invoice = await decoder.decode();
|
|
272
|
+
|
|
273
|
+
// Map the decoded invoice to our properties
|
|
274
|
+
this.mapFromTInvoice(invoice);
|
|
275
|
+
|
|
276
|
+
// Validate if requested
|
|
277
|
+
if (this.options.validateOnLoad) {
|
|
278
|
+
await this.validate(this.options.validationLevel);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return this;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
if (error instanceof EInvoiceError) {
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
throw new EInvoiceParsingError(`Failed to parse XML: ${error.message}`, {}, error as Error);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Loads invoice data from a file
|
|
292
|
+
* @param filePath Path to the file to load
|
|
293
|
+
* @returns The EInvoice instance for chaining
|
|
294
|
+
*/
|
|
295
|
+
public async fromFile(filePath: string): Promise<EInvoice> {
|
|
296
|
+
try {
|
|
297
|
+
const fileBuffer = await plugins.fs.readFile(filePath);
|
|
298
|
+
|
|
299
|
+
// Check if it's a PDF
|
|
300
|
+
if (filePath.toLowerCase().endsWith('.pdf') || fileBuffer.subarray(0, 4).toString() === '%PDF') {
|
|
301
|
+
return this.fromPdfFile(filePath);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Otherwise treat as XML
|
|
305
|
+
const xmlString = fileBuffer.toString('utf-8');
|
|
306
|
+
return this.fromXmlString(xmlString);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
throw new EInvoiceError(`Failed to load file: ${error.message}`, 'FILE_LOAD_ERROR', { filePath });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Loads invoice data from a PDF file
|
|
314
|
+
* @param filePath Path to the PDF file
|
|
315
|
+
* @returns The EInvoice instance for chaining
|
|
316
|
+
*/
|
|
317
|
+
public async fromPdfFile(filePath: string): Promise<EInvoice> {
|
|
318
|
+
try {
|
|
319
|
+
const pdfBuffer = await plugins.fs.readFile(filePath);
|
|
320
|
+
const extractResult = await this.pdfExtractor.extractXml(pdfBuffer);
|
|
321
|
+
const extractedXml = extractResult.success ? extractResult.xml : null;
|
|
322
|
+
|
|
323
|
+
if (!extractedXml) {
|
|
324
|
+
throw new EInvoicePDFError('No invoice XML found in PDF', 'extract', { filePath });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Store the PDF for later use
|
|
328
|
+
this.pdf = {
|
|
329
|
+
name: plugins.path.basename(filePath),
|
|
330
|
+
id: plugins.crypto.createHash('md5').update(pdfBuffer).digest('hex'),
|
|
331
|
+
buffer: new Uint8Array(pdfBuffer),
|
|
332
|
+
metadata: {
|
|
333
|
+
textExtraction: '',
|
|
334
|
+
format: 'PDF/A-3',
|
|
335
|
+
embeddedXml: {
|
|
336
|
+
filename: 'factur-x.xml',
|
|
337
|
+
description: 'Factur-X Invoice'
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
return this.fromXmlString(extractedXml);
|
|
343
|
+
} catch (error) {
|
|
344
|
+
if (error instanceof EInvoiceError) {
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
throw new EInvoicePDFError(`Failed to extract invoice from PDF: ${error.message}`, 'extract', {}, error as Error);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Maps data from a TInvoice to this EInvoice instance
|
|
353
|
+
*/
|
|
354
|
+
private mapFromTInvoice(invoice: TInvoice): void {
|
|
355
|
+
// Map all properties from the decoded invoice
|
|
356
|
+
Object.assign(this, invoice);
|
|
357
|
+
|
|
358
|
+
// Ensure backward compatibility
|
|
359
|
+
if (!this.id && this.accountingDocId) {
|
|
360
|
+
this.id = this.accountingDocId;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Maps this EInvoice instance to a TInvoice
|
|
366
|
+
*/
|
|
367
|
+
private mapToTInvoice(): TInvoice {
|
|
368
|
+
const invoice: any = {
|
|
369
|
+
type: 'accounting-doc',
|
|
370
|
+
accountingDocType: this.accountingDocType,
|
|
371
|
+
accountingDocId: this.accountingDocId || this.id,
|
|
372
|
+
accountingDocStatus: this.accountingDocStatus,
|
|
373
|
+
id: this.id,
|
|
374
|
+
date: this.date,
|
|
375
|
+
status: this.status,
|
|
376
|
+
subject: this.subject,
|
|
377
|
+
versionInfo: this.versionInfo,
|
|
378
|
+
from: this.from,
|
|
379
|
+
to: this.to,
|
|
380
|
+
legalContact: this.legalContact,
|
|
381
|
+
incidenceId: this.incidenceId,
|
|
382
|
+
language: this.language,
|
|
383
|
+
objectActions: this.objectActions,
|
|
384
|
+
items: this.items,
|
|
385
|
+
dueInDays: this.dueInDays,
|
|
386
|
+
reverseCharge: this.reverseCharge,
|
|
387
|
+
currency: this.currency,
|
|
388
|
+
notes: this.notes,
|
|
389
|
+
periodOfPerformance: this.periodOfPerformance,
|
|
390
|
+
deliveryDate: this.deliveryDate,
|
|
391
|
+
buyerReference: this.buyerReference,
|
|
392
|
+
electronicAddress: this.electronicAddress,
|
|
393
|
+
paymentOptions: this.paymentOptions,
|
|
394
|
+
relatedDocuments: this.relatedDocuments,
|
|
395
|
+
printResult: this.printResult
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// Preserve metadata for enhanced spec compliance
|
|
399
|
+
if ((this as any).metadata) {
|
|
400
|
+
invoice.metadata = (this as any).metadata;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return invoice;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Exports the invoice to an XML string in the specified format
|
|
408
|
+
* @param format The target format
|
|
409
|
+
* @returns The XML string
|
|
410
|
+
*/
|
|
411
|
+
public async toXmlString(format: ExportFormat): Promise<string> {
|
|
412
|
+
try {
|
|
413
|
+
const encoder = EncoderFactory.createEncoder(format);
|
|
414
|
+
const invoice = this.mapToTInvoice();
|
|
415
|
+
|
|
416
|
+
// Import EN16931Validator dynamically to avoid circular dependency
|
|
417
|
+
const { EN16931Validator } = await import('./formats/validation/en16931.validator.js');
|
|
418
|
+
|
|
419
|
+
// Validate mandatory fields before encoding
|
|
420
|
+
EN16931Validator.validateMandatoryFields(invoice);
|
|
421
|
+
|
|
422
|
+
return await encoder.encode(invoice);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
throw new EInvoiceFormatError(`Failed to encode to ${format}: ${error.message}`, { targetFormat: format });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Validates the invoice
|
|
430
|
+
* @param level The validation level to use
|
|
431
|
+
* @returns The validation result
|
|
432
|
+
*/
|
|
433
|
+
public async validate(level: ValidationLevel = ValidationLevel.BUSINESS): Promise<ValidationResult> {
|
|
434
|
+
try {
|
|
435
|
+
const format = this.detectedFormat || InvoiceFormat.UNKNOWN;
|
|
436
|
+
if (format === InvoiceFormat.UNKNOWN) {
|
|
437
|
+
throw new EInvoiceValidationError('Cannot validate: format unknown', []);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const validator = ValidatorFactory.createValidator(this.xmlString);
|
|
441
|
+
const result = validator.validate(level);
|
|
442
|
+
|
|
443
|
+
this.validationErrors = result.errors;
|
|
444
|
+
return result;
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (error instanceof EInvoiceError) {
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
throw new EInvoiceValidationError(`Validation failed: ${error.message}`, [], { validationLevel: level });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Embeds the invoice XML into a PDF
|
|
455
|
+
* @param pdfBuffer The PDF buffer to embed into
|
|
456
|
+
* @param format The format to use for embedding
|
|
457
|
+
* @returns The PDF buffer with embedded XML
|
|
458
|
+
*/
|
|
459
|
+
public async embedInPdf(pdfBuffer: Buffer, format: ExportFormat = 'facturx'): Promise<Buffer> {
|
|
460
|
+
try {
|
|
461
|
+
const xmlString = await this.toXmlString(format);
|
|
462
|
+
const embedResult = await this.pdfEmbedder.embedXml(pdfBuffer, xmlString, 'invoice.xml', `${format} Invoice`);
|
|
463
|
+
if (!embedResult.success) {
|
|
464
|
+
throw new EInvoicePDFError('Failed to embed XML in PDF', 'embed', { format });
|
|
465
|
+
}
|
|
466
|
+
return embedResult.data! as Buffer;
|
|
467
|
+
} catch (error) {
|
|
468
|
+
throw new EInvoicePDFError(`Failed to embed XML in PDF: ${error.message}`, 'embed', { format }, error as Error);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Saves the invoice to a file
|
|
474
|
+
* @param filePath The path to save to
|
|
475
|
+
* @param format The format to save in
|
|
476
|
+
*/
|
|
477
|
+
public async saveToFile(filePath: string, format?: ExportFormat): Promise<void> {
|
|
478
|
+
try {
|
|
479
|
+
// Determine format from file extension if not provided
|
|
480
|
+
if (!format && filePath.toLowerCase().endsWith('.xml')) {
|
|
481
|
+
format = this.detectedFormat === InvoiceFormat.UBL ? 'ubl' :
|
|
482
|
+
this.detectedFormat === InvoiceFormat.ZUGFERD ? 'zugferd' :
|
|
483
|
+
this.detectedFormat === InvoiceFormat.FACTURX ? 'facturx' :
|
|
484
|
+
'xrechnung';
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (filePath.toLowerCase().endsWith('.pdf')) {
|
|
488
|
+
// Save as PDF with embedded XML
|
|
489
|
+
if (!this.pdf) {
|
|
490
|
+
throw new EInvoiceError('No PDF available to save', 'NO_PDF_ERROR');
|
|
491
|
+
}
|
|
492
|
+
const pdfWithXml = await this.embedInPdf(Buffer.from(this.pdf.buffer), format);
|
|
493
|
+
await plugins.fs.writeFile(filePath, pdfWithXml);
|
|
494
|
+
} else {
|
|
495
|
+
// Save as XML
|
|
496
|
+
const xmlString = await this.toXmlString(format || 'xrechnung');
|
|
497
|
+
await plugins.fs.writeFile(filePath, xmlString, 'utf-8');
|
|
498
|
+
}
|
|
499
|
+
} catch (error) {
|
|
500
|
+
if (error instanceof EInvoiceError) {
|
|
501
|
+
throw error;
|
|
502
|
+
}
|
|
503
|
+
throw new EInvoiceError(`Failed to save file: ${error.message}`, 'FILE_SAVE_ERROR', { filePath });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Gets the validation errors
|
|
509
|
+
* @returns Array of validation errors
|
|
510
|
+
*/
|
|
511
|
+
public getValidationErrors(): ValidationError[] {
|
|
512
|
+
return this.validationErrors;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Checks if the invoice is valid
|
|
517
|
+
* @returns True if valid, false otherwise
|
|
518
|
+
*/
|
|
519
|
+
public isValid(): boolean {
|
|
520
|
+
return this.validationErrors.length === 0;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Gets the detected format
|
|
525
|
+
* @returns The detected invoice format
|
|
526
|
+
*/
|
|
527
|
+
public getFormat(): InvoiceFormat {
|
|
528
|
+
return this.detectedFormat;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Gets the original XML string
|
|
533
|
+
* @returns The XML string
|
|
534
|
+
*/
|
|
535
|
+
public getXml(): string {
|
|
536
|
+
return this.xmlString;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Calculates the total net amount
|
|
541
|
+
*/
|
|
542
|
+
private calculateTotalNet(): number {
|
|
543
|
+
return this.items.reduce((sum, item) => {
|
|
544
|
+
return sum + (item.unitQuantity * item.unitNetPrice);
|
|
545
|
+
}, 0);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Calculates the total VAT amount
|
|
550
|
+
*/
|
|
551
|
+
private calculateTotalVat(): number {
|
|
552
|
+
return this.items.reduce((sum, item) => {
|
|
553
|
+
const net = item.unitQuantity * item.unitNetPrice;
|
|
554
|
+
return sum + (net * item.vatPercentage / 100);
|
|
555
|
+
}, 0);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Calculates tax breakdown by rate
|
|
560
|
+
*/
|
|
561
|
+
private calculateTaxBreakdown(): Array<{ taxPercent: number; netAmount: number; taxAmount: number }> {
|
|
562
|
+
const breakdown = new Map<number, { net: number; tax: number }>();
|
|
563
|
+
|
|
564
|
+
this.items.forEach(item => {
|
|
565
|
+
const net = item.unitQuantity * item.unitNetPrice;
|
|
566
|
+
const tax = net * item.vatPercentage / 100;
|
|
567
|
+
|
|
568
|
+
const current = breakdown.get(item.vatPercentage) || { net: 0, tax: 0 };
|
|
569
|
+
breakdown.set(item.vatPercentage, {
|
|
570
|
+
net: current.net + net,
|
|
571
|
+
tax: current.tax + tax
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
return Array.from(breakdown.entries()).map(([rate, amounts]) => ({
|
|
576
|
+
taxPercent: rate,
|
|
577
|
+
netAmount: amounts.net,
|
|
578
|
+
taxAmount: amounts.tax
|
|
579
|
+
}));
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Creates a new invoice item
|
|
584
|
+
*/
|
|
585
|
+
public createItem(data: Partial<TAccountingDocItem>): TAccountingDocItem {
|
|
586
|
+
return {
|
|
587
|
+
position: data.position || this.items.length + 1,
|
|
588
|
+
name: data.name || '',
|
|
589
|
+
articleNumber: data.articleNumber,
|
|
590
|
+
unitType: data.unitType || 'unit',
|
|
591
|
+
unitQuantity: data.unitQuantity || 1,
|
|
592
|
+
unitNetPrice: data.unitNetPrice || 0,
|
|
593
|
+
vatPercentage: data.vatPercentage || 0
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Adds an item to the invoice
|
|
599
|
+
*/
|
|
600
|
+
public addItem(item: Partial<TAccountingDocItem>): void {
|
|
601
|
+
this.items.push(this.createItem(item));
|
|
602
|
+
}
|
|
603
|
+
}
|