feat: Implement PEPPOL and XRechnung validators for compliance with e-invoice specifications
- Added PeppolValidator class to validate PEPPOL BIS 3.0 invoices, including checks for endpoint IDs, document type IDs, process IDs, party identification, and business rules.
- Implemented validation for GLN check digits, document types, and transport protocols specific to PEPPOL.
- Added XRechnungValidator class to validate XRechnung 3.0 invoices, focusing on German-specific requirements such as Leitweg-ID, payment details, seller contact, and tax registration.
- Included validation for IBAN and BIC formats, ensuring compliance with SEPA regulations.
- Established methods for checking B2G invoice indicators and validating mandatory fields for both validators.
2025-08-11 18:07:01 +00:00
|
|
|
#!/usr/bin/env tsx
|
|
|
|
/**
|
|
|
|
* Downloads official XRechnung Schematron validation rules
|
|
|
|
* from the KoSIT repositories
|
|
|
|
*/
|
|
|
|
|
|
|
|
import * as fs from 'fs';
|
|
|
|
import * as path from 'path';
|
|
|
|
import { execSync } from 'child_process';
|
|
|
|
|
|
|
|
const XRECHNUNG_VERSION = '3.0.2'; // Latest version as of 2025
|
|
|
|
const VALIDATOR_VERSION = '2025-07-31'; // Next release date
|
|
|
|
|
|
|
|
const REPOS = {
|
|
|
|
schematron: {
|
|
|
|
url: 'https://github.com/itplr-kosit/xrechnung-schematron/archive/refs/tags/release-3.0.2.zip',
|
|
|
|
dir: 'xrechnung-schematron'
|
|
|
|
},
|
|
|
|
validator: {
|
|
|
|
url: 'https://github.com/itplr-kosit/validator-configuration-xrechnung/releases/download/release-2024-07-31/validator-configuration-xrechnung_3.0.1_2024-07-31.zip',
|
|
|
|
dir: 'xrechnung-validator'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const ASSETS_DIR = path.join(process.cwd(), 'assets', 'schematron', 'xrechnung');
|
|
|
|
|
|
|
|
async function downloadFile(url: string, destination: string): Promise<void> {
|
|
|
|
console.log(`Downloading ${url}...`);
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Use curl to download the file
|
|
|
|
execSync(`curl -L -o "${destination}" "${url}"`, { stdio: 'inherit' });
|
|
|
|
console.log(`Downloaded to ${destination}`);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Failed to download ${url}:`, error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function extractZip(zipFile: string, destination: string): Promise<void> {
|
|
|
|
console.log(`Extracting ${zipFile}...`);
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Create destination directory if it doesn't exist
|
|
|
|
fs.mkdirSync(destination, { recursive: true });
|
|
|
|
|
|
|
|
// Extract using unzip
|
|
|
|
execSync(`unzip -o "${zipFile}" -d "${destination}"`, { stdio: 'inherit' });
|
|
|
|
console.log(`Extracted to ${destination}`);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Failed to extract ${zipFile}:`, error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function downloadXRechnungRules(): Promise<void> {
|
|
|
|
console.log('Starting XRechnung Schematron rules download...\n');
|
|
|
|
|
|
|
|
// Create assets directory
|
|
|
|
fs.mkdirSync(ASSETS_DIR, { recursive: true });
|
|
|
|
|
|
|
|
const tempDir = path.join(ASSETS_DIR, 'temp');
|
|
|
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
|
|
|
|
|
|
// Download and extract Schematron rules
|
|
|
|
console.log('1. Downloading XRechnung Schematron rules...');
|
|
|
|
const schematronZip = path.join(tempDir, 'xrechnung-schematron.zip');
|
|
|
|
await downloadFile(REPOS.schematron.url, schematronZip);
|
|
|
|
|
|
|
|
const schematronDir = path.join(ASSETS_DIR, REPOS.schematron.dir);
|
|
|
|
await extractZip(schematronZip, schematronDir);
|
|
|
|
|
|
|
|
// Find the actual Schematron files
|
|
|
|
const schematronExtractedDir = path.join(schematronDir, `xrechnung-schematron-release-${XRECHNUNG_VERSION}`);
|
|
|
|
const schematronValidationDir = path.join(schematronExtractedDir, 'validation', 'schematron');
|
|
|
|
|
|
|
|
if (fs.existsSync(schematronValidationDir)) {
|
|
|
|
console.log('\nFound Schematron validation files:');
|
|
|
|
|
|
|
|
// List UBL Schematron files
|
|
|
|
const ublDir = path.join(schematronValidationDir, 'ubl-inv');
|
|
|
|
if (fs.existsSync(ublDir)) {
|
|
|
|
const ublFiles = fs.readdirSync(ublDir).filter(f => f.endsWith('.sch') || f.endsWith('.xsl'));
|
|
|
|
console.log(' UBL Invoice Schematron:', ublFiles.join(', '));
|
|
|
|
}
|
|
|
|
|
|
|
|
// List CII Schematron files
|
|
|
|
const ciiDir = path.join(schematronValidationDir, 'cii');
|
|
|
|
if (fs.existsSync(ciiDir)) {
|
|
|
|
const ciiFiles = fs.readdirSync(ciiDir).filter(f => f.endsWith('.sch') || f.endsWith('.xsl'));
|
|
|
|
console.log(' CII Schematron:', ciiFiles.join(', '));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy to final location
|
|
|
|
const finalUblDir = path.join(ASSETS_DIR, 'ubl');
|
|
|
|
const finalCiiDir = path.join(ASSETS_DIR, 'cii');
|
|
|
|
|
|
|
|
fs.mkdirSync(finalUblDir, { recursive: true });
|
|
|
|
fs.mkdirSync(finalCiiDir, { recursive: true });
|
|
|
|
|
|
|
|
// Copy UBL files
|
|
|
|
if (fs.existsSync(ublDir)) {
|
|
|
|
const ublFiles = fs.readdirSync(ublDir);
|
|
|
|
for (const file of ublFiles) {
|
|
|
|
if (file.endsWith('.sch') || file.endsWith('.xsl')) {
|
|
|
|
fs.copyFileSync(
|
|
|
|
path.join(ublDir, file),
|
|
|
|
path.join(finalUblDir, file)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
console.log(`\nCopied UBL Schematron files to ${finalUblDir}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy CII files
|
|
|
|
if (fs.existsSync(ciiDir)) {
|
|
|
|
const ciiFiles = fs.readdirSync(ciiDir);
|
|
|
|
for (const file of ciiFiles) {
|
|
|
|
if (file.endsWith('.sch') || file.endsWith('.xsl')) {
|
|
|
|
fs.copyFileSync(
|
|
|
|
path.join(ciiDir, file),
|
|
|
|
path.join(finalCiiDir, file)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
console.log(`Copied CII Schematron files to ${finalCiiDir}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Download validator configuration (contains additional rules and scenarios)
|
|
|
|
console.log('\n2. Downloading XRechnung validator configuration...');
|
|
|
|
const validatorZip = path.join(tempDir, 'xrechnung-validator.zip');
|
|
|
|
await downloadFile(REPOS.validator.url, validatorZip);
|
|
|
|
|
|
|
|
const validatorDir = path.join(ASSETS_DIR, REPOS.validator.dir);
|
|
|
|
await extractZip(validatorZip, validatorDir);
|
|
|
|
|
|
|
|
// Create metadata file
|
|
|
|
const metadata = {
|
|
|
|
version: XRECHNUNG_VERSION,
|
|
|
|
validatorVersion: VALIDATOR_VERSION,
|
|
|
|
downloadDate: new Date().toISOString(),
|
|
|
|
sources: {
|
|
|
|
schematron: REPOS.schematron.url,
|
|
|
|
validator: REPOS.validator.url
|
|
|
|
},
|
|
|
|
files: {
|
|
|
|
ubl: fs.existsSync(path.join(ASSETS_DIR, 'ubl'))
|
|
|
|
? fs.readdirSync(path.join(ASSETS_DIR, 'ubl')).filter(f => f.endsWith('.sch'))
|
|
|
|
: [],
|
|
|
|
cii: fs.existsSync(path.join(ASSETS_DIR, 'cii'))
|
|
|
|
? fs.readdirSync(path.join(ASSETS_DIR, 'cii')).filter(f => f.endsWith('.sch'))
|
|
|
|
: []
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
fs.writeFileSync(
|
|
|
|
path.join(ASSETS_DIR, 'metadata.json'),
|
|
|
|
JSON.stringify(metadata, null, 2)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Clean up temp directory
|
|
|
|
console.log('\n3. Cleaning up...');
|
|
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
|
|
|
|
|
|
console.log('\n✅ XRechnung Schematron rules downloaded successfully!');
|
|
|
|
console.log(`📁 Files are located in: ${ASSETS_DIR}`);
|
|
|
|
console.log('\nNext steps:');
|
|
|
|
console.log('1. Run Saxon-JS to compile .sch files to SEF format');
|
|
|
|
console.log('2. Integrate with SchematronValidator');
|
|
|
|
console.log('3. Add XRechnung-specific TypeScript validators');
|
|
|
|
}
|
|
|
|
|
2025-08-12 05:02:42 +00:00
|
|
|
// Run if executed directly
|
|
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
|
downloadXRechnungRules().catch(error => {
|
|
|
|
console.error('Failed to download XRechnung rules:', error);
|
|
|
|
process.exit(1);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export default downloadXRechnungRules;
|