Files
einvoice/test/test.integrated-validator.ts
Juergen Kunz cbb297b0b1 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

219 lines
6.4 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { MainValidator, createValidator } from '../ts/formats/validation/integrated.validator.js';
import { EInvoice } from '../ts/einvoice.js';
import * as fs from 'fs';
import * as path from 'path';
tap.test('Integrated Validator - Basic validation', async () => {
const validator = new MainValidator();
const invoice = new EInvoice();
invoice.invoiceNumber = 'TEST-001';
invoice.issueDate = new Date('2025-01-11');
invoice.from = {
type: 'company',
name: 'Test Seller',
address: {
streetName: 'Test Street',
city: 'Berlin',
postalCode: '10115',
countryCode: 'DE'
}
};
invoice.to = {
name: 'Test Buyer',
address: {
streetName: 'Buyer Street',
city: 'Munich',
postalCode: '80331',
countryCode: 'DE'
}
};
const report = await validator.validate(invoice);
console.log('Basic validation report:');
console.log(` Valid: ${report.valid}`);
console.log(` Errors: ${report.errorCount}`);
console.log(` Warnings: ${report.warningCount}`);
console.log(` Coverage: ${report.coverage.toFixed(1)}%`);
expect(report).toBeDefined();
expect(report.errorCount).toBeGreaterThan(0); // Should have errors (missing required fields)
});
tap.test('Integrated Validator - XRechnung detection', async () => {
const validator = new MainValidator();
const invoice = new EInvoice();
invoice.metadata = {
profileId: 'urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0',
buyerReference: '991-12345678901-23' // Leitweg-ID
};
invoice.invoiceNumber = 'XR-2025-001';
invoice.issueDate = new Date('2025-01-11');
const report = await validator.validate(invoice);
console.log('XRechnung validation report:');
console.log(` Profile: ${report.profile}`);
console.log(` XRechnung errors found: ${
report.results.filter(r => r.source === 'XRECHNUNG').length
}`);
expect(report.profile).toInclude('XRECHNUNG');
// Check for XRechnung-specific validation
const xrErrors = report.results.filter(r => r.source === 'XRECHNUNG');
expect(xrErrors.length).toBeGreaterThan(0);
});
tap.test('Integrated Validator - Complete valid invoice', async () => {
const validator = await createValidator({ enableSchematron: false });
const invoice = new EInvoice();
invoice.accountingDocId = 'INV-2025-001';
invoice.accountingDocType = '380';
invoice.invoiceNumber = 'INV-2025-001';
invoice.issueDate = new Date('2025-01-11');
invoice.currencyCode = 'EUR';
invoice.from = {
type: 'company',
name: 'Example GmbH',
address: {
streetName: 'Hauptstraße 1',
city: 'Berlin',
postalCode: '10115',
countryCode: 'DE'
},
registrationDetails: {
vatId: 'DE123456789'
}
};
invoice.to = {
name: 'Customer AG',
address: {
streetName: 'Kundenweg 42',
city: 'Munich',
postalCode: '80331',
countryCode: 'DE'
}
};
invoice.items = [{
title: 'Consulting Services',
description: 'Professional consulting',
quantity: 10,
unitPrice: 100,
netAmount: 1000,
vatRate: 19,
vatAmount: 190,
grossAmount: 1190
}];
invoice.metadata = {
customizationId: 'urn:cen.eu:en16931:2017',
profileId: 'urn:cen.eu:en16931:2017',
taxDetails: [{
taxPercent: 19,
netAmount: 1000,
taxAmount: 190
}],
totals: {
lineExtensionAmount: 1000,
taxExclusiveAmount: 1000,
taxInclusiveAmount: 1190,
payableAmount: 1190
}
};
const report = await validator.validate(invoice);
console.log('\nComplete invoice validation:');
console.log(validator.formatReport(report));
// Should have fewer errors with more complete data
expect(report.errorCount).toBeLessThan(10);
});
tap.test('Integrated Validator - With XML content', async () => {
const validator = await createValidator();
// Load a sample XML file if available
const xmlPath = path.join(
process.cwd(),
'corpus/xml-rechnung/3.1/ubl/01-01a-INVOICE_ubl.xml'
);
if (fs.existsSync(xmlPath)) {
const xmlContent = fs.readFileSync(xmlPath, 'utf-8');
const invoice = await EInvoice.fromXML(xmlContent);
const report = await validator.validateAuto(invoice, xmlContent);
console.log('\nXML validation with Schematron:');
console.log(` Format detected: ${report.format}`);
console.log(` Schematron enabled: ${report.schematronEnabled}`);
console.log(` Validation sources: ${
[...new Set(report.results.map(r => r.source))].join(', ')
}`);
expect(report.format).toBeDefined();
} else {
console.log('Sample XML not found, skipping XML validation test');
}
});
tap.test('Integrated Validator - Capabilities check', async () => {
const validator = new MainValidator();
const capabilities = validator.getCapabilities();
console.log('\nValidator capabilities:');
console.log(` Schematron: ${capabilities.schematron ? '✅' : '❌'}`);
console.log(` XRechnung: ${capabilities.xrechnung ? '✅' : '❌'}`);
console.log(` PEPPOL: ${capabilities.peppol ? '✅' : '❌'}`);
console.log(` Calculations: ${capabilities.calculations ? '✅' : '❌'}`);
console.log(` Code Lists: ${capabilities.codeLists ? '✅' : '❌'}`);
expect(capabilities.xrechnung).toBeTrue();
expect(capabilities.calculations).toBeTrue();
expect(capabilities.codeLists).toBeTrue();
});
tap.test('Integrated Validator - Deduplication', async () => {
const validator = new MainValidator();
// Create invoice that will trigger duplicate errors
const invoice = new EInvoice();
invoice.invoiceNumber = 'TEST-DUP';
const report = await validator.validate(invoice);
// Check that duplicates are removed
const ruleIds = report.results.map(r => r.ruleId);
const uniqueRuleIds = [...new Set(ruleIds)];
console.log(`\nDeduplication test:`);
console.log(` Total results: ${report.results.length}`);
console.log(` Unique rule IDs: ${uniqueRuleIds.length}`);
// Each rule+field combination should appear only once
const combinations = new Set();
let duplicates = 0;
for (const result of report.results) {
const key = `${result.ruleId}|${result.field || ''}`;
if (combinations.has(key)) {
duplicates++;
}
combinations.add(key);
}
console.log(` Duplicate combinations: ${duplicates}`);
expect(duplicates).toEqual(0);
});
export default tap.start();