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.
This commit is contained in:
311
ts/formats/validation/schematron.downloader.ts
Normal file
311
ts/formats/validation/schematron.downloader.ts
Normal file
@@ -0,0 +1,311 @@
|
||||
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');
|
||||
}
|
Reference in New Issue
Block a user