Files
einvoice/test/test.en16931-validators.ts

238 lines
6.6 KiB
TypeScript
Raw Normal View History

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { EInvoice } from '../ts/index.js';
import { ValidationLevel } from '../ts/interfaces/common.js';
// Test EN16931 business rules and code list validators
tap.test('EN16931 Validators - should validate business rules with feature flags', async () => {
// Create a minimal invoice that violates several EN16931 rules
const invoice = new EInvoice();
// Set some basic fields but leave mandatory ones missing
invoice.currency = 'EUR';
invoice.date = Date.now();
invoice.from = {
type: 'company',
name: 'Test Seller',
address: {
streetName: 'Test Street',
houseNumber: '1',
city: 'Berlin',
postalCode: '10115',
countryCode: 'DE'
}
} as any;
// Missing buyer details and invoice ID (violates BR-02, BR-07)
// Add an item with calculation issues
invoice.items = [{
position: 1,
name: 'Test Item',
unitType: 'C62', // Valid UNECE code
unitQuantity: 10,
unitNetPrice: 100,
vatPercentage: 19
}];
// Test without feature flags (should pass basic validation)
const basicResult = await invoice.validate(ValidationLevel.BUSINESS);
console.log('Basic validation errors:', basicResult.errors.length);
// Test with EN16931 business rules feature flag
const en16931Result = await invoice.validate(ValidationLevel.BUSINESS, {
featureFlags: ['EN16931_BUSINESS_RULES'],
checkCalculations: true,
checkVAT: true
});
console.log('EN16931 validation errors:', en16931Result.errors.length);
// Should find missing mandatory fields
const mandatoryErrors = en16931Result.errors.filter(e =>
e.code && ['BR-01', 'BR-02', 'BR-07'].includes(e.code)
);
expect(mandatoryErrors.length).toBeGreaterThan(0);
// Test code list validation
const codeListResult = await invoice.validate(ValidationLevel.BUSINESS, {
featureFlags: ['CODE_LIST_VALIDATION'],
checkCodeLists: true
});
console.log('Code list validation errors:', codeListResult.errors.length);
// Test invalid currency code
invoice.currency = 'XXX' as any; // Invalid currency
const currencyResult = await invoice.validate(ValidationLevel.BUSINESS, {
featureFlags: ['CODE_LIST_VALIDATION']
});
const currencyErrors = currencyResult.errors.filter(e =>
e.code && e.code.includes('BR-CL-03')
);
expect(currencyErrors.length).toEqual(1);
// Test with both validators enabled
const fullResult = await invoice.validate(ValidationLevel.BUSINESS, {
featureFlags: ['EN16931_BUSINESS_RULES', 'CODE_LIST_VALIDATION'],
checkCalculations: true,
checkVAT: true,
checkCodeLists: true,
reportOnly: true // Don't fail validation, just report
});
console.log('Full validation with both validators:');
console.log('- Total errors:', fullResult.errors.length);
console.log('- Valid (report-only mode):', fullResult.valid);
expect(fullResult.valid).toEqual(true); // Should be true in report-only mode
expect(fullResult.errors.length).toBeGreaterThan(0); // Should find issues
console.log('Error codes found:', fullResult.errors.map(e => e.code));
});
tap.test('EN16931 Validators - should validate calculations correctly', async () => {
const invoice = new EInvoice();
// Set up a complete invoice with correct mandatory fields
invoice.accountingDocId = 'INV-2024-001';
invoice.currency = 'EUR';
invoice.date = Date.now();
invoice.metadata = {
customizationId: 'urn:cen.eu:en16931:2017'
};
invoice.from = {
type: 'company',
name: 'Test Seller GmbH',
address: {
streetName: 'Hauptstraße',
houseNumber: '1',
city: 'Berlin',
postalCode: '10115',
countryCode: 'DE'
}
} as any;
invoice.to = {
type: 'company',
name: 'Test Buyer Ltd',
address: {
streetName: 'Main Street',
houseNumber: '10',
city: 'London',
postalCode: 'SW1A 1AA',
countryCode: 'GB'
}
} as any;
// Add items with specific amounts
invoice.items = [
{
position: 1,
name: 'Product A',
unitType: 'C62',
unitQuantity: 5,
unitNetPrice: 100.00,
vatPercentage: 19
},
{
position: 2,
name: 'Product B',
unitType: 'C62',
unitQuantity: 3,
unitNetPrice: 50.00,
vatPercentage: 19
}
];
// Expected calculations:
// Line 1: 5 * 100 = 500
// Line 2: 3 * 50 = 150
// Total net: 650
// VAT (19%): 123.50
// Total gross: 773.50
const result = await invoice.validate(ValidationLevel.BUSINESS, {
featureFlags: ['EN16931_BUSINESS_RULES'],
checkCalculations: true,
tolerance: 0.01
});
// Should not have calculation errors
const calcErrors = result.errors.filter(e =>
e.code && e.code.startsWith('BR-CO-')
);
console.log('Calculation validation errors:', calcErrors);
expect(calcErrors.length).toEqual(0);
// Verify computed totals
expect(invoice.totalNet).toEqual(650);
expect(invoice.totalVat).toEqual(123.50);
expect(invoice.totalGross).toEqual(773.50);
});
tap.test('EN16931 Validators - should validate VAT rules correctly', async () => {
const invoice = new EInvoice();
// Set up mandatory fields
invoice.accountingDocId = 'INV-2024-002';
invoice.currency = 'EUR';
invoice.date = Date.now();
invoice.metadata = {
customizationId: 'urn:cen.eu:en16931:2017'
};
invoice.from = {
type: 'company',
name: 'Seller',
address: { countryCode: 'DE' }
} as any;
invoice.to = {
type: 'company',
name: 'Buyer',
address: { countryCode: 'FR' }
} as any;
// Add mixed VAT rate items
invoice.items = [
{
position: 1,
name: 'Standard rated item',
unitType: 'C62',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19 // Standard rate
},
{
position: 2,
name: 'Zero rated item',
unitType: 'C62',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 0 // Zero rate
}
];
const result = await invoice.validate(ValidationLevel.BUSINESS, {
featureFlags: ['EN16931_BUSINESS_RULES'],
checkVAT: true
});
// Check for VAT breakdown requirements
const vatErrors = result.errors.filter(e =>
e.code && (e.code.startsWith('BR-S-') || e.code.startsWith('BR-Z-'))
);
console.log('VAT validation results:');
console.log('- VAT errors found:', vatErrors.length);
console.log('- Tax breakdown:', invoice.taxBreakdown);
// Should have proper tax breakdown
expect(invoice.taxBreakdown.length).toEqual(2);
expect(invoice.taxBreakdown.find(t => t.taxPercent === 19)).toBeTruthy();
expect(invoice.taxBreakdown.find(t => t.taxPercent === 0)).toBeTruthy();
});
export default tap.start();