import { expect, tap } from '@git.zone/tstest/tapbundle'; import { EInvoice } from '../../../ts/index.js'; import { CorpusLoader } from '../../helpers/corpus.loader.js'; // CONV-05: Verify mandatory fields are maintained during format conversion // This test ensures no required data is lost during transformation tap.test('CONV-05: EN16931 mandatory fields in UBL', async () => { // UBL invoice with all EN16931 mandatory fields const ublInvoice = ` MANDATORY-UBL-001 2025-01-25 380 EUR Mandatory Fields Supplier AB Mandatory Fields Supplier AB Kungsgatan 10 Stockholm 11143 SE SE123456789001 VAT Mandatory Fields Buyer GmbH Mandatory Fields Buyer GmbH Hauptstraße 123 Berlin 10115 DE 1000.00 1190.00 1190.00 1 1 1000.00 Mandatory Test Product 1000.00 `; try { const einvoice = new EInvoice(); await einvoice.loadXml(ublInvoice); // Define mandatory fields to check const mandatoryFields = [ { field: 'id', value: einvoice.id, bt: 'BT-1' }, { field: 'date', value: einvoice.date, bt: 'BT-2' }, { field: 'currency', value: einvoice.currency, bt: 'BT-5' }, { field: 'from.name', value: einvoice.from?.name, bt: 'BT-27' }, { field: 'from.address.city', value: einvoice.from?.address?.city, bt: 'BT-37' }, { field: 'from.address.countryCode', value: einvoice.from?.address?.countryCode, bt: 'BT-40' }, { field: 'to.name', value: einvoice.to?.name, bt: 'BT-44' }, { field: 'to.address.city', value: einvoice.to?.address?.city, bt: 'BT-52' }, { field: 'to.address.countryCode', value: einvoice.to?.address?.countryCode, bt: 'BT-55' }, { field: 'items', value: einvoice.items?.length > 0, bt: 'BG-25' } ]; // Check each mandatory field const missingFields = mandatoryFields.filter(f => !f.value); if (missingFields.length > 0) { console.error('Missing mandatory fields:', missingFields.map(f => `${f.bt}: ${f.field}`)); } expect(missingFields.length).toEqual(0); // Test conversion to other formats const ciiXml = await einvoice.toXmlString('cii'); expect(ciiXml.length).toBeGreaterThan(100); // Convert back and check mandatory fields are preserved const einvoice2 = new EInvoice(); await einvoice2.loadXml(ciiXml); // Check key mandatory fields survived conversion expect(einvoice2.id).toEqual('MANDATORY-UBL-001'); expect(einvoice2.currency).toEqual('EUR'); expect(einvoice2.from?.name).toBeTruthy(); expect(einvoice2.to?.name).toBeTruthy(); expect(einvoice2.items?.length).toBeGreaterThan(0); } catch (error) { console.error('Mandatory fields test failed:', error); throw error; } }); tap.test('CONV-05: EN16931 mandatory fields in CII', async () => { // CII invoice with all EN16931 mandatory fields const ciiInvoice = ` urn:cen.eu:en16931:2017 MANDATORY-CII-001 380 20250125 1 Mandatory Test Product 1000.00 1 1000.00 Mandatory Fields Supplier AB Kungsgatan 10 Stockholm 11143 SE SE123456789001 Mandatory Fields Buyer GmbH Hauptstraße 123 Berlin 10115 DE EUR 1000.00 1190.00 1190.00 `; try { const einvoice = new EInvoice(); await einvoice.loadXml(ciiInvoice); // Check mandatory fields expect(einvoice.id).toEqual('MANDATORY-CII-001'); expect(einvoice.date).toBeTruthy(); expect(einvoice.currency).toEqual('EUR'); expect(einvoice.from?.name).toEqual('Mandatory Fields Supplier AB'); expect(einvoice.from?.address?.city).toEqual('Stockholm'); expect(einvoice.from?.address?.countryCode).toEqual('SE'); expect(einvoice.to?.name).toEqual('Mandatory Fields Buyer GmbH'); expect(einvoice.to?.address?.city).toEqual('Berlin'); expect(einvoice.to?.address?.countryCode).toEqual('DE'); expect(einvoice.items?.length).toBeGreaterThan(0); expect(einvoice.items?.[0]?.name).toEqual('Mandatory Test Product'); // Test conversion to UBL const ublXml = await einvoice.toXmlString('ubl'); expect(ublXml.length).toBeGreaterThan(100); // Verify UBL contains mandatory fields expect(ublXml).toContain('MANDATORY-CII-001'); expect(ublXml).toContain('EUR'); expect(ublXml).toContain('Mandatory Fields Supplier AB'); expect(ublXml).toContain('Mandatory Fields Buyer GmbH'); } catch (error) { console.error('CII mandatory fields test failed:', error); throw error; } }); tap.test('CONV-05: XRechnung specific mandatory fields', async () => { // XRechnung has additional mandatory fields beyond EN16931 const xrechnungUbl = ` urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0 urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 XRECHNUNG-001 2025-01-25 380 EUR XR-2025-001 1234567890123 1234567890123 XRechnung Supplier GmbH XRechnung Supplier GmbH 1234567890123 Teststraße 1 Berlin 10115 DE DE123456789 VAT Max Mustermann +49 30 123456 max@example.com 991-12345-67 991-12345-67 Bundesamt für XRechnung Bundesamt für XRechnung Amtsstraße 100 Berlin 10117 DE 58 DE89370400440532013000 190.00 1000.00 190.00 S 19 VAT 1000.00 1190.00 1190.00 1 1 1000.00 XRechnung Test Product S 19 VAT 1000.00 `; try { const einvoice = new EInvoice(); await einvoice.loadXml(xrechnungUbl); // Check basic mandatory fields (XRechnung-specific fields might not all be extracted yet) expect(einvoice.id).toEqual('XRECHNUNG-001'); expect(einvoice.currency).toEqual('EUR'); expect(einvoice.from?.name).toBeTruthy(); expect(einvoice.to?.name).toBeTruthy(); // Test conversion to XRechnung format const xrechnungXml = await einvoice.toXmlString('xrechnung'); expect(xrechnungXml.length).toBeGreaterThan(100); // Verify XRechnung XML contains key elements expect(xrechnungXml).toContain('XRECHNUNG-001'); expect(xrechnungXml).toContain('EUR'); // Note: Some XRechnung-specific fields like BuyerReference and Leitweg-ID // might not be fully supported in the current implementation } catch (error) { console.error('XRechnung mandatory fields test failed:', error); throw error; } }); tap.test('CONV-05: Mandatory fields validation errors', async () => { // Test invoice missing mandatory fields const invalidInvoices = [ { name: 'Missing invoice ID', xml: ` 2025-01-25 EUR `, expectedError: 'invoice ID' }, { name: 'Missing currency', xml: ` TEST-001 2025-01-25 `, expectedError: 'currency' } ]; for (const testCase of invalidInvoices) { console.log(`Testing: ${testCase.name}`); try { const einvoice = new EInvoice(); await einvoice.loadXml(testCase.xml); // Check if critical fields are missing if (!einvoice.id && testCase.expectedError.includes('ID')) { console.log('✓ Correctly identified missing invoice ID'); } if (!einvoice.currency && testCase.expectedError.includes('currency')) { console.log('✓ Correctly identified missing currency'); } } catch (error) { // Some formats might throw errors for missing mandatory fields console.log(`✓ Validation error for ${testCase.name}: ${error.message}`); } } }); tap.test('CONV-05: Conditional mandatory fields', async () => { // Test conditional mandatory fields (e.g., VAT details when applicable) const conditionalInvoice = ` CONDITIONAL-001 2025-01-25 380 EUR VAT Registered Supplier VAT Registered Supplier Main Street Brussels 1000 BE BE0123456789 VAT EU Customer EU Customer Rue de la Paix Paris 75001 FR 210.00 1000.00 210.00 S 21 VAT 1000.00 1210.00 1210.00 1 1 1000.00 Taxable Product S 21 VAT 1000.00 `; try { const einvoice = new EInvoice(); await einvoice.loadXml(conditionalInvoice); // Check conditional mandatory fields // When VAT applies, certain fields become mandatory if (einvoice.from?.registrationDetails?.vatId) { console.log('✓ VAT ID present when seller is VAT registered'); } // Check if tax information is properly extracted if (einvoice.items?.[0]?.vatPercentage) { console.log('✓ VAT percentage present for taxable items'); } // Test conversion preserves conditional fields const ciiXml = await einvoice.toXmlString('cii'); expect(ciiXml).toContain('BE0123456789'); // VAT ID } catch (error) { console.error('Conditional mandatory fields test failed:', error); throw error; } }); tap.test('CONV-05: Corpus mandatory fields analysis', async () => { console.log('Analyzing mandatory fields in corpus files...'); // Get a sample of files from different formats const corpusFiles = await CorpusLoader.createTestDataset({ formats: ['UBL', 'CII'], categories: ['UBL_XMLRECHNUNG', 'CII_XMLRECHNUNG'], maxFiles: 10, validOnly: true }); let totalFiles = 0; let filesWithAllMandatory = 0; const missingFieldsCount: Record = {}; for (const file of corpusFiles) { try { const content = await CorpusLoader.loadFile(file.path); const einvoice = new EInvoice(); await einvoice.loadXml(content.toString('utf-8')); totalFiles++; // Check EN16931 mandatory fields const mandatoryChecks = { 'BT-1 Invoice ID': !!einvoice.id, 'BT-2 Issue Date': !!einvoice.date, 'BT-5 Currency': !!einvoice.currency, 'BT-27 Seller Name': !!einvoice.from?.name, 'BT-40 Seller Country': !!einvoice.from?.address?.countryCode, 'BT-44 Buyer Name': !!einvoice.to?.name, 'BT-55 Buyer Country': !!einvoice.to?.address?.countryCode, 'BG-25 Invoice Lines': einvoice.items?.length > 0 }; const missingFields = Object.entries(mandatoryChecks) .filter(([_, present]) => !present) .map(([field, _]) => field); if (missingFields.length === 0) { filesWithAllMandatory++; } else { missingFields.forEach(field => { missingFieldsCount[field] = (missingFieldsCount[field] || 0) + 1; }); } } catch (error) { console.error(`Failed to process ${file.path}:`, error.message); } } console.log(`\nMandatory fields analysis:`); console.log(`- Total files analyzed: ${totalFiles}`); console.log(`- Files with all mandatory fields: ${filesWithAllMandatory}`); console.log(`- Compliance rate: ${((filesWithAllMandatory / totalFiles) * 100).toFixed(1)}%`); if (Object.keys(missingFieldsCount).length > 0) { console.log(`\nMost commonly missing fields:`); Object.entries(missingFieldsCount) .sort(([, a], [, b]) => b - a) .slice(0, 5) .forEach(([field, count]) => { console.log(` - ${field}: missing in ${count} files`); }); } // At least 50% of valid corpus files should have all mandatory fields // Note: Some corpus files may use different structures that aren't fully supported yet const complianceRate = (filesWithAllMandatory / totalFiles) * 100; expect(complianceRate).toBeGreaterThan(50); }); tap.start();