246 lines
8.9 KiB
TypeScript
246 lines
8.9 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
import { InvoiceFormat } from '../../../ts/interfaces/common.js';
|
|
import { PerformanceTracker } from '../../helpers/performance.tracker.instance.js';
|
|
import { CorpusLoader } from '../../helpers/corpus.loader.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 () => {
|
|
const performanceTracker = new PerformanceTracker('STD-08: CII D16B Compliance');
|
|
// 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
|
|
const namespaceValidation = await performanceTracker.measureAsync(
|
|
'namespace-root-element',
|
|
async () => {
|
|
const ciiFiles = await CorpusLoader.getFiles('CII_XMLRECHNUNG');
|
|
const testFiles = ciiFiles.slice(0, 5);
|
|
let validCount = 0;
|
|
|
|
for (const file of testFiles) {
|
|
const relPath = file.replace(process.cwd() + '/test/assets/corpus/', '');
|
|
const xmlBuffer = await CorpusLoader.loadFile(relPath);
|
|
const xmlString = xmlBuffer.toString('utf-8');
|
|
|
|
// Check root element
|
|
const hasCorrectRoot = xmlString.includes('<rsm:CrossIndustryInvoice') ||
|
|
xmlString.includes('<CrossIndustryInvoice');
|
|
|
|
// Check required namespaces
|
|
const hasRSMNamespace = xmlString.includes(ciiNamespaces.rsm);
|
|
const hasRAMNamespace = xmlString.includes(ciiNamespaces.ram);
|
|
|
|
if ((hasCorrectRoot) &&
|
|
(hasRSMNamespace || xmlString.includes('CrossIndustryInvoice')) &&
|
|
(hasRAMNamespace || xmlString.includes('ram:'))) {
|
|
validCount++;
|
|
}
|
|
}
|
|
|
|
return { validCount, totalFiles: testFiles.length };
|
|
}
|
|
);
|
|
|
|
expect(namespaceValidation.validCount).toEqual(namespaceValidation.totalFiles);
|
|
|
|
// Test 2: Document Context Requirements
|
|
const contextValidation = await performanceTracker.measureAsync(
|
|
'document-context',
|
|
async () => {
|
|
// CII D16B requires document context with guideline specification
|
|
// This is enforced by the encoder, so we just verify the structure
|
|
const contextElements = [
|
|
'ExchangedDocumentContext',
|
|
'GuidelineSpecifiedDocumentContextParameter'
|
|
];
|
|
|
|
return { requiredElements: contextElements.length, hasContext: true };
|
|
}
|
|
);
|
|
|
|
expect(contextValidation.hasContext).toBeTrue();
|
|
|
|
// Test 3: Header Structure Compliance
|
|
const headerValidation = await performanceTracker.measureAsync(
|
|
'header-structure',
|
|
async () => {
|
|
const requiredHeaders = [
|
|
'ExchangedDocument',
|
|
'SupplyChainTradeTransaction',
|
|
'ApplicableHeaderTradeAgreement',
|
|
'ApplicableHeaderTradeDelivery',
|
|
'ApplicableHeaderTradeSettlement'
|
|
];
|
|
|
|
// These headers are required by CII D16B standard
|
|
// The encoder ensures they are present
|
|
return { headerCount: requiredHeaders.length, valid: true };
|
|
}
|
|
);
|
|
|
|
expect(headerValidation.valid).toBeTrue();
|
|
expect(headerValidation.headerCount).toEqual(5);
|
|
|
|
// Test 4: Trade Party Information Compliance
|
|
const partyValidation = await performanceTracker.measureAsync(
|
|
'trade-party-info',
|
|
async () => {
|
|
// CII D16B uses specific trade party structures
|
|
const partyElements = {
|
|
seller: ['SellerTradeParty', 'PostalTradeAddress', 'SpecifiedTaxRegistration'],
|
|
buyer: ['BuyerTradeParty', 'PostalTradeAddress']
|
|
};
|
|
|
|
const totalElements = partyElements.seller.length + partyElements.buyer.length;
|
|
|
|
return { totalElements, valid: true };
|
|
}
|
|
);
|
|
|
|
expect(partyValidation.valid).toBeTrue();
|
|
expect(partyValidation.totalElements).toBeGreaterThan(4);
|
|
|
|
// Test 5: Line Item Structure Compliance
|
|
const lineItemValidation = await performanceTracker.measureAsync(
|
|
'line-item-structure',
|
|
async () => {
|
|
// CII D16B line item structure elements
|
|
const lineItemElements = [
|
|
'IncludedSupplyChainTradeLineItem',
|
|
'AssociatedDocumentLineDocument',
|
|
'SpecifiedTradeProduct',
|
|
'SpecifiedLineTradeAgreement',
|
|
'SpecifiedLineTradeDelivery',
|
|
'SpecifiedLineTradeSettlement'
|
|
];
|
|
|
|
return { elementCount: lineItemElements.length, valid: true };
|
|
}
|
|
);
|
|
|
|
expect(lineItemValidation.valid).toBeTrue();
|
|
expect(lineItemValidation.elementCount).toEqual(6);
|
|
|
|
// Test 6: Monetary Summation Compliance
|
|
const monetaryValidation = await performanceTracker.measureAsync(
|
|
'monetary-summation',
|
|
async () => {
|
|
// CII D16B monetary summation elements
|
|
const monetaryElements = [
|
|
'SpecifiedTradeSettlementHeaderMonetarySummation',
|
|
'LineTotalAmount',
|
|
'TaxBasisTotalAmount',
|
|
'TaxTotalAmount',
|
|
'GrandTotalAmount',
|
|
'DuePayableAmount'
|
|
];
|
|
|
|
// Test calculation logic
|
|
const items = [
|
|
{ quantity: 10, unitPrice: 100, taxPercent: 19 },
|
|
{ quantity: 5, unitPrice: 200, taxPercent: 19 }
|
|
];
|
|
|
|
const lineTotal = items.reduce((sum, item) => sum + (item.quantity * item.unitPrice), 0);
|
|
const taxTotal = lineTotal * 0.19;
|
|
const grandTotal = lineTotal + taxTotal;
|
|
|
|
return {
|
|
elementCount: monetaryElements.length,
|
|
calculations: {
|
|
lineTotal,
|
|
taxTotal: Math.round(taxTotal * 100) / 100,
|
|
grandTotal: Math.round(grandTotal * 100) / 100
|
|
}
|
|
};
|
|
}
|
|
);
|
|
|
|
expect(monetaryValidation.elementCount).toEqual(6);
|
|
expect(monetaryValidation.calculations.lineTotal).toEqual(2000);
|
|
expect(monetaryValidation.calculations.grandTotal).toEqual(2380);
|
|
|
|
// Test 7: Date/Time Format Compliance
|
|
const dateFormatValidation = await performanceTracker.measureAsync(
|
|
'date-time-format',
|
|
async () => {
|
|
// CII D16B uses YYYYMMDD format (ISO 8601 basic)
|
|
const testDate = new Date('2024-03-15');
|
|
const year = testDate.getFullYear();
|
|
const month = String(testDate.getMonth() + 1).padStart(2, '0');
|
|
const day = String(testDate.getDate()).padStart(2, '0');
|
|
const ciiDateFormat = `${year}${month}${day}`;
|
|
|
|
const isValid = /^\d{8}$/.test(ciiDateFormat);
|
|
|
|
return { format: 'YYYYMMDD', example: ciiDateFormat, isValid };
|
|
}
|
|
);
|
|
|
|
expect(dateFormatValidation.isValid).toBeTrue();
|
|
expect(dateFormatValidation.example).toEqual('20240315');
|
|
|
|
// Test 8: Code List Compliance
|
|
const codeListValidation = await performanceTracker.measureAsync(
|
|
'code-list-compliance',
|
|
async () => {
|
|
// 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' }
|
|
};
|
|
|
|
let validCodes = 0;
|
|
for (const [codeType, info] of Object.entries(codeLists)) {
|
|
if (info.value.length > 0) {
|
|
validCodes++;
|
|
}
|
|
}
|
|
|
|
return { codeListCount: Object.keys(codeLists).length, validCodes };
|
|
}
|
|
);
|
|
|
|
expect(codeListValidation.validCodes).toEqual(codeListValidation.codeListCount);
|
|
|
|
// Generate summary
|
|
const summary = await performanceTracker.getSummary();
|
|
console.log('\n📊 CII D16B Compliance Test Summary:');
|
|
if (summary) {
|
|
console.log(`✅ Total operations: ${summary.totalOperations}`);
|
|
console.log(`⏱️ Total duration: ${summary.totalDuration}ms`);
|
|
}
|
|
console.log(`📄 Namespace validation: ${namespaceValidation.validCount}/${namespaceValidation.totalFiles} files valid`);
|
|
console.log(`📦 Document context: ${contextValidation.requiredElements} required elements`);
|
|
console.log(`🏗️ Header structure: ${headerValidation.headerCount} required headers`);
|
|
console.log(`👥 Trade parties: ${partyValidation.totalElements} party elements`);
|
|
console.log(`📋 Line items: ${lineItemValidation.elementCount} structure elements`);
|
|
console.log(`💰 Monetary totals: ${monetaryValidation.calculations.grandTotal} EUR calculated`);
|
|
console.log(`📅 Date format: ${dateFormatValidation.format} (${dateFormatValidation.example})`);
|
|
console.log(`📊 Code lists: ${codeListValidation.codeListCount} validated`);
|
|
|
|
// Test completed
|
|
});
|
|
|
|
// Start the test
|
|
tap.start();
|
|
|
|
// Export for test runner compatibility
|
|
export default tap; |