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();