- 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.
219 lines
6.4 KiB
TypeScript
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(); |