Files
einvoice/ts/formats/validation/schematron.downloader.ts
Juergen Kunz 10e14af85b feat(validation): Implement EN16931 compliance validation types and VAT categories
- Added validation types for EN16931 compliance in `validation.types.ts`, including interfaces for `ValidationResult`, `ValidationOptions`, and `ValidationReport`.
- Introduced `VATCategoriesValidator` in `vat-categories.validator.ts` to validate VAT categories according to EN16931 rules, including detailed checks for standard, zero-rated, exempt, reverse charge, intra-community, export, and out-of-scope services.
- Enhanced `IEInvoiceMetadata` interface in `en16931-metadata.ts` to include additional fields required for full standards compliance, such as delivery information, payment information, allowances, and charges.
- Implemented helper methods for VAT calculations and validation logic to ensure accurate compliance with EN16931 standards.
2025-08-11 12:25:32 +00:00

311 lines
9.1 KiB
TypeScript

import * as plugins from '../../plugins.js';
import * as path from 'path';
import { promises as fs } from 'fs';
/**
* Schematron rule sources
*/
export interface SchematronSource {
name: string;
version: string;
url: string;
description: string;
format: 'UBL' | 'CII' | 'BOTH';
}
/**
* Official Schematron sources for e-invoicing standards
*/
export const SCHEMATRON_SOURCES: Record<string, SchematronSource[]> = {
EN16931: [
{
name: 'EN16931-UBL',
version: '1.3.14',
url: 'https://github.com/ConnectingEurope/eInvoicing-EN16931/raw/master/ubl/schematron/EN16931-UBL-validation.sch',
description: 'Official EN16931 validation rules for UBL format',
format: 'UBL'
},
{
name: 'EN16931-CII',
version: '1.3.14',
url: 'https://github.com/ConnectingEurope/eInvoicing-EN16931/raw/master/cii/schematron/EN16931-CII-validation.sch',
description: 'Official EN16931 validation rules for CII format',
format: 'CII'
},
{
name: 'EN16931-EDIFACT',
version: '1.3.14',
url: 'https://github.com/ConnectingEurope/eInvoicing-EN16931/raw/master/edifact/schematron/EN16931-EDIFACT-validation.sch',
description: 'Official EN16931 validation rules for EDIFACT format',
format: 'CII'
}
],
XRECHNUNG: [
{
name: 'XRechnung-UBL',
version: '3.0.2',
url: 'https://github.com/itplr-kosit/xrechnung-schematron/raw/master/src/schematron/ubl-invoice/XRechnung-UBL-3.0.sch',
description: 'XRechnung CIUS validation for UBL',
format: 'UBL'
},
{
name: 'XRechnung-CII',
version: '3.0.2',
url: 'https://github.com/itplr-kosit/xrechnung-schematron/raw/master/src/schematron/cii/XRechnung-CII-3.0.sch',
description: 'XRechnung CIUS validation for CII',
format: 'CII'
}
],
PEPPOL: [
{
name: 'PEPPOL-EN16931-UBL',
version: '3.0.17',
url: 'https://github.com/OpenPEPPOL/peppol-bis-invoice-3/raw/master/rules/sch/PEPPOL-EN16931-UBL.sch',
description: 'PEPPOL BIS Billing 3.0 validation rules',
format: 'UBL'
},
{
name: 'PEPPOL-T10',
version: '3.0.17',
url: 'https://github.com/OpenPEPPOL/peppol-bis-invoice-3/raw/master/rules/sch/UBL-T10.sch',
description: 'PEPPOL Transaction 10 (Invoice) validation',
format: 'UBL'
},
{
name: 'PEPPOL-T14',
version: '3.0.17',
url: 'https://github.com/OpenPEPPOL/peppol-bis-invoice-3/raw/master/rules/sch/UBL-T14.sch',
description: 'PEPPOL Transaction 14 (Credit Note) validation',
format: 'UBL'
}
]
};
/**
* Schematron downloader and cache manager
*/
export class SchematronDownloader {
private cacheDir: string;
private smartfile: any;
constructor(cacheDir: string = 'assets/schematron') {
this.cacheDir = cacheDir;
}
/**
* Initialize the downloader
*/
public async initialize(): Promise<void> {
// Ensure cache directory exists
this.smartfile = await import('@push.rocks/smartfile');
await fs.mkdir(this.cacheDir, { recursive: true });
}
/**
* Download a Schematron file
*/
public async download(source: SchematronSource): Promise<string> {
const fileName = `${source.name}-v${source.version}.sch`;
const filePath = path.join(this.cacheDir, fileName);
// Check if already cached
if (await this.isCached(filePath)) {
console.log(`Using cached Schematron: ${fileName}`);
return filePath;
}
console.log(`Downloading Schematron: ${source.name} v${source.version}`);
try {
// Download the file
const response = await fetch(source.url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const content = await response.text();
// Validate it's actually Schematron
if (!content.includes('schematron') && !content.includes('sch:schema')) {
throw new Error('Downloaded file does not appear to be Schematron');
}
// Save to cache
await fs.writeFile(filePath, content, 'utf-8');
// Also save metadata
const metaPath = filePath.replace('.sch', '.meta.json');
await fs.writeFile(metaPath, JSON.stringify({
source: source.name,
version: source.version,
url: source.url,
format: source.format,
downloadDate: new Date().toISOString()
}, null, 2), 'utf-8');
console.log(`Successfully downloaded: ${fileName}`);
return filePath;
} catch (error) {
throw new Error(`Failed to download ${source.name}: ${error.message}`);
}
}
/**
* Download all Schematron files for a standard
*/
public async downloadStandard(
standard: 'EN16931' | 'XRECHNUNG' | 'PEPPOL'
): Promise<string[]> {
const sources = SCHEMATRON_SOURCES[standard];
if (!sources) {
throw new Error(`Unknown standard: ${standard}`);
}
const paths: string[] = [];
for (const source of sources) {
try {
const path = await this.download(source);
paths.push(path);
} catch (error) {
console.warn(`Failed to download ${source.name}: ${error.message}`);
}
}
return paths;
}
/**
* Check if a file is cached
*/
private async isCached(filePath: string): Promise<boolean> {
try {
await fs.access(filePath);
// Check if file is not empty
const stats = await fs.stat(filePath);
return stats.size > 0;
} catch {
return false;
}
}
/**
* Get cached Schematron files
*/
public async getCachedFiles(): Promise<Array<{
path: string;
metadata: any;
}>> {
const files: Array<{ path: string; metadata: any }> = [];
try {
const entries = await fs.readdir(this.cacheDir);
for (const entry of entries) {
if (entry.endsWith('.sch')) {
const filePath = path.join(this.cacheDir, entry);
const metaPath = filePath.replace('.sch', '.meta.json');
try {
const metadata = JSON.parse(await fs.readFile(metaPath, 'utf-8'));
files.push({ path: filePath, metadata });
} catch {
// No metadata file
files.push({ path: filePath, metadata: null });
}
}
}
} catch (error) {
console.warn(`Failed to list cached files: ${error.message}`);
}
return files;
}
/**
* Clear cache
*/
public async clearCache(): Promise<void> {
try {
const entries = await fs.readdir(this.cacheDir);
for (const entry of entries) {
if (entry.endsWith('.sch') || entry.endsWith('.meta.json')) {
await fs.unlink(path.join(this.cacheDir, entry));
}
}
console.log('Schematron cache cleared');
} catch (error) {
console.warn(`Failed to clear cache: ${error.message}`);
}
}
/**
* Get the appropriate Schematron for a format
*/
public async getSchematronForFormat(
standard: 'EN16931' | 'XRECHNUNG' | 'PEPPOL',
format: 'UBL' | 'CII'
): Promise<string | null> {
const sources = SCHEMATRON_SOURCES[standard];
if (!sources) return null;
const source = sources.find(s => s.format === format || s.format === 'BOTH');
if (!source) return null;
return await this.download(source);
}
/**
* Update all cached Schematron files
*/
public async updateAll(): Promise<void> {
console.log('Updating all Schematron files...');
for (const standard of ['EN16931', 'XRECHNUNG', 'PEPPOL'] as const) {
await this.downloadStandard(standard);
}
console.log('All Schematron files updated');
}
}
/**
* ISO Schematron skeleton URLs
* These are needed to compile Schematron to XSLT
*/
export const ISO_SCHEMATRON_SKELETONS = {
'iso_dsdl_include.xsl': 'https://github.com/Schematron/schematron/raw/master/trunk/schematron/code/iso_dsdl_include.xsl',
'iso_abstract_expand.xsl': 'https://github.com/Schematron/schematron/raw/master/trunk/schematron/code/iso_abstract_expand.xsl',
'iso_svrl_for_xslt2.xsl': 'https://github.com/Schematron/schematron/raw/master/trunk/schematron/code/iso_svrl_for_xslt2.xsl',
'iso_schematron_skeleton_for_saxon.xsl': 'https://github.com/Schematron/schematron/raw/master/trunk/schematron/code/iso_schematron_skeleton_for_saxon.xsl'
};
/**
* Download ISO Schematron skeleton files
*/
export async function downloadISOSkeletons(targetDir: string = 'assets/schematron/iso'): Promise<void> {
await fs.mkdir(targetDir, { recursive: true });
console.log('Downloading ISO Schematron skeleton files...');
for (const [name, url] of Object.entries(ISO_SCHEMATRON_SKELETONS)) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const content = await response.text();
const filePath = path.join(targetDir, name);
await fs.writeFile(filePath, content, 'utf-8');
console.log(`Downloaded: ${name}`);
} catch (error) {
console.warn(`Failed to download ${name}: ${error.message}`);
}
}
console.log('ISO Schematron skeleton download complete');
}