@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,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CII-specific types and constants
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// CII namespaces (ZUGFeRD v2/Factur-X)
|
|
6
|
+
export const CII_NAMESPACES = {
|
|
7
|
+
RSM: 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
|
|
8
|
+
RAM: 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100',
|
|
9
|
+
UDT: 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// ZUGFeRD v1 namespaces
|
|
13
|
+
export const ZUGFERD_V1_NAMESPACES = {
|
|
14
|
+
RSM: 'urn:ferd:CrossIndustryDocument:invoice:1p0',
|
|
15
|
+
RAM: 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12',
|
|
16
|
+
UDT: 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// CII profiles
|
|
20
|
+
export enum CIIProfile {
|
|
21
|
+
BASIC = 'BASIC',
|
|
22
|
+
COMFORT = 'COMFORT',
|
|
23
|
+
EXTENDED = 'EXTENDED',
|
|
24
|
+
EN16931 = 'EN16931',
|
|
25
|
+
MINIMUM = 'MINIMUM'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// CII profile IDs for different formats
|
|
29
|
+
export const CII_PROFILE_IDS = {
|
|
30
|
+
// Factur-X profiles
|
|
31
|
+
FACTURX_MINIMUM: 'urn:factur-x.eu:1p0:minimum',
|
|
32
|
+
FACTURX_BASIC: 'urn:factur-x.eu:1p0:basicwl',
|
|
33
|
+
FACTURX_EN16931: 'urn:cen.eu:en16931:2017',
|
|
34
|
+
|
|
35
|
+
// ZUGFeRD v2 profiles
|
|
36
|
+
ZUGFERD_BASIC: 'urn:zugferd:basic',
|
|
37
|
+
ZUGFERD_COMFORT: 'urn:zugferd:comfort',
|
|
38
|
+
ZUGFERD_EXTENDED: 'urn:zugferd:extended',
|
|
39
|
+
|
|
40
|
+
// ZUGFeRD v1 profiles
|
|
41
|
+
ZUGFERD_V1_BASIC: 'urn:ferd:CrossIndustryDocument:invoice:1p0:basic',
|
|
42
|
+
ZUGFERD_V1_COMFORT: 'urn:ferd:CrossIndustryDocument:invoice:1p0:comfort',
|
|
43
|
+
ZUGFERD_V1_EXTENDED: 'urn:ferd:CrossIndustryDocument:invoice:1p0:extended'
|
|
44
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { BaseValidator } from '../base/base.validator.js';
|
|
2
|
+
import { ValidationLevel } from '../../interfaces/common.js';
|
|
3
|
+
import type { ValidationResult } from '../../interfaces/common.js';
|
|
4
|
+
import { CII_NAMESPACES, CIIProfile } from './cii.types.js';
|
|
5
|
+
import { DOMParser, xpath } from '../../plugins.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base validator for CII-based invoice formats
|
|
9
|
+
*/
|
|
10
|
+
export abstract class CIIBaseValidator extends BaseValidator {
|
|
11
|
+
protected doc: Document;
|
|
12
|
+
protected namespaces: Record<string, string>;
|
|
13
|
+
protected select: xpath.XPathSelect;
|
|
14
|
+
protected profile: CIIProfile = CIIProfile.EN16931;
|
|
15
|
+
|
|
16
|
+
constructor(xml: string) {
|
|
17
|
+
super(xml);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Parse XML document
|
|
21
|
+
this.doc = new DOMParser().parseFromString(xml, 'application/xml');
|
|
22
|
+
|
|
23
|
+
// Set up namespaces for XPath queries
|
|
24
|
+
this.namespaces = {
|
|
25
|
+
rsm: CII_NAMESPACES.RSM,
|
|
26
|
+
ram: CII_NAMESPACES.RAM,
|
|
27
|
+
udt: CII_NAMESPACES.UDT
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Create XPath selector with namespaces
|
|
31
|
+
this.select = xpath.useNamespaces(this.namespaces);
|
|
32
|
+
|
|
33
|
+
// Detect profile
|
|
34
|
+
this.detectProfile();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
this.addError('CII-PARSE', `Failed to parse XML: ${error}`, '/');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validates CII XML against the specified level of validation
|
|
42
|
+
* @param level Validation level
|
|
43
|
+
* @returns Result of validation
|
|
44
|
+
*/
|
|
45
|
+
public validate(level: ValidationLevel = ValidationLevel.SYNTAX): ValidationResult {
|
|
46
|
+
// Reset errors
|
|
47
|
+
this.errors = [];
|
|
48
|
+
|
|
49
|
+
// Check if document was parsed successfully
|
|
50
|
+
if (!this.doc) {
|
|
51
|
+
return {
|
|
52
|
+
valid: false,
|
|
53
|
+
errors: this.errors,
|
|
54
|
+
level: level
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Perform validation based on level
|
|
59
|
+
let valid = true;
|
|
60
|
+
|
|
61
|
+
if (level === ValidationLevel.SYNTAX) {
|
|
62
|
+
valid = this.validateSchema();
|
|
63
|
+
} else if (level === ValidationLevel.SEMANTIC) {
|
|
64
|
+
valid = this.validateSchema() && this.validateStructure();
|
|
65
|
+
} else if (level === ValidationLevel.BUSINESS) {
|
|
66
|
+
valid = this.validateSchema() &&
|
|
67
|
+
this.validateStructure() &&
|
|
68
|
+
this.validateBusinessRules();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
valid,
|
|
73
|
+
errors: this.errors,
|
|
74
|
+
level
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validates CII XML against schema
|
|
80
|
+
* @returns True if schema validation passed
|
|
81
|
+
*/
|
|
82
|
+
protected validateSchema(): boolean {
|
|
83
|
+
// Basic schema validation (simplified for now)
|
|
84
|
+
if (!this.doc) return false;
|
|
85
|
+
|
|
86
|
+
// Check for root element
|
|
87
|
+
const root = this.doc.documentElement;
|
|
88
|
+
if (!root || root.nodeName !== 'rsm:CrossIndustryInvoice') {
|
|
89
|
+
this.addError('CII-SCHEMA-1', 'Root element must be rsm:CrossIndustryInvoice', '/');
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check for required namespaces
|
|
94
|
+
if (!root.lookupNamespaceURI('rsm') || !root.lookupNamespaceURI('ram')) {
|
|
95
|
+
this.addError('CII-SCHEMA-2', 'Required namespaces rsm and ram must be declared', '/');
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Validates structure of the CII XML document
|
|
104
|
+
* @returns True if structure validation passed
|
|
105
|
+
*/
|
|
106
|
+
protected abstract validateStructure(): boolean;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Detects the CII profile from the XML
|
|
110
|
+
*/
|
|
111
|
+
protected detectProfile(): void {
|
|
112
|
+
// Look for profile identifier
|
|
113
|
+
const profileNode = this.select(
|
|
114
|
+
'string(//rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID)',
|
|
115
|
+
this.doc
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (profileNode) {
|
|
119
|
+
const profileText = profileNode.toString();
|
|
120
|
+
|
|
121
|
+
if (profileText.includes('BASIC')) {
|
|
122
|
+
this.profile = CIIProfile.BASIC;
|
|
123
|
+
} else if (profileText.includes('EN16931')) {
|
|
124
|
+
this.profile = CIIProfile.EN16931;
|
|
125
|
+
} else if (profileText.includes('EXTENDED')) {
|
|
126
|
+
this.profile = CIIProfile.EXTENDED;
|
|
127
|
+
} else if (profileText.includes('MINIMUM')) {
|
|
128
|
+
this.profile = CIIProfile.MINIMUM;
|
|
129
|
+
} else if (profileText.includes('COMFORT')) {
|
|
130
|
+
this.profile = CIIProfile.COMFORT;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Gets a text value from an XPath expression
|
|
137
|
+
* @param xpath XPath expression
|
|
138
|
+
* @param context Optional context node
|
|
139
|
+
* @returns Text value or empty string if not found
|
|
140
|
+
*/
|
|
141
|
+
protected getText(xpathExpr: string, context?: Node): string {
|
|
142
|
+
const node = this.select(xpathExpr, context || this.doc)[0];
|
|
143
|
+
return node ? (node.textContent || '') : '';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Gets a number value from an XPath expression
|
|
148
|
+
* @param xpath XPath expression
|
|
149
|
+
* @param context Optional context node
|
|
150
|
+
* @returns Number value or 0 if not found or not a number
|
|
151
|
+
*/
|
|
152
|
+
protected getNumber(xpathExpr: string, context?: Node): number {
|
|
153
|
+
const text = this.getText(xpathExpr, context);
|
|
154
|
+
const num = parseFloat(text);
|
|
155
|
+
return isNaN(num) ? 0 : num;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Checks if a node exists
|
|
160
|
+
* @param xpath XPath expression
|
|
161
|
+
* @param context Optional context node
|
|
162
|
+
* @returns True if node exists
|
|
163
|
+
*/
|
|
164
|
+
protected exists(xpathExpr: string, context?: Node): boolean {
|
|
165
|
+
const nodes = this.select(xpathExpr, context || this.doc);
|
|
166
|
+
if (Array.isArray(nodes)) {
|
|
167
|
+
return nodes.length > 0;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { CIIBaseDecoder } from '../cii.decoder.js';
|
|
2
|
+
import type { TInvoice, TCreditNote, TDebitNote } from '../../../interfaces/common.js';
|
|
3
|
+
import { FACTURX_PROFILE_IDS } from './facturx.types.js';
|
|
4
|
+
import { business, finance, general } from '../../../plugins.js';
|
|
5
|
+
import { EN16931Validator } from '../../validation/en16931.validator.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Decoder for Factur-X invoice format
|
|
9
|
+
*/
|
|
10
|
+
export class FacturXDecoder extends CIIBaseDecoder {
|
|
11
|
+
/**
|
|
12
|
+
* Decodes a Factur-X credit note
|
|
13
|
+
* @returns Promise resolving to a TCreditNote object
|
|
14
|
+
*/
|
|
15
|
+
protected async decodeCreditNote(): Promise<TCreditNote> {
|
|
16
|
+
// Get common invoice data
|
|
17
|
+
const commonData = await this.extractCommonData();
|
|
18
|
+
|
|
19
|
+
// Create a credit note with the common data
|
|
20
|
+
return {
|
|
21
|
+
...commonData,
|
|
22
|
+
accountingDocType: 'creditnote' as const
|
|
23
|
+
} as unknown as TCreditNote;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Decodes a Factur-X debit note (invoice)
|
|
28
|
+
* @returns Promise resolving to a TDebitNote object
|
|
29
|
+
*/
|
|
30
|
+
protected async decodeDebitNote(): Promise<TDebitNote> {
|
|
31
|
+
// Get common invoice data
|
|
32
|
+
const commonData = await this.extractCommonData();
|
|
33
|
+
|
|
34
|
+
// Create a debit note with the common data
|
|
35
|
+
return {
|
|
36
|
+
...commonData,
|
|
37
|
+
accountingDocType: 'debitnote' as const
|
|
38
|
+
} as unknown as TDebitNote;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extracts common invoice data from Factur-X XML
|
|
43
|
+
* @returns Common invoice data
|
|
44
|
+
*/
|
|
45
|
+
private async extractCommonData(): Promise<Partial<TInvoice>> {
|
|
46
|
+
// Extract invoice ID
|
|
47
|
+
const invoiceId = this.getText('//rsm:ExchangedDocument/ram:ID');
|
|
48
|
+
|
|
49
|
+
// Extract issue date
|
|
50
|
+
const issueDateStr = this.getText('//ram:IssueDateTime/udt:DateTimeString');
|
|
51
|
+
const issueDateFormat = this.getText('//ram:IssueDateTime/udt:DateTimeString/@format');
|
|
52
|
+
const issueDate = this.parseCIIDate(issueDateStr, issueDateFormat);
|
|
53
|
+
|
|
54
|
+
// Extract seller information
|
|
55
|
+
const seller = this.extractParty('//ram:SellerTradeParty');
|
|
56
|
+
|
|
57
|
+
// Extract buyer information
|
|
58
|
+
const buyer = this.extractParty('//ram:BuyerTradeParty');
|
|
59
|
+
|
|
60
|
+
// Extract items
|
|
61
|
+
const items = this.extractItems();
|
|
62
|
+
|
|
63
|
+
// Extract due date
|
|
64
|
+
const dueDateStr = this.getText('//ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime/udt:DateTimeString');
|
|
65
|
+
const dueDateFormat = this.getText('//ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime/udt:DateTimeString/@format');
|
|
66
|
+
const dueDate = dueDateStr ? this.parseCIIDate(dueDateStr, dueDateFormat) : issueDate;
|
|
67
|
+
const dueInDays = Math.round((dueDate - issueDate) / (1000 * 60 * 60 * 24));
|
|
68
|
+
|
|
69
|
+
// Extract currency
|
|
70
|
+
const currencyCode = this.getText('//ram:InvoiceCurrencyCode') || 'EUR';
|
|
71
|
+
|
|
72
|
+
// Extract total amount
|
|
73
|
+
const totalAmount = this.getNumber('//ram:GrandTotalAmount');
|
|
74
|
+
|
|
75
|
+
// Extract notes
|
|
76
|
+
const allNotes = this.extractNotes();
|
|
77
|
+
|
|
78
|
+
// Extract subject and notes separately
|
|
79
|
+
let subject = `Invoice ${invoiceId}`;
|
|
80
|
+
let notes = [...allNotes];
|
|
81
|
+
|
|
82
|
+
// If the first note doesn't look like a payment term or other standard note,
|
|
83
|
+
// treat it as the subject
|
|
84
|
+
if (allNotes.length > 0 && !allNotes[0].toLowerCase().includes('due in') &&
|
|
85
|
+
!allNotes[0].toLowerCase().includes('payment')) {
|
|
86
|
+
subject = allNotes[0];
|
|
87
|
+
notes = allNotes.slice(1); // Remove subject from notes
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check for reverse charge
|
|
91
|
+
const reverseCharge = this.exists('//ram:SpecifiedTradeAllowanceCharge/ram:ReasonCode[text()="62"]');
|
|
92
|
+
|
|
93
|
+
// Create the common invoice data
|
|
94
|
+
const invoiceData = {
|
|
95
|
+
type: 'accounting-doc' as const,
|
|
96
|
+
accountingDocType: 'invoice' as const,
|
|
97
|
+
id: invoiceId,
|
|
98
|
+
accountingDocId: invoiceId,
|
|
99
|
+
date: issueDate,
|
|
100
|
+
accountingDocStatus: 'issued' as const,
|
|
101
|
+
versionInfo: {
|
|
102
|
+
type: 'final' as const,
|
|
103
|
+
version: '1.0.0'
|
|
104
|
+
},
|
|
105
|
+
language: 'en',
|
|
106
|
+
incidenceId: invoiceId,
|
|
107
|
+
from: seller,
|
|
108
|
+
to: buyer,
|
|
109
|
+
subject: subject,
|
|
110
|
+
items: items,
|
|
111
|
+
dueInDays: dueInDays,
|
|
112
|
+
reverseCharge: reverseCharge,
|
|
113
|
+
currency: currencyCode as finance.TCurrency,
|
|
114
|
+
notes: notes,
|
|
115
|
+
deliveryDate: issueDate,
|
|
116
|
+
objectActions: []
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Validate mandatory EN16931 fields unless validation is skipped
|
|
120
|
+
if (!this.skipValidation) {
|
|
121
|
+
EN16931Validator.validateMandatoryFields(invoiceData);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return invoiceData;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Extracts party information from Factur-X XML
|
|
129
|
+
* @param partyXPath XPath to the party node
|
|
130
|
+
* @returns Party information as TContact
|
|
131
|
+
*/
|
|
132
|
+
private extractParty(partyXPath: string): business.TContact {
|
|
133
|
+
// Extract name
|
|
134
|
+
const name = this.getText(`${partyXPath}/ram:Name`);
|
|
135
|
+
|
|
136
|
+
// Extract address
|
|
137
|
+
const address: business.IAddress = {
|
|
138
|
+
streetName: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:LineOne`) || '',
|
|
139
|
+
houseNumber: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:LineTwo`) || '0',
|
|
140
|
+
postalCode: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:PostcodeCode`) || '',
|
|
141
|
+
city: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:CityName`) || '',
|
|
142
|
+
country: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:CountryID`) || '',
|
|
143
|
+
countryCode: this.getText(`${partyXPath}/ram:PostalTradeAddress/ram:CountryID`) || ''
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Extract VAT ID
|
|
147
|
+
const vatId = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]`) || '';
|
|
148
|
+
|
|
149
|
+
// Extract registration ID
|
|
150
|
+
const registrationId = this.getText(`${partyXPath}/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="FC"]`) || '';
|
|
151
|
+
|
|
152
|
+
// Create contact object
|
|
153
|
+
return {
|
|
154
|
+
type: 'company',
|
|
155
|
+
name: name,
|
|
156
|
+
description: '',
|
|
157
|
+
address: address,
|
|
158
|
+
status: 'active',
|
|
159
|
+
foundedDate: this.createDefaultDate(),
|
|
160
|
+
registrationDetails: {
|
|
161
|
+
vatId: vatId,
|
|
162
|
+
registrationId: registrationId,
|
|
163
|
+
registrationName: ''
|
|
164
|
+
}
|
|
165
|
+
} as business.TContact;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extracts invoice items from Factur-X XML
|
|
170
|
+
* @returns Array of invoice items
|
|
171
|
+
*/
|
|
172
|
+
private extractItems(): finance.TAccountingDocItem[] {
|
|
173
|
+
const items: finance.TAccountingDocItem[] = [];
|
|
174
|
+
|
|
175
|
+
// Get all item nodes
|
|
176
|
+
const itemNodes = this.select('//ram:IncludedSupplyChainTradeLineItem', this.doc);
|
|
177
|
+
|
|
178
|
+
// Process each item
|
|
179
|
+
if (Array.isArray(itemNodes)) {
|
|
180
|
+
for (let i = 0; i < itemNodes.length; i++) {
|
|
181
|
+
const itemNode = itemNodes[i];
|
|
182
|
+
|
|
183
|
+
// Extract item data
|
|
184
|
+
const name = this.getText('ram:SpecifiedTradeProduct/ram:Name', itemNode);
|
|
185
|
+
const articleNumber = this.getText('ram:SpecifiedTradeProduct/ram:SellerAssignedID', itemNode);
|
|
186
|
+
const unitQuantity = this.getNumber('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', itemNode);
|
|
187
|
+
const unitType = this.getText('ram:SpecifiedLineTradeDelivery/ram:BilledQuantity/@unitCode', itemNode) || 'EA';
|
|
188
|
+
const unitNetPrice = this.getNumber('ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount', itemNode);
|
|
189
|
+
const vatPercentage = this.getNumber('ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', itemNode);
|
|
190
|
+
|
|
191
|
+
// Create item object
|
|
192
|
+
items.push({
|
|
193
|
+
position: i + 1,
|
|
194
|
+
name: name,
|
|
195
|
+
articleNumber: articleNumber,
|
|
196
|
+
unitType: unitType,
|
|
197
|
+
unitQuantity: unitQuantity,
|
|
198
|
+
unitNetPrice: unitNetPrice,
|
|
199
|
+
vatPercentage: vatPercentage
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return items;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Extracts notes from Factur-X XML
|
|
209
|
+
* @returns Array of notes
|
|
210
|
+
*/
|
|
211
|
+
private extractNotes(): string[] {
|
|
212
|
+
const notes: string[] = [];
|
|
213
|
+
|
|
214
|
+
// Get all note nodes
|
|
215
|
+
const noteNodes = this.select('//ram:IncludedNote', this.doc);
|
|
216
|
+
|
|
217
|
+
// Process each note
|
|
218
|
+
if (Array.isArray(noteNodes)) {
|
|
219
|
+
for (let i = 0; i < noteNodes.length; i++) {
|
|
220
|
+
const noteNode = noteNodes[i];
|
|
221
|
+
const noteText = this.getText('ram:Content', noteNode);
|
|
222
|
+
|
|
223
|
+
if (noteText) {
|
|
224
|
+
notes.push(noteText);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return notes;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Creates a default date object
|
|
234
|
+
* @returns Default date object
|
|
235
|
+
*/
|
|
236
|
+
private createDefaultDate(): general.IDate {
|
|
237
|
+
return {
|
|
238
|
+
year: 2000,
|
|
239
|
+
month: 1,
|
|
240
|
+
day: 1
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|