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