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