@fin.cx/einvoice 5.0.3 → 5.1.3
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.js +1 -1
- package/dist_ts/einvoice.d.ts +4 -8
- package/dist_ts/einvoice.js +46 -7
- package/dist_ts/formats/converters/xml-to-einvoice.converter.d.ts +21 -0
- package/dist_ts/formats/converters/xml-to-einvoice.converter.js +127 -0
- package/dist_ts/formats/semantic/bt-bg.model.d.ts +445 -0
- package/dist_ts/formats/semantic/bt-bg.model.js +12 -0
- package/dist_ts/formats/semantic/semantic.adapter.d.ts +87 -0
- package/dist_ts/formats/semantic/semantic.adapter.js +538 -0
- package/dist_ts/formats/semantic/semantic.validator.d.ts +48 -0
- package/dist_ts/formats/semantic/semantic.validator.js +596 -0
- package/dist_ts/formats/utils/currency.calculator.decimal.d.ts +98 -0
- package/dist_ts/formats/utils/currency.calculator.decimal.js +212 -0
- package/dist_ts/formats/utils/currency.utils.d.ts +99 -0
- package/dist_ts/formats/utils/currency.utils.js +250 -0
- package/dist_ts/formats/utils/decimal.d.ts +151 -0
- package/dist_ts/formats/utils/decimal.js +435 -0
- package/dist_ts/formats/validation/codelist.validator.d.ts +49 -0
- package/dist_ts/formats/validation/codelist.validator.js +188 -0
- package/dist_ts/formats/validation/conformance.harness.d.ts +99 -0
- package/dist_ts/formats/validation/conformance.harness.js +471 -0
- package/dist_ts/formats/validation/en16931.business-rules.validator.d.ts +58 -0
- package/dist_ts/formats/validation/en16931.business-rules.validator.js +459 -0
- package/dist_ts/formats/validation/facturx.validator.d.ts +75 -0
- package/dist_ts/formats/validation/facturx.validator.js +522 -0
- package/dist_ts/formats/validation/integrated.validator.d.ts +82 -0
- package/dist_ts/formats/validation/integrated.validator.js +311 -0
- package/dist_ts/formats/validation/peppol.validator.d.ts +77 -0
- package/dist_ts/formats/validation/peppol.validator.js +529 -0
- package/dist_ts/formats/validation/schematron.downloader.d.ts +71 -0
- package/dist_ts/formats/validation/schematron.downloader.js +254 -0
- package/dist_ts/formats/validation/schematron.integration.d.ts +60 -0
- package/dist_ts/formats/validation/schematron.integration.js +230 -0
- package/dist_ts/formats/validation/schematron.validator.d.ts +79 -0
- package/dist_ts/formats/validation/schematron.validator.js +288 -0
- package/dist_ts/formats/validation/schematron.worker.d.ts +50 -0
- package/dist_ts/formats/validation/schematron.worker.js +185 -0
- package/dist_ts/formats/validation/validation.types.d.ts +163 -0
- package/dist_ts/formats/validation/validation.types.js +181 -0
- package/dist_ts/formats/validation/vat-categories.validator.d.ts +87 -0
- package/dist_ts/formats/validation/vat-categories.validator.js +497 -0
- package/dist_ts/formats/validation/xrechnung.validator.d.ts +92 -0
- package/dist_ts/formats/validation/xrechnung.validator.js +417 -0
- package/dist_ts/interfaces/common.d.ts +1 -0
- package/dist_ts/interfaces/en16931-metadata.d.ts +83 -0
- package/dist_ts/interfaces/en16931-metadata.js +6 -0
- package/dist_ts/plugins.d.ts +3 -1
- package/dist_ts/plugins.js +7 -2
- package/dist_ts_install/download-schematron.d.ts +6 -0
- package/dist_ts_install/download-schematron.js +62 -0
- package/dist_ts_install/download-test-samples.d.ts +19 -0
- package/dist_ts_install/download-test-samples.js +169 -0
- package/dist_ts_install/download-xrechnung-rules.d.ts +7 -0
- package/dist_ts_install/download-xrechnung-rules.js +146 -0
- package/dist_ts_install/index.d.ts +8 -0
- package/dist_ts_install/index.js +246 -0
- package/npmextra.json +1 -1
- package/package.json +12 -6
- package/readme.md +255 -810
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/einvoice.ts +63 -14
- package/ts/formats/converters/xml-to-einvoice.converter.ts +142 -0
- package/ts/formats/semantic/bt-bg.model.ts +524 -0
- package/ts/formats/semantic/semantic.adapter.ts +600 -0
- package/ts/formats/semantic/semantic.validator.ts +654 -0
- package/ts/formats/utils/currency.calculator.decimal.ts +323 -0
- package/ts/formats/utils/currency.utils.ts +299 -0
- package/ts/formats/utils/decimal.ts +509 -0
- package/ts/formats/validation/codelist.validator.ts +317 -0
- package/ts/formats/validation/conformance.harness.ts +591 -0
- package/ts/formats/validation/en16931.business-rules.validator.ts +694 -0
- package/ts/formats/validation/facturx.validator.ts +579 -0
- package/ts/formats/validation/integrated.validator.ts +405 -0
- package/ts/formats/validation/peppol.validator.ts +589 -0
- package/ts/formats/validation/schematron.downloader.ts +304 -0
- package/ts/formats/validation/schematron.integration.ts +285 -0
- package/ts/formats/validation/schematron.validator.ts +348 -0
- package/ts/formats/validation/schematron.worker.ts +221 -0
- package/ts/formats/validation/validation.types.ts +274 -0
- package/ts/formats/validation/vat-categories.validator.ts +845 -0
- package/ts/formats/validation/xrechnung.validator.ts +494 -0
- package/ts/interfaces/common.ts +1 -0
- package/ts/interfaces/en16931-metadata.ts +104 -0
- package/ts/plugins.ts +8 -0
- package/ts/tspublish.json +3 -0
- package/dist_ts/classes.decoder.d.ts +0 -40
- package/dist_ts/classes.decoder.js +0 -320
- package/dist_ts/classes.encoder.d.ts +0 -51
- package/dist_ts/classes.encoder.js +0 -293
- package/dist_ts/classes.xinvoice.d.ts +0 -136
- package/dist_ts/classes.xinvoice.js +0 -352
- package/dist_ts/formats/base.decoder.d.ts +0 -19
- package/dist_ts/formats/base.decoder.js +0 -130
- package/dist_ts/formats/base.validator.d.ts +0 -44
- package/dist_ts/formats/base.validator.js +0 -39
- package/dist_ts/formats/decoder.factory.d.ts +0 -15
- package/dist_ts/formats/decoder.factory.js +0 -46
- package/dist_ts/formats/factorx.decoder.d.ts +0 -18
- package/dist_ts/formats/factorx.decoder.js +0 -178
- package/dist_ts/formats/factorx.encoder.d.ts +0 -51
- package/dist_ts/formats/factorx.encoder.js +0 -293
- package/dist_ts/formats/facturx.decoder.d.ts +0 -18
- package/dist_ts/formats/facturx.decoder.js +0 -210
- package/dist_ts/formats/facturx.encoder.d.ts +0 -51
- package/dist_ts/formats/facturx.encoder.js +0 -293
- package/dist_ts/formats/facturx.validator.d.ts +0 -67
- package/dist_ts/formats/facturx.validator.js +0 -266
- package/dist_ts/formats/pdf/robust-pdf.extractor.d.ts +0 -40
- package/dist_ts/formats/pdf/robust-pdf.extractor.js +0 -324
- package/dist_ts/formats/ubl.validator.d.ts +0 -82
- package/dist_ts/formats/ubl.validator.js +0 -306
- package/dist_ts/formats/validator.factory.d.ts +0 -18
- package/dist_ts/formats/validator.factory.js +0 -78
- package/dist_ts/formats/xinvoice.decoder.d.ts +0 -28
- package/dist_ts/formats/xinvoice.decoder.js +0 -332
- package/dist_ts/formats/xinvoice.encoder.d.ts +0 -19
- package/dist_ts/formats/xinvoice.encoder.js +0 -282
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Schematron rule sources
|
|
7
|
+
*/
|
|
8
|
+
export interface SchematronSource {
|
|
9
|
+
name: string;
|
|
10
|
+
version: string;
|
|
11
|
+
url: string;
|
|
12
|
+
description: string;
|
|
13
|
+
format: 'UBL' | 'CII' | 'BOTH';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Official Schematron sources for e-invoicing standards
|
|
18
|
+
*/
|
|
19
|
+
export const SCHEMATRON_SOURCES: Record<string, SchematronSource[]> = {
|
|
20
|
+
EN16931: [
|
|
21
|
+
{
|
|
22
|
+
name: 'EN16931-UBL',
|
|
23
|
+
version: '1.3.14',
|
|
24
|
+
url: 'https://github.com/ConnectingEurope/eInvoicing-EN16931/raw/master/ubl/schematron/EN16931-UBL-validation.sch',
|
|
25
|
+
description: 'Official EN16931 validation rules for UBL format',
|
|
26
|
+
format: 'UBL'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'EN16931-CII',
|
|
30
|
+
version: '1.3.14',
|
|
31
|
+
url: 'https://github.com/ConnectingEurope/eInvoicing-EN16931/raw/master/cii/schematron/EN16931-CII-validation.sch',
|
|
32
|
+
description: 'Official EN16931 validation rules for CII format',
|
|
33
|
+
format: 'CII'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'EN16931-EDIFACT',
|
|
37
|
+
version: '1.3.14',
|
|
38
|
+
url: 'https://github.com/ConnectingEurope/eInvoicing-EN16931/raw/master/edifact/schematron/EN16931-EDIFACT-validation.sch',
|
|
39
|
+
description: 'Official EN16931 validation rules for EDIFACT format',
|
|
40
|
+
format: 'CII'
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
XRECHNUNG: [
|
|
44
|
+
{
|
|
45
|
+
name: 'XRechnung-UBL',
|
|
46
|
+
version: '3.0.2',
|
|
47
|
+
url: 'https://github.com/itplr-kosit/xrechnung-schematron/raw/master/src/validation/schematron/ubl/XRechnung-UBL-validation.sch',
|
|
48
|
+
description: 'XRechnung CIUS validation for UBL',
|
|
49
|
+
format: 'UBL'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'XRechnung-CII',
|
|
53
|
+
version: '3.0.2',
|
|
54
|
+
url: 'https://github.com/itplr-kosit/xrechnung-schematron/raw/master/src/validation/schematron/cii/XRechnung-CII-validation.sch',
|
|
55
|
+
description: 'XRechnung CIUS validation for CII',
|
|
56
|
+
format: 'CII'
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
PEPPOL: [
|
|
60
|
+
{
|
|
61
|
+
name: 'PEPPOL-EN16931-UBL',
|
|
62
|
+
version: '3.0.17',
|
|
63
|
+
url: 'https://github.com/OpenPEPPOL/peppol-bis-invoice-3/raw/master/rules/sch/PEPPOL-EN16931-UBL.sch',
|
|
64
|
+
description: 'PEPPOL BIS Billing 3.0 validation rules for UBL',
|
|
65
|
+
format: 'UBL'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'PEPPOL-EN16931-CII',
|
|
69
|
+
version: '3.0.17',
|
|
70
|
+
url: 'https://github.com/OpenPEPPOL/peppol-bis-invoice-3/raw/master/rules/sch/PEPPOL-EN16931-CII.sch',
|
|
71
|
+
description: 'PEPPOL BIS Billing 3.0 validation rules for CII',
|
|
72
|
+
format: 'CII'
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Schematron downloader and cache manager
|
|
79
|
+
*/
|
|
80
|
+
export class SchematronDownloader {
|
|
81
|
+
private cacheDir: string;
|
|
82
|
+
private smartfile: any;
|
|
83
|
+
|
|
84
|
+
constructor(cacheDir: string = 'assets_downloaded/schematron') {
|
|
85
|
+
this.cacheDir = cacheDir;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Initialize the downloader
|
|
90
|
+
*/
|
|
91
|
+
public async initialize(): Promise<void> {
|
|
92
|
+
// Ensure cache directory exists
|
|
93
|
+
this.smartfile = await import('@push.rocks/smartfile');
|
|
94
|
+
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Download a Schematron file
|
|
99
|
+
*/
|
|
100
|
+
public async download(source: SchematronSource): Promise<string> {
|
|
101
|
+
const fileName = `${source.name}-v${source.version}.sch`;
|
|
102
|
+
const filePath = path.join(this.cacheDir, fileName);
|
|
103
|
+
|
|
104
|
+
// Check if already cached
|
|
105
|
+
if (await this.isCached(filePath)) {
|
|
106
|
+
console.log(`Using cached Schematron: ${fileName}`);
|
|
107
|
+
return filePath;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(`Downloading Schematron: ${source.name} v${source.version}`);
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// Download the file
|
|
114
|
+
const response = await fetch(source.url);
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const content = await response.text();
|
|
120
|
+
|
|
121
|
+
// Validate it's actually Schematron
|
|
122
|
+
if (!content.includes('schematron') && !content.includes('sch:schema')) {
|
|
123
|
+
throw new Error('Downloaded file does not appear to be Schematron');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Save to cache
|
|
127
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
128
|
+
|
|
129
|
+
// Also save metadata
|
|
130
|
+
const metaPath = filePath.replace('.sch', '.meta.json');
|
|
131
|
+
await fs.writeFile(metaPath, JSON.stringify({
|
|
132
|
+
source: source.name,
|
|
133
|
+
version: source.version,
|
|
134
|
+
url: source.url,
|
|
135
|
+
format: source.format,
|
|
136
|
+
downloadDate: new Date().toISOString()
|
|
137
|
+
}, null, 2), 'utf-8');
|
|
138
|
+
|
|
139
|
+
console.log(`Successfully downloaded: ${fileName}`);
|
|
140
|
+
return filePath;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
throw new Error(`Failed to download ${source.name}: ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Download all Schematron files for a standard
|
|
148
|
+
*/
|
|
149
|
+
public async downloadStandard(
|
|
150
|
+
standard: 'EN16931' | 'XRECHNUNG' | 'PEPPOL'
|
|
151
|
+
): Promise<string[]> {
|
|
152
|
+
const sources = SCHEMATRON_SOURCES[standard];
|
|
153
|
+
if (!sources) {
|
|
154
|
+
throw new Error(`Unknown standard: ${standard}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const paths: string[] = [];
|
|
158
|
+
for (const source of sources) {
|
|
159
|
+
try {
|
|
160
|
+
const path = await this.download(source);
|
|
161
|
+
paths.push(path);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.warn(`Failed to download ${source.name}: ${error.message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return paths;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if a file is cached
|
|
172
|
+
*/
|
|
173
|
+
private async isCached(filePath: string): Promise<boolean> {
|
|
174
|
+
try {
|
|
175
|
+
await fs.access(filePath);
|
|
176
|
+
|
|
177
|
+
// Check if file is not empty
|
|
178
|
+
const stats = await fs.stat(filePath);
|
|
179
|
+
return stats.size > 0;
|
|
180
|
+
} catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get cached Schematron files
|
|
187
|
+
*/
|
|
188
|
+
public async getCachedFiles(): Promise<Array<{
|
|
189
|
+
path: string;
|
|
190
|
+
metadata: any;
|
|
191
|
+
}>> {
|
|
192
|
+
const files: Array<{ path: string; metadata: any }> = [];
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const entries = await fs.readdir(this.cacheDir);
|
|
196
|
+
|
|
197
|
+
for (const entry of entries) {
|
|
198
|
+
if (entry.endsWith('.sch')) {
|
|
199
|
+
const filePath = path.join(this.cacheDir, entry);
|
|
200
|
+
const metaPath = filePath.replace('.sch', '.meta.json');
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const metadata = JSON.parse(await fs.readFile(metaPath, 'utf-8'));
|
|
204
|
+
files.push({ path: filePath, metadata });
|
|
205
|
+
} catch {
|
|
206
|
+
// No metadata file
|
|
207
|
+
files.push({ path: filePath, metadata: null });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.warn(`Failed to list cached files: ${error.message}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return files;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Clear cache
|
|
220
|
+
*/
|
|
221
|
+
public async clearCache(): Promise<void> {
|
|
222
|
+
try {
|
|
223
|
+
const entries = await fs.readdir(this.cacheDir);
|
|
224
|
+
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
if (entry.endsWith('.sch') || entry.endsWith('.meta.json')) {
|
|
227
|
+
await fs.unlink(path.join(this.cacheDir, entry));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.log('Schematron cache cleared');
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.warn(`Failed to clear cache: ${error.message}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get the appropriate Schematron for a format
|
|
239
|
+
*/
|
|
240
|
+
public async getSchematronForFormat(
|
|
241
|
+
standard: 'EN16931' | 'XRECHNUNG' | 'PEPPOL',
|
|
242
|
+
format: 'UBL' | 'CII'
|
|
243
|
+
): Promise<string | null> {
|
|
244
|
+
const sources = SCHEMATRON_SOURCES[standard];
|
|
245
|
+
if (!sources) return null;
|
|
246
|
+
|
|
247
|
+
const source = sources.find(s => s.format === format || s.format === 'BOTH');
|
|
248
|
+
if (!source) return null;
|
|
249
|
+
|
|
250
|
+
return await this.download(source);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Update all cached Schematron files
|
|
255
|
+
*/
|
|
256
|
+
public async updateAll(): Promise<void> {
|
|
257
|
+
console.log('Updating all Schematron files...');
|
|
258
|
+
|
|
259
|
+
for (const standard of ['EN16931', 'XRECHNUNG', 'PEPPOL'] as const) {
|
|
260
|
+
await this.downloadStandard(standard);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
console.log('All Schematron files updated');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* ISO Schematron skeleton URLs
|
|
269
|
+
* These are needed to compile Schematron to XSLT
|
|
270
|
+
*/
|
|
271
|
+
export const ISO_SCHEMATRON_SKELETONS = {
|
|
272
|
+
'iso_dsdl_include.xsl': 'https://github.com/Schematron/schematron/raw/master/trunk/schematron/code/iso_dsdl_include.xsl',
|
|
273
|
+
'iso_abstract_expand.xsl': 'https://github.com/Schematron/schematron/raw/master/trunk/schematron/code/iso_abstract_expand.xsl',
|
|
274
|
+
'iso_svrl_for_xslt2.xsl': 'https://github.com/Schematron/schematron/raw/master/trunk/schematron/code/iso_svrl_for_xslt2.xsl',
|
|
275
|
+
'iso_schematron_skeleton_for_saxon.xsl': 'https://github.com/Schematron/schematron/raw/master/trunk/schematron/code/iso_schematron_skeleton_for_saxon.xsl'
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Download ISO Schematron skeleton files
|
|
280
|
+
*/
|
|
281
|
+
export async function downloadISOSkeletons(targetDir: string = 'assets_downloaded/schematron/iso'): Promise<void> {
|
|
282
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
283
|
+
|
|
284
|
+
console.log('Downloading ISO Schematron skeleton files...');
|
|
285
|
+
|
|
286
|
+
for (const [name, url] of Object.entries(ISO_SCHEMATRON_SKELETONS)) {
|
|
287
|
+
try {
|
|
288
|
+
const response = await fetch(url);
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
throw new Error(`HTTP ${response.status}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const content = await response.text();
|
|
294
|
+
const filePath = path.join(targetDir, name);
|
|
295
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
296
|
+
|
|
297
|
+
console.log(`Downloaded: ${name}`);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.warn(`Failed to download ${name}: ${error.message}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
console.log('ISO Schematron skeleton download complete');
|
|
304
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration of official Schematron validation with the EInvoice module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { SchematronValidator, HybridValidator } from './schematron.validator.js';
|
|
6
|
+
import { EN16931BusinessRulesValidator } from './en16931.business-rules.validator.js';
|
|
7
|
+
import { CodeListValidator } from './codelist.validator.js';
|
|
8
|
+
import type { ValidationResult, ValidationOptions, ValidationReport } from './validation.types.js';
|
|
9
|
+
import type { EInvoice } from '../../einvoice.js';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Integrated validator combining TypeScript and Schematron validation
|
|
15
|
+
*/
|
|
16
|
+
export class IntegratedValidator {
|
|
17
|
+
private hybridValidator: HybridValidator;
|
|
18
|
+
private schematronValidator: SchematronValidator;
|
|
19
|
+
private businessRulesValidator: EN16931BusinessRulesValidator;
|
|
20
|
+
private codeListValidator: CodeListValidator;
|
|
21
|
+
private schematronLoaded: boolean = false;
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
this.schematronValidator = new SchematronValidator();
|
|
25
|
+
this.hybridValidator = new HybridValidator(this.schematronValidator);
|
|
26
|
+
this.businessRulesValidator = new EN16931BusinessRulesValidator();
|
|
27
|
+
this.codeListValidator = new CodeListValidator();
|
|
28
|
+
|
|
29
|
+
// Add TypeScript validators to hybrid pipeline
|
|
30
|
+
this.setupTypeScriptValidators();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Setup TypeScript validators in the hybrid pipeline
|
|
35
|
+
*/
|
|
36
|
+
private setupTypeScriptValidators(): void {
|
|
37
|
+
// Wrap business rules validator
|
|
38
|
+
this.hybridValidator.addTSValidator({
|
|
39
|
+
validate: (xml: string) => {
|
|
40
|
+
// Note: This would need the invoice object, not XML
|
|
41
|
+
// In practice, we'd parse the XML to EInvoice first
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Load Schematron for a specific format and standard
|
|
49
|
+
*/
|
|
50
|
+
public async loadSchematron(
|
|
51
|
+
standard: 'EN16931' | 'PEPPOL' | 'XRECHNUNG',
|
|
52
|
+
format: 'UBL' | 'CII'
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
const schematronPath = await this.getSchematronPath(standard, format);
|
|
55
|
+
|
|
56
|
+
if (!schematronPath) {
|
|
57
|
+
throw new Error(`No Schematron available for ${standard} ${format}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check if file exists
|
|
61
|
+
try {
|
|
62
|
+
await fs.access(schematronPath);
|
|
63
|
+
} catch {
|
|
64
|
+
throw new Error(`Schematron file not found: ${schematronPath}. Run 'npm run download-schematron' first.`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await this.schematronValidator.loadSchematron(schematronPath, true);
|
|
68
|
+
this.schematronLoaded = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the path to the appropriate Schematron file
|
|
73
|
+
*/
|
|
74
|
+
private async getSchematronPath(
|
|
75
|
+
standard: 'EN16931' | 'PEPPOL' | 'XRECHNUNG',
|
|
76
|
+
format: 'UBL' | 'CII'
|
|
77
|
+
): Promise<string | null> {
|
|
78
|
+
const basePath = 'assets_downloaded/schematron';
|
|
79
|
+
|
|
80
|
+
// Map standard and format to file pattern
|
|
81
|
+
const patterns: Record<string, Record<string, string>> = {
|
|
82
|
+
EN16931: {
|
|
83
|
+
UBL: 'EN16931-UBL-*.sch',
|
|
84
|
+
CII: 'EN16931-CII-*.sch'
|
|
85
|
+
},
|
|
86
|
+
PEPPOL: {
|
|
87
|
+
UBL: 'PEPPOL-EN16931-UBL-*.sch',
|
|
88
|
+
CII: 'PEPPOL-EN16931-CII-*.sch'
|
|
89
|
+
},
|
|
90
|
+
XRECHNUNG: {
|
|
91
|
+
UBL: 'XRechnung-UBL-*.sch',
|
|
92
|
+
CII: 'XRechnung-CII-*.sch'
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const pattern = patterns[standard]?.[format];
|
|
97
|
+
if (!pattern) return null;
|
|
98
|
+
|
|
99
|
+
// Find matching files
|
|
100
|
+
try {
|
|
101
|
+
const files = await fs.readdir(basePath);
|
|
102
|
+
const regex = new RegExp(pattern.replace('*', '.*'));
|
|
103
|
+
const matches = files.filter(f => regex.test(f));
|
|
104
|
+
|
|
105
|
+
if (matches.length > 0) {
|
|
106
|
+
// Return the most recent version (lexicographically last)
|
|
107
|
+
matches.sort();
|
|
108
|
+
return path.join(basePath, matches[matches.length - 1]);
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
// Directory doesn't exist
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Validate an invoice using all available validators
|
|
119
|
+
*/
|
|
120
|
+
public async validate(
|
|
121
|
+
invoice: EInvoice,
|
|
122
|
+
xmlContent?: string,
|
|
123
|
+
options: ValidationOptions = {}
|
|
124
|
+
): Promise<ValidationReport> {
|
|
125
|
+
const startTime = Date.now();
|
|
126
|
+
const results: ValidationResult[] = [];
|
|
127
|
+
|
|
128
|
+
// Determine format hint
|
|
129
|
+
const formatHint = options.formatHint || this.detectFormat(xmlContent);
|
|
130
|
+
|
|
131
|
+
// Run TypeScript validators
|
|
132
|
+
if (options.checkCodeLists !== false) {
|
|
133
|
+
results.push(...this.codeListValidator.validate(invoice));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
results.push(...this.businessRulesValidator.validate(invoice, options));
|
|
137
|
+
|
|
138
|
+
// Run Schematron validation if XML is provided and Schematron is loaded
|
|
139
|
+
if (xmlContent && this.schematronLoaded) {
|
|
140
|
+
try {
|
|
141
|
+
const schematronResults = await this.schematronValidator.validate(xmlContent, {
|
|
142
|
+
includeWarnings: !options.strictMode,
|
|
143
|
+
parameters: {
|
|
144
|
+
profile: options.profile
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
results.push(...schematronResults);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.warn(`Schematron validation failed: ${error.message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Calculate statistics
|
|
154
|
+
const errorCount = results.filter(r => r.severity === 'error').length;
|
|
155
|
+
const warningCount = results.filter(r => r.severity === 'warning').length;
|
|
156
|
+
const infoCount = results.filter(r => r.severity === 'info').length;
|
|
157
|
+
|
|
158
|
+
// Estimate rule coverage
|
|
159
|
+
const totalRules = this.estimateTotalRules(options.profile);
|
|
160
|
+
const rulesChecked = new Set(results.map(r => r.ruleId)).size;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
valid: errorCount === 0,
|
|
164
|
+
profile: options.profile || 'EN16931',
|
|
165
|
+
timestamp: new Date().toISOString(),
|
|
166
|
+
validatorVersion: '1.0.0',
|
|
167
|
+
rulesetVersion: '1.3.14',
|
|
168
|
+
results,
|
|
169
|
+
errorCount,
|
|
170
|
+
warningCount,
|
|
171
|
+
infoCount,
|
|
172
|
+
rulesChecked,
|
|
173
|
+
rulesTotal: totalRules,
|
|
174
|
+
coverage: (rulesChecked / totalRules) * 100,
|
|
175
|
+
validationTime: Date.now() - startTime,
|
|
176
|
+
documentId: invoice.accountingDocId,
|
|
177
|
+
documentType: invoice.accountingDocType,
|
|
178
|
+
format: formatHint
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Detect format from XML content
|
|
184
|
+
*/
|
|
185
|
+
private detectFormat(xmlContent?: string): 'UBL' | 'CII' | undefined {
|
|
186
|
+
if (!xmlContent) return undefined;
|
|
187
|
+
|
|
188
|
+
if (xmlContent.includes('urn:oasis:names:specification:ubl:schema:xsd:Invoice-2')) {
|
|
189
|
+
return 'UBL';
|
|
190
|
+
} else if (xmlContent.includes('urn:un:unece:uncefact:data:standard:CrossIndustryInvoice')) {
|
|
191
|
+
return 'CII';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Estimate total number of rules for a profile
|
|
199
|
+
*/
|
|
200
|
+
private estimateTotalRules(profile?: string): number {
|
|
201
|
+
const ruleCounts: Record<string, number> = {
|
|
202
|
+
EN16931: 150,
|
|
203
|
+
PEPPOL_BIS_3_0: 250,
|
|
204
|
+
XRECHNUNG_3_0: 280,
|
|
205
|
+
FACTURX_BASIC: 100,
|
|
206
|
+
FACTURX_EN16931: 150
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
return ruleCounts[profile || 'EN16931'] || 150;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Validate with automatic format detection
|
|
214
|
+
*/
|
|
215
|
+
public async validateAuto(
|
|
216
|
+
invoice: EInvoice,
|
|
217
|
+
xmlContent?: string
|
|
218
|
+
): Promise<ValidationReport> {
|
|
219
|
+
// Auto-detect format
|
|
220
|
+
const format = this.detectFormat(xmlContent);
|
|
221
|
+
|
|
222
|
+
// Try to load appropriate Schematron
|
|
223
|
+
if (format && !this.schematronLoaded) {
|
|
224
|
+
try {
|
|
225
|
+
await this.loadSchematron('EN16931', format);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.warn(`Could not load Schematron: ${error.message}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return this.validate(invoice, xmlContent, {
|
|
232
|
+
formatHint: format,
|
|
233
|
+
checkCalculations: true,
|
|
234
|
+
checkVAT: true,
|
|
235
|
+
checkCodeLists: true
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Check if Schematron validation is available
|
|
241
|
+
*/
|
|
242
|
+
public hasSchematron(): boolean {
|
|
243
|
+
return this.schematronLoaded;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get available Schematron files
|
|
248
|
+
*/
|
|
249
|
+
public async getAvailableSchematron(): Promise<Array<{
|
|
250
|
+
standard: string;
|
|
251
|
+
format: string;
|
|
252
|
+
path: string;
|
|
253
|
+
}>> {
|
|
254
|
+
const available: Array<{ standard: string; format: string; path: string }> = [];
|
|
255
|
+
|
|
256
|
+
for (const standard of ['EN16931', 'PEPPOL', 'XRECHNUNG'] as const) {
|
|
257
|
+
for (const format of ['UBL', 'CII'] as const) {
|
|
258
|
+
const schematronPath = await this.getSchematronPath(standard, format);
|
|
259
|
+
if (schematronPath) {
|
|
260
|
+
available.push({ standard, format, path: schematronPath });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return available;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Create a pre-configured validator for a specific standard
|
|
271
|
+
*/
|
|
272
|
+
export async function createStandardValidator(
|
|
273
|
+
standard: 'EN16931' | 'PEPPOL' | 'XRECHNUNG',
|
|
274
|
+
format: 'UBL' | 'CII'
|
|
275
|
+
): Promise<IntegratedValidator> {
|
|
276
|
+
const validator = new IntegratedValidator();
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
await validator.loadSchematron(standard, format);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.warn(`Schematron not available for ${standard} ${format}: ${error.message}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return validator;
|
|
285
|
+
}
|