import { tap } from '@git.zone/tstest/tapbundle'; import * as plugins from '../plugins.js'; import { EInvoice } from '../../../ts/index.js'; import { PerformanceTracker } from '../performance.tracker.js'; import { CorpusLoader } from '../corpus.loader.js'; const performanceTracker = new PerformanceTracker('STD-02: XRechnung CIUS Compliance'); tap.test('STD-02: XRechnung CIUS Compliance - should validate XRechnung Core Invoice Usage Specification', async (t) => { const einvoice = new EInvoice(); const corpusLoader = new CorpusLoader(); // Test 1: XRechnung specific mandatory fields const xrechnungMandatoryFields = await performanceTracker.measureAsync( 'xrechnung-mandatory-fields', async () => { const xrechnungSpecificFields = [ 'BT-10', // Buyer reference (mandatory in XRechnung) 'BT-23', // Business process 'BT-24', // Specification identifier (must be specific value) 'BT-49', // Buyer electronic address 'BT-34', // Seller electronic address 'BG-4', // Seller postal address (all sub-elements mandatory) 'BG-8', // Buyer postal address (all sub-elements mandatory) ]; const testCases = [ { name: 'complete-xrechnung', xml: createCompleteXRechnungInvoice() }, { name: 'missing-buyer-reference', xml: createXRechnungWithoutField('BT-10') }, { name: 'missing-electronic-addresses', xml: createXRechnungWithoutField(['BT-49', 'BT-34']) }, { name: 'incomplete-postal-address', xml: createXRechnungWithIncompleteAddress() } ]; const results = []; for (const test of testCases) { try { const parsed = await einvoice.parseDocument(test.xml); const validation = await einvoice.validateXRechnung(parsed); results.push({ testCase: test.name, valid: validation?.isValid || false, xrechnungCompliant: validation?.xrechnungCompliant || false, missingFields: validation?.missingXRechnungFields || [], errors: validation?.errors || [] }); } catch (error) { results.push({ testCase: test.name, valid: false, error: error.message }); } } return results; } ); const completeTest = xrechnungMandatoryFields.find(r => r.testCase === 'complete-xrechnung'); t.ok(completeTest?.xrechnungCompliant, 'Complete XRechnung invoice should be compliant'); xrechnungMandatoryFields.filter(r => r.testCase !== 'complete-xrechnung').forEach(result => { t.notOk(result.xrechnungCompliant, `${result.testCase} should not be XRechnung compliant`); t.ok(result.missingFields?.length > 0, 'Missing XRechnung fields should be detected'); }); // Test 2: XRechnung specific business rules const xrechnungBusinessRules = await performanceTracker.measureAsync( 'xrechnung-business-rules', async () => { const xrechnungRules = [ { rule: 'BR-DE-1', description: 'Payment account must be provided for credit transfer', test: createInvoiceViolatingXRechnungRule('BR-DE-1') }, { rule: 'BR-DE-2', description: 'Buyer reference is mandatory', test: createInvoiceViolatingXRechnungRule('BR-DE-2') }, { rule: 'BR-DE-3', description: 'Specification identifier must be correct', test: createInvoiceViolatingXRechnungRule('BR-DE-3') }, { rule: 'BR-DE-15', description: 'Buyer electronic address must be provided', test: createInvoiceViolatingXRechnungRule('BR-DE-15') }, { rule: 'BR-DE-21', description: 'VAT identifier format must be correct', test: createInvoiceViolatingXRechnungRule('BR-DE-21') } ]; const results = []; for (const ruleTest of xrechnungRules) { try { const parsed = await einvoice.parseDocument(ruleTest.test); const validation = await einvoice.validateXRechnungBusinessRules(parsed); const violation = validation?.violations?.find(v => v.rule === ruleTest.rule); results.push({ rule: ruleTest.rule, description: ruleTest.description, violated: !!violation, severity: violation?.severity || 'unknown', message: violation?.message }); } catch (error) { results.push({ rule: ruleTest.rule, error: error.message }); } } return results; } ); xrechnungBusinessRules.forEach(result => { t.ok(result.violated, `XRechnung rule ${result.rule} violation should be detected`); }); // Test 3: Leitweg-ID validation const leitwegIdValidation = await performanceTracker.measureAsync( 'leitweg-id-validation', async () => { const leitwegTests = [ { name: 'valid-format', leitwegId: '04011000-12345-67', expected: { valid: true } }, { name: 'valid-with-extension', leitwegId: '04011000-12345-67-001', expected: { valid: true } }, { name: 'invalid-checksum', leitwegId: '04011000-12345-99', expected: { valid: false, error: 'checksum' } }, { name: 'invalid-format', leitwegId: '12345', expected: { valid: false, error: 'format' } }, { name: 'missing-leitweg', leitwegId: null, expected: { valid: false, error: 'missing' } } ]; const results = []; for (const test of leitwegTests) { const invoice = createXRechnungWithLeitwegId(test.leitwegId); const validation = await einvoice.validateLeitwegId(invoice); results.push({ test: test.name, leitwegId: test.leitwegId, valid: validation?.isValid || false, checksumValid: validation?.checksumValid, formatValid: validation?.formatValid, error: validation?.error }); } return results; } ); leitwegIdValidation.forEach(result => { const expected = leitwegTests.find(t => t.name === result.test)?.expected; t.equal(result.valid, expected?.valid, `Leitweg-ID ${result.test} validation should match expected`); }); // Test 4: XRechnung version compliance const versionCompliance = await performanceTracker.measureAsync( 'xrechnung-version-compliance', async () => { const versions = [ { version: '1.2', specId: 'urn:cen.eu:en16931:2017:compliant:xoev-de:kosit:standard:xrechnung_1.2', supported: false }, { version: '2.0', specId: 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0', supported: true }, { version: '2.3', specId: 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.3', supported: true }, { version: '3.0', specId: 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_3.0', supported: true } ]; const results = []; for (const ver of versions) { const invoice = createXRechnungWithVersion(ver.specId); const validation = await einvoice.validateXRechnungVersion(invoice); results.push({ version: ver.version, specId: ver.specId, recognized: validation?.versionRecognized || false, supported: validation?.versionSupported || false, deprecated: validation?.deprecated || false, migrationPath: validation?.migrationPath }); } return results; } ); versionCompliance.forEach(result => { const expected = versions.find(v => v.version === result.version); if (expected?.supported) { t.ok(result.supported, `XRechnung version ${result.version} should be supported`); } }); // Test 5: Code list restrictions const codeListRestrictions = await performanceTracker.measureAsync( 'xrechnung-code-list-restrictions', async () => { const codeTests = [ { field: 'Payment means', code: '1', // Instrument not defined allowed: false, alternative: '58' // SEPA credit transfer }, { field: 'Tax category', code: 'B', // Split payment allowed: false, alternative: 'S' // Standard rate }, { field: 'Invoice type', code: '384', // Corrected invoice allowed: false, alternative: '380' // Commercial invoice } ]; const results = []; for (const test of codeTests) { const invoice = createXRechnungWithCode(test.field, test.code); const validation = await einvoice.validateXRechnungCodeLists(invoice); const alternative = createXRechnungWithCode(test.field, test.alternative); const altValidation = await einvoice.validateXRechnungCodeLists(alternative); results.push({ field: test.field, code: test.code, rejected: !validation?.isValid, alternativeCode: test.alternative, alternativeAccepted: altValidation?.isValid || false, reason: validation?.codeListErrors?.[0] }); } return results; } ); codeListRestrictions.forEach(result => { t.ok(result.rejected, `Restricted code ${result.code} for ${result.field} should be rejected`); t.ok(result.alternativeAccepted, `Alternative code ${result.alternativeCode} should be accepted`); }); // Test 6: XRechnung extension handling const extensionHandling = await performanceTracker.measureAsync( 'xrechnung-extension-handling', async () => { const extensionTests = [ { name: 'ublex-extension', xml: createXRechnungWithUBLExtension() }, { name: 'additional-doc-ref', xml: createXRechnungWithAdditionalDocRef() }, { name: 'custom-fields', xml: createXRechnungWithCustomFields() } ]; const results = []; for (const test of extensionTests) { try { const parsed = await einvoice.parseDocument(test.xml); const validation = await einvoice.validateXRechnungWithExtensions(parsed); results.push({ extension: test.name, valid: validation?.isValid || false, coreCompliant: validation?.coreXRechnungValid || false, extensionAllowed: validation?.extensionAllowed || false, extensionPreserved: validation?.extensionDataIntact || false }); } catch (error) { results.push({ extension: test.name, error: error.message }); } } return results; } ); extensionHandling.forEach(result => { t.ok(result.coreCompliant, `Core XRechnung should remain valid with ${result.extension}`); }); // Test 7: KOSIT validator compatibility const kositValidatorCompatibility = await performanceTracker.measureAsync( 'kosit-validator-compatibility', async () => { const kositScenarios = [ { name: 'standard-invoice', scenario: 'EN16931 CIUS XRechnung (UBL Invoice)' }, { name: 'credit-note', scenario: 'EN16931 CIUS XRechnung (UBL CreditNote)' }, { name: 'cii-invoice', scenario: 'EN16931 CIUS XRechnung (CII)' } ]; const results = []; for (const scenario of kositScenarios) { const invoice = createInvoiceForKOSITScenario(scenario.name); const validation = await einvoice.validateWithKOSITRules(invoice); results.push({ scenario: scenario.name, kositScenario: scenario.scenario, schematronValid: validation?.schematronPassed || false, schemaValid: validation?.schemaPassed || false, businessRulesValid: validation?.businessRulesPassed || false, overallValid: validation?.isValid || false }); } return results; } ); kositValidatorCompatibility.forEach(result => { t.ok(result.overallValid, `KOSIT scenario ${result.scenario} should validate`); }); // Test 8: Corpus XRechnung validation const corpusXRechnungValidation = await performanceTracker.measureAsync( 'corpus-xrechnung-validation', async () => { const xrechnungFiles = await corpusLoader.getFilesByPattern('**/XRECHNUNG*.xml'); const results = { total: xrechnungFiles.length, valid: 0, invalid: 0, errors: [], versions: {} }; for (const file of xrechnungFiles.slice(0, 10)) { // Test first 10 try { const content = await corpusLoader.readFile(file); const parsed = await einvoice.parseDocument(content); const validation = await einvoice.validateXRechnung(parsed); if (validation?.isValid) { results.valid++; const version = validation.xrechnungVersion || 'unknown'; results.versions[version] = (results.versions[version] || 0) + 1; } else { results.invalid++; results.errors.push({ file: file.name, errors: validation?.errors?.slice(0, 3) }); } } catch (error) { results.invalid++; results.errors.push({ file: file.name, error: error.message }); } } return results; } ); t.ok(corpusXRechnungValidation.valid > 0, 'Some corpus files should be valid XRechnung'); // Test 9: German administrative requirements const germanAdminRequirements = await performanceTracker.measureAsync( 'german-administrative-requirements', async () => { const adminTests = [ { name: 'tax-number-format', field: 'German tax number', valid: 'DE123456789', invalid: '123456789' }, { name: 'bank-account-iban', field: 'IBAN', valid: 'DE89370400440532013000', invalid: 'DE00000000000000000000' }, { name: 'postal-code-format', field: 'Postal code', valid: '10115', invalid: '1234' }, { name: 'email-format', field: 'Email', valid: 'rechnung@example.de', invalid: 'invalid-email' } ]; const results = []; for (const test of adminTests) { // Test valid format const validInvoice = createXRechnungWithAdminField(test.field, test.valid); const validResult = await einvoice.validateGermanAdminRequirements(validInvoice); // Test invalid format const invalidInvoice = createXRechnungWithAdminField(test.field, test.invalid); const invalidResult = await einvoice.validateGermanAdminRequirements(invalidInvoice); results.push({ requirement: test.name, field: test.field, validAccepted: validResult?.isValid || false, invalidRejected: !invalidResult?.isValid, formatError: invalidResult?.formatErrors?.[0] }); } return results; } ); germanAdminRequirements.forEach(result => { t.ok(result.validAccepted, `Valid ${result.field} should be accepted`); t.ok(result.invalidRejected, `Invalid ${result.field} should be rejected`); }); // Test 10: XRechnung profile variations const profileVariations = await performanceTracker.measureAsync( 'xrechnung-profile-variations', async () => { const profiles = [ { name: 'B2G', description: 'Business to Government', requirements: ['Leitweg-ID', 'Buyer reference', 'Order reference'] }, { name: 'B2B-public', description: 'B2B with public sector involvement', requirements: ['Buyer reference', 'Contract reference'] }, { name: 'Cross-border', description: 'Cross-border within EU', requirements: ['VAT numbers', 'Country codes'] } ]; const results = []; for (const profile of profiles) { const invoice = createXRechnungForProfile(profile); const validation = await einvoice.validateXRechnungProfile(invoice, profile.name); results.push({ profile: profile.name, description: profile.description, valid: validation?.isValid || false, profileCompliant: validation?.profileCompliant || false, missingRequirements: validation?.missingRequirements || [], additionalChecks: validation?.additionalChecksPassed || false }); } return results; } ); profileVariations.forEach(result => { t.ok(result.profileCompliant, `XRechnung profile ${result.profile} should be compliant`); }); // Print performance summary performanceTracker.printSummary(); }); // Helper functions function createCompleteXRechnungInvoice(): string { return ` urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.3 urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 RE-2024-00001 2024-01-15 2024-02-15 380 EUR 04011000-12345-67 seller@example.de Verkäufer GmbH Musterstraße 1 Berlin 10115 DE DE123456789 VAT buyer@example.de Käufer AG Beispielweg 2 Hamburg 20095 DE 58 DE89370400440532013000 1000.00 1000.00 1190.00 1190.00 1 10 1000.00 Produkt A 100.00 `; } function createXRechnungWithoutField(fields: string | string[]): string { const fieldsToRemove = Array.isArray(fields) ? fields : [fields]; let invoice = createCompleteXRechnungInvoice(); if (fieldsToRemove.includes('BT-10')) { invoice = invoice.replace(/.*?<\/cbc:BuyerReference>/, ''); } if (fieldsToRemove.includes('BT-49')) { invoice = invoice.replace(/buyer@example.de<\/cbc:EndpointID>/, ''); } return invoice; } function createXRechnungWithIncompleteAddress(): string { let invoice = createCompleteXRechnungInvoice(); // Remove postal code from address return invoice.replace(/.*?<\/cbc:PostalZone>/, ''); } function createInvoiceViolatingXRechnungRule(rule: string): string { const base = createCompleteXRechnungInvoice(); switch (rule) { case 'BR-DE-1': // Remove payment account for credit transfer return base.replace(/[\s\S]*?<\/cac:PayeeFinancialAccount>/, ''); case 'BR-DE-2': // Remove buyer reference return base.replace(/.*?<\/cbc:BuyerReference>/, ''); case 'BR-DE-3': // Wrong specification identifier return base.replace( 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.3', 'urn:cen.eu:en16931:2017' ); default: return base; } } function createXRechnungWithLeitwegId(leitwegId: string | null): any { return { buyerReference: leitwegId, supplierParty: { name: 'Test Supplier' }, customerParty: { name: 'Test Customer' } }; } function createXRechnungWithVersion(specId: string): string { const base = createCompleteXRechnungInvoice(); return base.replace( /.*?<\/cbc:CustomizationID>/, `${specId}` ); } function createXRechnungWithCode(field: string, code: string): any { return { paymentMeansCode: field === 'Payment means' ? code : '58', taxCategoryCode: field === 'Tax category' ? code : 'S', invoiceTypeCode: field === 'Invoice type' ? code : '380' }; } function createXRechnungWithUBLExtension(): string { const base = createCompleteXRechnungInvoice(); const extension = ` Custom Value `; return base.replace('', extension + '\n '); } function createXRechnungWithAdditionalDocRef(): string { const base = createCompleteXRechnungInvoice(); const docRef = ` DOC-001 Lieferschein `; return base.replace('', docRef + '\n'); } function createXRechnungWithCustomFields(): string { const base = createCompleteXRechnungInvoice(); return base.replace( '', 'CUSTOM:Field1=Value1;Field2=Value2\n ' ); } function createInvoiceForKOSITScenario(scenario: string): string { if (scenario === 'credit-note') { return createCompleteXRechnungInvoice().replace( '380', '381' ); } return createCompleteXRechnungInvoice(); } function createXRechnungWithAdminField(field: string, value: string): any { const invoice = { supplierTaxId: field === 'German tax number' ? value : 'DE123456789', paymentAccount: field === 'IBAN' ? value : 'DE89370400440532013000', postalCode: field === 'Postal code' ? value : '10115', email: field === 'Email' ? value : 'test@example.de' }; return invoice; } function createXRechnungForProfile(profile: any): string { const base = createCompleteXRechnungInvoice(); if (profile.name === 'B2G') { // Already has Leitweg-ID as BuyerReference return base; } else if (profile.name === 'Cross-border') { // Add foreign VAT number return base.replace( 'DE123456789', 'ATU12345678' ); } return base; } const leitwegTests = [ { name: 'valid-format', leitwegId: '04011000-12345-67', expected: { valid: true } }, { name: 'valid-with-extension', leitwegId: '04011000-12345-67-001', expected: { valid: true } }, { name: 'invalid-checksum', leitwegId: '04011000-12345-99', expected: { valid: false } }, { name: 'invalid-format', leitwegId: '12345', expected: { valid: false } }, { name: 'missing-leitweg', leitwegId: null, expected: { valid: false } } ]; const versions = [ { version: '1.2', specId: 'urn:cen.eu:en16931:2017:compliant:xoev-de:kosit:standard:xrechnung_1.2', supported: false }, { version: '2.0', specId: 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0', supported: true }, { version: '2.3', specId: 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.3', supported: true }, { version: '3.0', specId: 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_3.0', supported: true } ]; // Run the test tap.start();