import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as plugins from '../plugins.js'; import { EInvoice } from '../../../ts/index.js'; import { CorpusLoader } from '../corpus.loader.js'; import { PerformanceTracker } from '../performance.tracker.js'; tap.test('CONV-05: Mandatory Fields - should ensure all mandatory fields are preserved', async (t) => { // CONV-05: Verify mandatory fields are maintained during format conversion // This test ensures no required data is lost during transformation const performanceTracker = new PerformanceTracker('CONV-05: Mandatory Fields'); const corpusLoader = new CorpusLoader(); t.test('EN16931 mandatory fields in UBL', async () => { const startTime = performance.now(); // UBL invoice with all EN16931 mandatory fields const ublInvoice = ` MANDATORY-UBL-001 2025-01-25 380 EUR Mandatory Fields Supplier AB Kungsgatan 10 Stockholm 11143 SE SE123456789001 VAT Mandatory Fields Customer AS Karl Johans gate 1 Oslo 0154 NO 1000.00 1000.00 1190.00 1190.00 190.00 1000.00 190.00 S 19 VAT 1 10 1000.00 Mandatory Test Product S 19 VAT 100.00 `; const einvoice = new EInvoice(); await einvoice.loadFromString(ublInvoice); const xmlString = einvoice.getXmlString(); const invoiceData = einvoice.getInvoiceData(); // Verify mandatory fields are present const mandatoryChecks = { 'Invoice number': xmlString.includes('MANDATORY-UBL-001'), 'Issue date': xmlString.includes('2025-01-25'), 'Invoice type': xmlString.includes('380'), 'Currency': xmlString.includes('EUR'), 'Seller name': xmlString.includes('Mandatory Fields Supplier'), 'Seller country': xmlString.includes('SE'), 'Buyer name': xmlString.includes('Mandatory Fields Customer'), 'Buyer country': xmlString.includes('NO'), 'Payable amount': xmlString.includes('1190.00'), 'VAT amount': xmlString.includes('190.00'), 'Line ID': xmlString.includes('1') || xmlString.includes('1'), 'Item name': xmlString.includes('Mandatory Test Product') }; const missingFields = Object.entries(mandatoryChecks) .filter(([field, present]) => !present) .map(([field]) => field); if (missingFields.length > 0) { console.log('Missing mandatory fields:', missingFields); } else { console.log('All EN16931 mandatory fields preserved'); } expect(missingFields.length).toBe(0); const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('en16931-mandatory', elapsed); }); t.test('EN16931 mandatory fields in CII', async () => { const startTime = performance.now(); // CII invoice with all mandatory fields const ciiInvoice = ` urn:cen.eu:en16931:2017 MANDATORY-CII-001 380 20250125 1 CII Mandatory Product 100.00 10 VAT S 19 1000.00 CII Mandatory Seller Musterstraße 1 Berlin 10115 DE DE123456789 CII Mandatory Buyer Schulstraße 10 Hamburg 20095 DE EUR 190.00 VAT S 1000.00 19 1000.00 1000.00 190.00 1190.00 1190.00 `; const einvoice = new EInvoice(); await einvoice.loadFromString(ciiInvoice); const xmlString = einvoice.getXmlString(); // Verify CII mandatory fields const ciiMandatoryChecks = { 'Invoice ID': xmlString.includes('MANDATORY-CII-001'), 'Type code': xmlString.includes('380'), 'Issue date': xmlString.includes('20250125'), 'Currency': xmlString.includes('EUR'), 'Seller name': xmlString.includes('CII Mandatory Seller'), 'Seller country': xmlString.includes('DE'), 'Buyer name': xmlString.includes('CII Mandatory Buyer'), 'Line ID': xmlString.includes('1'), 'Product name': xmlString.includes('CII Mandatory Product'), 'Due amount': xmlString.includes('1190.00') }; const missingCiiFields = Object.entries(ciiMandatoryChecks) .filter(([field, present]) => !present) .map(([field]) => field); if (missingCiiFields.length > 0) { console.log('Missing CII mandatory fields:', missingCiiFields); } expect(missingCiiFields.length).toBe(0); const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('cii-mandatory', elapsed); }); t.test('XRechnung specific mandatory fields', async () => { const startTime = performance.now(); // XRechnung has additional mandatory fields const xrechnungInvoice = ` 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 LEITWEG-ID-123456 seller@example.de XRechnung Seller GmbH Berliner Straße 1 Berlin 10115 DE Max Mustermann +49 30 12345678 max@seller.de buyer@behoerde.de Bundesbehörde XY Amtsstraße 100 Bonn 53113 DE 30 DE89370400440532013000 119.00 `; const einvoice = new EInvoice(); await einvoice.loadFromString(xrechnungInvoice); const xmlString = einvoice.getXmlString(); // Check XRechnung specific mandatory fields const xrechnungChecks = { 'Customization ID': xmlString.includes('xrechnung'), 'Buyer reference': xmlString.includes('LEITWEG-ID-123456'), 'Seller email': xmlString.includes('seller@example.de') || xmlString.includes('max@seller.de'), 'Buyer endpoint': xmlString.includes('buyer@behoerde.de'), 'Payment means': xmlString.includes('>30<') }; const missingXrechnung = Object.entries(xrechnungChecks) .filter(([field, present]) => !present) .map(([field]) => field); if (missingXrechnung.length > 0) { console.log('Missing XRechnung fields:', missingXrechnung); } const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('xrechnung-mandatory', elapsed); }); t.test('Mandatory fields validation errors', async () => { const startTime = performance.now(); // Invoice missing mandatory fields const incompleteInvoice = ` 2025-01-25 380 Test Street `; const einvoice = new EInvoice(); try { await einvoice.loadFromString(incompleteInvoice); const validationResult = await einvoice.validate(); if (!validationResult.isValid) { console.log('Validation detected missing mandatory fields'); // Check for specific mandatory field errors const mandatoryErrors = validationResult.errors?.filter(err => err.message.toLowerCase().includes('mandatory') || err.message.toLowerCase().includes('required') || err.message.toLowerCase().includes('must') ); if (mandatoryErrors && mandatoryErrors.length > 0) { console.log(`Found ${mandatoryErrors.length} mandatory field errors`); } } } catch (error) { console.log('Processing incomplete invoice:', error.message); } const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('validation-errors', elapsed); }); t.test('Conditional mandatory fields', async () => { const startTime = performance.now(); // Some fields are mandatory only in certain conditions const conditionalInvoice = ` CONDITIONAL-001 2025-01-25 380 EUR VAT Exempt Supplier Paris FR Tax Exempt Customer Brussels BE 0.00 1000.00 0.00 E 0 VATEX-EU-IC Intra-community supply VAT ORIGINAL-INV-001 2025-01-01 1000.00 `; const einvoice = new EInvoice(); await einvoice.loadFromString(conditionalInvoice); const xmlString = einvoice.getXmlString(); // Check conditional mandatory fields const conditionalChecks = { 'VAT exemption reason code': xmlString.includes('VATEX-EU-IC'), 'VAT exemption reason': xmlString.includes('Intra-community supply'), 'Referenced invoice': xmlString.includes('ORIGINAL-INV-001') }; Object.entries(conditionalChecks).forEach(([field, present]) => { if (present) { console.log(`✓ Conditional mandatory field preserved: ${field}`); } }); const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('conditional-mandatory', elapsed); }); t.test('Corpus mandatory fields analysis', async () => { const startTime = performance.now(); let processedCount = 0; const missingFieldStats: Record = {}; const files = await corpusLoader.getAllFiles(); const xmlFiles = files.filter(f => f.endsWith('.xml') && !f.includes('.pdf')); // Sample corpus files for mandatory field analysis const sampleSize = Math.min(40, xmlFiles.length); const sample = xmlFiles.slice(0, sampleSize); for (const file of sample) { try { const content = await corpusLoader.readFile(file); const einvoice = new EInvoice(); if (typeof content === 'string') { await einvoice.loadFromString(content); } else { await einvoice.loadFromBuffer(content); } const xmlString = einvoice.getXmlString(); // Check for mandatory fields const mandatoryFields = [ { name: 'Invoice ID', patterns: ['', ''] }, { name: 'Issue Date', patterns: ['', ''] }, { name: 'Currency', patterns: ['', ''] }, { name: 'Seller Name', patterns: ['', ''] }, { name: 'Buyer Name', patterns: ['AccountingCustomerParty', 'BuyerTradeParty'] }, { name: 'Total Amount', patterns: ['', ''] } ]; mandatoryFields.forEach(field => { const hasField = field.patterns.some(pattern => xmlString.includes(pattern)); if (!hasField) { missingFieldStats[field.name] = (missingFieldStats[field.name] || 0) + 1; } }); processedCount++; } catch (error) { console.log(`Error checking ${file}:`, error.message); } } console.log(`Corpus mandatory fields analysis (${processedCount} files):`); if (Object.keys(missingFieldStats).length > 0) { console.log('Files missing mandatory fields:'); Object.entries(missingFieldStats) .sort((a, b) => b[1] - a[1]) .forEach(([field, count]) => { console.log(` ${field}: ${count} files`); }); } else { console.log('All sampled files have mandatory fields'); } const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('corpus-analysis', elapsed); }); // Print performance summary performanceTracker.printSummary(); // Performance assertions const avgTime = performanceTracker.getAverageTime(); expect(avgTime).toBeLessThan(300); // Mandatory field checks should be fast }); tap.start();