272 lines
9.8 KiB
TypeScript
272 lines
9.8 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
import { InvoiceFormat } from '../../../ts/interfaces/common.js';
|
|
import { CorpusLoader, PerformanceTracker } from '../../helpers/test-utils.js';
|
|
import * as path from 'path';
|
|
|
|
/**
|
|
* Test ID: STD-08
|
|
* Test Description: CII D16B Compliance
|
|
* Priority: High
|
|
*
|
|
* This test validates compliance with the UN/CEFACT Cross Industry Invoice (CII) D16B standard,
|
|
* ensuring proper structure, data types, and business term mappings.
|
|
*/
|
|
|
|
tap.test('STD-08: CII D16B Compliance - should validate CII D16B standard compliance', async (t) => {
|
|
// CII D16B namespace and structure requirements
|
|
const ciiNamespaces = {
|
|
rsm: 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
|
|
qdt: 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100',
|
|
ram: 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100',
|
|
udt: 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'
|
|
};
|
|
|
|
// Test 1: Namespace and Root Element Compliance
|
|
t.test('CII D16B namespace and root element', async (st) => {
|
|
const ciiFiles = await CorpusLoader.getFiles('XML_RECHNUNG_CII');
|
|
const testFiles = ciiFiles.slice(0, 5);
|
|
|
|
for (const file of testFiles) {
|
|
const xmlBuffer = await CorpusLoader.loadFile(file);
|
|
const xmlString = xmlBuffer.toString('utf-8');
|
|
|
|
// Check root element
|
|
const hasCorrectRoot = xmlString.includes('<rsm:CrossIndustryInvoice') ||
|
|
xmlString.includes('<CrossIndustryInvoice');
|
|
expect(hasCorrectRoot).toBeTrue();
|
|
|
|
// Check required namespaces
|
|
const hasRSMNamespace = xmlString.includes(ciiNamespaces.rsm);
|
|
const hasRAMNamespace = xmlString.includes(ciiNamespaces.ram);
|
|
|
|
expect(hasRSMNamespace || xmlString.includes('CrossIndustryInvoice')).toBeTrue();
|
|
expect(hasRAMNamespace || xmlString.includes('ram:')).toBeTrue();
|
|
|
|
st.pass(`✓ ${path.basename(file)}: CII D16B structure compliant`);
|
|
}
|
|
});
|
|
|
|
// Test 2: Document Context Requirements
|
|
t.test('CII D16B document context', async (st) => {
|
|
const invoice = new EInvoice();
|
|
invoice.id = 'CII-CTX-001';
|
|
invoice.issueDate = new Date();
|
|
invoice.from = { name: 'Seller', address: { country: 'DE' } };
|
|
invoice.to = { name: 'Buyer', address: { country: 'DE' } };
|
|
invoice.items = [{ name: 'Product', quantity: 1, unitPrice: 100 }];
|
|
|
|
const ciiXml = await invoice.toXmlString('cii');
|
|
|
|
// Check for ExchangedDocumentContext
|
|
expect(ciiXml.includes('ExchangedDocumentContext')).toBeTrue();
|
|
|
|
// Check for GuidelineSpecifiedDocumentContextParameter
|
|
const hasGuideline = ciiXml.includes('GuidelineSpecifiedDocumentContextParameter') ||
|
|
ciiXml.includes('SpecifiedDocumentContextParameter');
|
|
expect(hasGuideline).toBeTrue();
|
|
|
|
st.pass('✓ CII D16B document context is present');
|
|
});
|
|
|
|
// Test 3: Header Structure Compliance
|
|
t.test('CII D16B header structure', async (st) => {
|
|
const requiredHeaders = [
|
|
'ExchangedDocument',
|
|
'SupplyChainTradeTransaction',
|
|
'ApplicableHeaderTradeAgreement',
|
|
'ApplicableHeaderTradeDelivery',
|
|
'ApplicableHeaderTradeSettlement'
|
|
];
|
|
|
|
const invoice = new EInvoice();
|
|
invoice.id = 'CII-HDR-001';
|
|
invoice.issueDate = new Date();
|
|
invoice.currency = 'EUR';
|
|
invoice.from = {
|
|
name: 'Test Supplier',
|
|
address: { street: 'Main St', city: 'Berlin', postalCode: '10115', country: 'DE' },
|
|
vatNumber: 'DE123456789'
|
|
};
|
|
invoice.to = {
|
|
name: 'Test Buyer',
|
|
address: { street: 'Market St', city: 'Munich', postalCode: '80331', country: 'DE' }
|
|
};
|
|
invoice.items = [{
|
|
name: 'Service',
|
|
description: 'Consulting',
|
|
quantity: 10,
|
|
unitPrice: 150,
|
|
taxPercent: 19
|
|
}];
|
|
|
|
const xml = await invoice.toXmlString('cii');
|
|
|
|
for (const header of requiredHeaders) {
|
|
expect(xml.includes(header)).toBeTrue();
|
|
st.pass(`✓ Required header element: ${header}`);
|
|
}
|
|
});
|
|
|
|
// Test 4: Trade Party Information Compliance
|
|
t.test('CII D16B trade party information', async (st) => {
|
|
const invoice = new EInvoice();
|
|
invoice.id = 'CII-PARTY-001';
|
|
invoice.issueDate = new Date();
|
|
invoice.from = {
|
|
name: 'Seller Company GmbH',
|
|
address: {
|
|
street: 'Hauptstraße 1',
|
|
city: 'Berlin',
|
|
postalCode: '10115',
|
|
country: 'DE'
|
|
},
|
|
vatNumber: 'DE123456789',
|
|
email: 'info@seller.de'
|
|
};
|
|
invoice.to = {
|
|
name: 'Buyer AG',
|
|
address: {
|
|
street: 'Marktplatz 5',
|
|
city: 'München',
|
|
postalCode: '80331',
|
|
country: 'DE'
|
|
},
|
|
registrationNumber: 'HRB 12345'
|
|
};
|
|
invoice.items = [{ name: 'Item', quantity: 1, unitPrice: 100 }];
|
|
|
|
const xml = await invoice.toXmlString('cii');
|
|
|
|
// Check seller party structure
|
|
expect(xml.includes('SellerTradeParty')).toBeTrue();
|
|
expect(xml.includes('Seller Company GmbH')).toBeTrue();
|
|
expect(xml.includes('DE123456789')).toBeTrue();
|
|
|
|
// Check buyer party structure
|
|
expect(xml.includes('BuyerTradeParty')).toBeTrue();
|
|
expect(xml.includes('Buyer AG')).toBeTrue();
|
|
|
|
// Check address structure
|
|
expect(xml.includes('PostalTradeAddress')).toBeTrue();
|
|
expect(xml.includes('10115')).toBeTrue(); // Postal code
|
|
|
|
st.pass('✓ CII D16B trade party information is compliant');
|
|
});
|
|
|
|
// Test 5: Line Item Structure Compliance
|
|
t.test('CII D16B line item structure', async (st) => {
|
|
const invoice = new EInvoice();
|
|
invoice.id = 'CII-LINE-001';
|
|
invoice.issueDate = new Date();
|
|
invoice.from = { name: 'Seller', address: { country: 'DE' } };
|
|
invoice.to = { name: 'Buyer', address: { country: 'DE' } };
|
|
invoice.items = [{
|
|
id: 'ITEM-001',
|
|
name: 'Professional Service',
|
|
description: 'Consulting service for project X',
|
|
quantity: 20,
|
|
unitPrice: 250,
|
|
unit: 'HUR', // Hours
|
|
taxPercent: 19,
|
|
articleNumber: 'SRV-001'
|
|
}];
|
|
|
|
const xml = await invoice.toXmlString('cii');
|
|
|
|
// Check line item structure
|
|
expect(xml.includes('IncludedSupplyChainTradeLineItem')).toBeTrue();
|
|
expect(xml.includes('AssociatedDocumentLineDocument')).toBeTrue();
|
|
expect(xml.includes('SpecifiedTradeProduct')).toBeTrue();
|
|
expect(xml.includes('SpecifiedLineTradeAgreement')).toBeTrue();
|
|
expect(xml.includes('SpecifiedLineTradeDelivery')).toBeTrue();
|
|
expect(xml.includes('SpecifiedLineTradeSettlement')).toBeTrue();
|
|
|
|
// Check specific values
|
|
expect(xml.includes('Professional Service')).toBeTrue();
|
|
expect(xml.includes('20')).toBeTrue(); // Quantity
|
|
|
|
st.pass('✓ CII D16B line item structure is compliant');
|
|
});
|
|
|
|
// Test 6: Monetary Summation Compliance
|
|
t.test('CII D16B monetary summation', async (st) => {
|
|
const invoice = new EInvoice();
|
|
invoice.id = 'CII-SUM-001';
|
|
invoice.issueDate = new Date();
|
|
invoice.currency = 'EUR';
|
|
invoice.from = { name: 'Seller', address: { country: 'DE' } };
|
|
invoice.to = { name: 'Buyer', address: { country: 'DE' } };
|
|
invoice.items = [
|
|
{ name: 'Item 1', quantity: 10, unitPrice: 100, taxPercent: 19 },
|
|
{ name: 'Item 2', quantity: 5, unitPrice: 200, taxPercent: 19 }
|
|
];
|
|
|
|
const xml = await invoice.toXmlString('cii');
|
|
|
|
// Check monetary summation structure
|
|
expect(xml.includes('SpecifiedTradeSettlementHeaderMonetarySummation')).toBeTrue();
|
|
expect(xml.includes('LineTotalAmount')).toBeTrue();
|
|
expect(xml.includes('TaxBasisTotalAmount')).toBeTrue();
|
|
expect(xml.includes('TaxTotalAmount')).toBeTrue();
|
|
expect(xml.includes('GrandTotalAmount')).toBeTrue();
|
|
expect(xml.includes('DuePayableAmount')).toBeTrue();
|
|
|
|
// Verify calculation (10*100 + 5*200 = 2000, tax = 380, total = 2380)
|
|
expect(xml.includes('2000')).toBeTrue(); // Line total
|
|
expect(xml.includes('2380')).toBeTrue(); // Grand total
|
|
|
|
st.pass('✓ CII D16B monetary summation is compliant');
|
|
});
|
|
|
|
// Test 7: Date/Time Format Compliance
|
|
t.test('CII D16B date/time format', async (st) => {
|
|
const invoice = new EInvoice();
|
|
invoice.id = 'CII-DATE-001';
|
|
invoice.issueDate = new Date('2024-03-15');
|
|
invoice.dueDate = new Date('2024-04-15');
|
|
invoice.from = { name: 'Seller', address: { country: 'DE' } };
|
|
invoice.to = { name: 'Buyer', address: { country: 'DE' } };
|
|
invoice.items = [{ name: 'Item', quantity: 1, unitPrice: 100 }];
|
|
|
|
const xml = await invoice.toXmlString('cii');
|
|
|
|
// CII uses YYYYMMDD format for dates
|
|
const datePattern = />(\d{8})</g;
|
|
const dates = [...xml.matchAll(datePattern)].map(m => m[1]);
|
|
|
|
expect(dates.length).toBeGreaterThan(0);
|
|
|
|
// Check format
|
|
for (const date of dates) {
|
|
expect(date).toMatch(/^\d{8}$/);
|
|
st.pass(`✓ Valid CII date format: ${date}`);
|
|
}
|
|
});
|
|
|
|
// Test 8: Code List Compliance
|
|
t.test('CII D16B code list compliance', async (st) => {
|
|
// Test various code lists used in CII
|
|
const codeLists = {
|
|
currencyCode: { value: 'EUR', list: 'ISO 4217' },
|
|
countryCode: { value: 'DE', list: 'ISO 3166-1' },
|
|
taxCategoryCode: { value: 'S', list: 'UNCL5305' },
|
|
unitCode: { value: 'C62', list: 'UNECE Rec 20' }
|
|
};
|
|
|
|
for (const [codeType, info] of Object.entries(codeLists)) {
|
|
// In real implementation, would validate against actual code lists
|
|
expect(info.value.length).toBeGreaterThan(0);
|
|
st.pass(`✓ Valid ${codeType}: ${info.value} (${info.list})`);
|
|
}
|
|
});
|
|
|
|
// Performance summary
|
|
const perfSummary = await PerformanceTracker.getSummary('cii-compliance');
|
|
if (perfSummary) {
|
|
console.log('\nCII D16B Compliance Test Performance:');
|
|
console.log(` Average: ${perfSummary.average.toFixed(2)}ms`);
|
|
}
|
|
});
|
|
|
|
tap.start(); |