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.
This commit is contained in:
219
test/test.integrated-validator.ts
Normal file
219
test/test.integrated-validator.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
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();
|
Reference in New Issue
Block a user