463 lines
15 KiB
TypeScript
463 lines
15 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
|
|
tap.test('STD-01: EN16931 Core Compliance - should validate EN16931 core standard compliance', async () => {
|
|
console.log('Testing EN16931 core standard compliance...\n');
|
|
|
|
// Test 1: Mandatory fields compliance
|
|
const testMandatoryFields = async () => {
|
|
console.log('Test 1 - Mandatory fields compliance:');
|
|
|
|
// Test with missing invoice ID (BR-01 violation)
|
|
let errorCaught = false;
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
// Missing invoiceId - should fail BR-01
|
|
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: 'Test Company',
|
|
description: 'EN16931 compliance test',
|
|
address: {
|
|
streetName: 'Test Street',
|
|
houseNumber: '1',
|
|
postalCode: '12345',
|
|
city: 'Test City',
|
|
country: 'DE'
|
|
},
|
|
status: 'active',
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
registrationDetails: {
|
|
vatId: 'DE123456789',
|
|
registrationId: 'HRB 12345',
|
|
registrationName: 'Commercial Register'
|
|
}
|
|
};
|
|
|
|
einvoice.to = {
|
|
type: 'person',
|
|
name: 'Test',
|
|
surname: 'Customer',
|
|
salutation: 'Mr' as const,
|
|
sex: 'male' as const,
|
|
title: 'Doctor' as const,
|
|
description: 'Test customer',
|
|
address: {
|
|
streetName: 'Customer Street',
|
|
houseNumber: '2',
|
|
postalCode: '54321',
|
|
city: 'Customer City',
|
|
country: 'DE'
|
|
}
|
|
};
|
|
|
|
einvoice.items = [{
|
|
position: 1,
|
|
name: 'Test Item',
|
|
articleNumber: 'TEST-001',
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
await einvoice.toXmlString('ubl');
|
|
} catch (error) {
|
|
errorCaught = true;
|
|
const errorMessage = error.message;
|
|
console.log(` BR-01 (Invoice number mandatory): ${errorMessage.includes('BR-01') ? 'Enforced' : 'Detected'}`);
|
|
}
|
|
|
|
// Test complete valid invoice
|
|
let validInvoiceProcessed = false;
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
einvoice.invoiceId = 'EN16931-001';
|
|
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: 'EN16931 Compliant Company',
|
|
description: 'EN16931 compliance test',
|
|
address: {
|
|
streetName: 'Compliant Street',
|
|
houseNumber: '1',
|
|
postalCode: '12345',
|
|
city: 'Compliant City',
|
|
country: 'DE'
|
|
},
|
|
status: 'active',
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
registrationDetails: {
|
|
vatId: 'DE123456789',
|
|
registrationId: 'HRB 12345',
|
|
registrationName: 'Commercial Register'
|
|
}
|
|
};
|
|
|
|
einvoice.to = {
|
|
type: 'person',
|
|
name: 'Compliant',
|
|
surname: 'Customer',
|
|
salutation: 'Mr' as const,
|
|
sex: 'male' as const,
|
|
title: 'Doctor' as const,
|
|
description: 'Compliant customer',
|
|
address: {
|
|
streetName: 'Customer Street',
|
|
houseNumber: '2',
|
|
postalCode: '54321',
|
|
city: 'Customer City',
|
|
country: 'DE'
|
|
}
|
|
};
|
|
|
|
einvoice.items = [{
|
|
position: 1,
|
|
name: 'Compliant Item',
|
|
articleNumber: 'EN16931-001',
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
const xml = await einvoice.toXmlString('ubl');
|
|
validInvoiceProcessed = xml.includes('EN16931-001');
|
|
console.log(` Complete valid invoice: ${validInvoiceProcessed ? 'Processed' : 'Failed'}`);
|
|
} catch (error) {
|
|
console.log(` Complete valid invoice: Failed - ${error.message}`);
|
|
}
|
|
|
|
return { errorCaught, validInvoiceProcessed };
|
|
};
|
|
|
|
// Test 2: Business rules compliance
|
|
const testBusinessRulesCompliance = async () => {
|
|
console.log('\nTest 2 - Business rules compliance:');
|
|
|
|
const businessRuleTests = [
|
|
{
|
|
name: 'BR-06 (Seller name mandatory)',
|
|
test: async () => {
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
einvoice.invoiceId = 'BR-06-TEST';
|
|
|
|
// Missing seller name
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: '', // Empty name - should fail BR-06
|
|
description: 'BR-06 test',
|
|
address: {
|
|
streetName: 'Test Street',
|
|
houseNumber: '1',
|
|
postalCode: '12345',
|
|
city: 'Test City',
|
|
country: 'DE'
|
|
},
|
|
status: 'active',
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
registrationDetails: {
|
|
vatId: 'DE123456789',
|
|
registrationId: 'HRB 12345',
|
|
registrationName: 'Commercial Register'
|
|
}
|
|
};
|
|
|
|
einvoice.to = {
|
|
type: 'person',
|
|
name: 'Test',
|
|
surname: 'Customer',
|
|
salutation: 'Mr' as const,
|
|
sex: 'male' as const,
|
|
title: 'Doctor' as const,
|
|
description: 'Test customer',
|
|
address: {
|
|
streetName: 'Customer Street',
|
|
houseNumber: '2',
|
|
postalCode: '54321',
|
|
city: 'Customer City',
|
|
country: 'DE'
|
|
}
|
|
};
|
|
|
|
einvoice.items = [{
|
|
position: 1,
|
|
name: 'Test Item',
|
|
articleNumber: 'TEST-001',
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
await einvoice.toXmlString('ubl');
|
|
return false; // Should not reach here
|
|
} catch (error) {
|
|
return error.message.includes('BR-06') || error.message.includes('name');
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'BR-07 (Buyer name mandatory)',
|
|
test: async () => {
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
einvoice.invoiceId = 'BR-07-TEST';
|
|
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: 'Test Company',
|
|
description: 'BR-07 test',
|
|
address: {
|
|
streetName: 'Test Street',
|
|
houseNumber: '1',
|
|
postalCode: '12345',
|
|
city: 'Test City',
|
|
country: 'DE'
|
|
},
|
|
status: 'active',
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
registrationDetails: {
|
|
vatId: 'DE123456789',
|
|
registrationId: 'HRB 12345',
|
|
registrationName: 'Commercial Register'
|
|
}
|
|
};
|
|
|
|
// Missing buyer name
|
|
einvoice.to = {
|
|
type: 'person',
|
|
name: '', // Empty name - should fail BR-07
|
|
surname: 'Customer',
|
|
salutation: 'Mr' as const,
|
|
sex: 'male' as const,
|
|
title: 'Doctor' as const,
|
|
description: 'Test customer',
|
|
address: {
|
|
streetName: 'Customer Street',
|
|
houseNumber: '2',
|
|
postalCode: '54321',
|
|
city: 'Customer City',
|
|
country: 'DE'
|
|
}
|
|
};
|
|
|
|
einvoice.items = [{
|
|
position: 1,
|
|
name: 'Test Item',
|
|
articleNumber: 'TEST-001',
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
await einvoice.toXmlString('ubl');
|
|
return false; // Should not reach here
|
|
} catch (error) {
|
|
return error.message.includes('BR-07') || error.message.includes('name');
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
let rulesEnforced = 0;
|
|
|
|
for (const rule of businessRuleTests) {
|
|
try {
|
|
const enforced = await rule.test();
|
|
console.log(` ${rule.name}: ${enforced ? 'Enforced' : 'Not enforced'}`);
|
|
if (enforced) rulesEnforced++;
|
|
} catch (error) {
|
|
console.log(` ${rule.name}: Error - ${error.message}`);
|
|
}
|
|
}
|
|
|
|
return { rulesEnforced, totalRules: businessRuleTests.length };
|
|
};
|
|
|
|
// Test 3: Format-specific compliance
|
|
const testFormatCompliance = async () => {
|
|
console.log('\nTest 3 - Format-specific compliance:');
|
|
|
|
const formats = ['ubl', 'cii', 'xrechnung'];
|
|
let formatsWorking = 0;
|
|
|
|
for (const format of formats) {
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
einvoice.invoiceId = `${format.toUpperCase()}-COMPLIANCE-001`;
|
|
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: `${format.toUpperCase()} Test Company`,
|
|
description: `Testing ${format} format compliance`,
|
|
address: {
|
|
streetName: 'Compliance Street',
|
|
houseNumber: '1',
|
|
postalCode: '12345',
|
|
city: 'Compliance City',
|
|
country: 'DE'
|
|
},
|
|
status: 'active',
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
registrationDetails: {
|
|
vatId: 'DE123456789',
|
|
registrationId: 'HRB 12345',
|
|
registrationName: 'Commercial Register'
|
|
}
|
|
};
|
|
|
|
einvoice.to = {
|
|
type: 'person',
|
|
name: 'Compliance',
|
|
surname: 'Customer',
|
|
salutation: 'Mr' as const,
|
|
sex: 'male' as const,
|
|
title: 'Doctor' as const,
|
|
description: 'Compliance customer',
|
|
address: {
|
|
streetName: 'Customer Street',
|
|
houseNumber: '2',
|
|
postalCode: '54321',
|
|
city: 'Customer City',
|
|
country: 'DE'
|
|
}
|
|
};
|
|
|
|
einvoice.items = [{
|
|
position: 1,
|
|
name: `${format.toUpperCase()} Compliance Item`,
|
|
articleNumber: `${format.toUpperCase()}-001`,
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
const xml = await einvoice.toXmlString(format as any);
|
|
const success = xml.includes(`${format.toUpperCase()}-COMPLIANCE-001`);
|
|
console.log(` ${format.toUpperCase()} format: ${success ? 'Compliant' : 'Failed'}`);
|
|
if (success) formatsWorking++;
|
|
} catch (error) {
|
|
console.log(` ${format.toUpperCase()} format: Failed - ${error.message}`);
|
|
}
|
|
}
|
|
|
|
return { formatsWorking, totalFormats: formats.length };
|
|
};
|
|
|
|
// Test 4: EN16931 mandatory elements presence
|
|
const testMandatoryElementsPresence = async () => {
|
|
console.log('\nTest 4 - Mandatory elements presence:');
|
|
|
|
const einvoice = new EInvoice();
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
einvoice.invoiceId = 'MANDATORY-ELEMENTS-001';
|
|
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: 'Mandatory Elements Company',
|
|
description: 'Testing mandatory elements',
|
|
address: {
|
|
streetName: 'Mandatory Street',
|
|
houseNumber: '1',
|
|
postalCode: '12345',
|
|
city: 'Mandatory City',
|
|
country: 'DE'
|
|
},
|
|
status: 'active',
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
registrationDetails: {
|
|
vatId: 'DE123456789',
|
|
registrationId: 'HRB 12345',
|
|
registrationName: 'Commercial Register'
|
|
}
|
|
};
|
|
|
|
einvoice.to = {
|
|
type: 'person',
|
|
name: 'Mandatory',
|
|
surname: 'Customer',
|
|
salutation: 'Mr' as const,
|
|
sex: 'male' as const,
|
|
title: 'Doctor' as const,
|
|
description: 'Mandatory customer',
|
|
address: {
|
|
streetName: 'Customer Street',
|
|
houseNumber: '2',
|
|
postalCode: '54321',
|
|
city: 'Customer City',
|
|
country: 'DE'
|
|
}
|
|
};
|
|
|
|
einvoice.items = [{
|
|
position: 1,
|
|
name: 'Mandatory Item',
|
|
articleNumber: 'MANDATORY-001',
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
try {
|
|
const xml = await einvoice.toXmlString('ubl');
|
|
|
|
// Check for EN16931 mandatory elements
|
|
const mandatoryElements = [
|
|
'cbc:ID', // BT-1 Invoice number
|
|
'cbc:IssueDate', // BT-2 Invoice issue date
|
|
'cac:AccountingSupplierParty', // BG-4 Seller
|
|
'cac:AccountingCustomerParty', // BG-7 Buyer
|
|
'cac:InvoiceLine' // BG-25 Invoice line
|
|
];
|
|
|
|
let elementsPresent = 0;
|
|
for (const element of mandatoryElements) {
|
|
if (xml.includes(element)) {
|
|
elementsPresent++;
|
|
console.log(` ${element}: Present`);
|
|
} else {
|
|
console.log(` ${element}: Missing`);
|
|
}
|
|
}
|
|
|
|
return { elementsPresent, totalElements: mandatoryElements.length };
|
|
} catch (error) {
|
|
console.log(` Mandatory elements test failed: ${error.message}`);
|
|
return { elementsPresent: 0, totalElements: 5 };
|
|
}
|
|
};
|
|
|
|
// Run all tests
|
|
const result1 = await testMandatoryFields();
|
|
const result2 = await testBusinessRulesCompliance();
|
|
const result3 = await testFormatCompliance();
|
|
const result4 = await testMandatoryElementsPresence();
|
|
|
|
console.log('\n=== EN16931 Core Compliance Summary ===');
|
|
console.log(`Mandatory field validation: ${result1.errorCaught ? 'Working' : 'Not implemented'}`);
|
|
console.log(`Valid invoice processing: ${result1.validInvoiceProcessed ? 'Working' : 'Failed'}`);
|
|
console.log(`Business rules enforced: ${result2.rulesEnforced}/${result2.totalRules}`);
|
|
console.log(`Format compliance: ${result3.formatsWorking}/${result3.totalFormats}`);
|
|
console.log(`Mandatory elements: ${result4.elementsPresent}/${result4.totalElements}`);
|
|
|
|
// Test passes if core EN16931 functionality works
|
|
const coreValidationWorks = result1.errorCaught && result1.validInvoiceProcessed; // BR validation + valid processing
|
|
const businessRulesWork = result2.rulesEnforced === result2.totalRules; // All business rules enforced
|
|
const mandatoryElementsWork = result4.elementsPresent === result4.totalElements; // All mandatory elements present
|
|
|
|
expect(coreValidationWorks).toBeTrue(); // Core EN16931 validation must work
|
|
expect(businessRulesWork).toBeTrue(); // Business rules must be enforced
|
|
expect(mandatoryElementsWork).toBeTrue(); // All mandatory elements must be present
|
|
});
|
|
|
|
tap.start(); |