2025-05-26 05:16:32 +00:00
|
|
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
|
|
import { InvoiceFormat } from '../../../ts/interfaces/common.js';
|
2025-05-30 06:31:02 +00:00
|
|
|
import { CorpusLoader } from '../../helpers/corpus.loader.js';
|
|
|
|
import { PerformanceTracker } from '../../helpers/performance.tracker.instance.js';
|
2025-05-26 05:16:32 +00:00
|
|
|
import * as path from 'path';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Test ID: STD-10
|
|
|
|
* Test Description: Country-Specific Extensions
|
|
|
|
* Priority: Medium
|
|
|
|
*
|
|
|
|
* This test validates handling of country-specific extensions to EN16931,
|
|
|
|
* including XRechnung (Germany), FatturaPA (Italy), and PEPPOL BIS variations.
|
|
|
|
*/
|
|
|
|
|
2025-05-30 06:31:02 +00:00
|
|
|
// Test 1: German XRechnung Extensions
|
|
|
|
tap.test('STD-10: German XRechnung specific requirements', async () => {
|
|
|
|
const invoice = new EInvoice();
|
2025-05-26 05:16:32 +00:00
|
|
|
|
2025-05-30 06:31:02 +00:00
|
|
|
// XRechnung specific fields
|
|
|
|
invoice.id = 'XRECHNUNG-001';
|
|
|
|
invoice.issueDate = new Date();
|
|
|
|
invoice.metadata = {
|
|
|
|
format: InvoiceFormat.XRECHNUNG,
|
|
|
|
extensions: {
|
|
|
|
'BT-DE-1': 'Payment conditions text', // German specific
|
|
|
|
'BT-DE-2': 'Buyer reference', // Leitweg-ID
|
|
|
|
'BT-DE-3': 'Project reference',
|
|
|
|
'BT-DE-4': 'Contract reference',
|
|
|
|
'BT-DE-5': 'Order reference'
|
2025-05-26 05:16:32 +00:00
|
|
|
}
|
2025-05-30 06:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Leitweg-ID validation (German routing ID)
|
|
|
|
const leitwegId = '04011000-12345-67';
|
|
|
|
const leitwegPattern = /^\d{8,12}-\d{1,30}-\d{1,2}$/;
|
|
|
|
|
|
|
|
expect(leitwegPattern.test(leitwegId)).toBeTrue();
|
|
|
|
console.log('✓ Valid Leitweg-ID format');
|
|
|
|
|
|
|
|
// Bank transfer requirements
|
|
|
|
invoice.paymentTerms = {
|
|
|
|
method: 'SEPA',
|
|
|
|
iban: 'DE89370400440532013000',
|
|
|
|
bic: 'DEUTDEFF',
|
|
|
|
reference: 'RF18539007547034'
|
|
|
|
};
|
|
|
|
|
|
|
|
// IBAN validation for Germany
|
|
|
|
const germanIbanPattern = /^DE\d{20}$/;
|
|
|
|
expect(germanIbanPattern.test(invoice.paymentTerms.iban)).toBeTrue();
|
|
|
|
console.log('✓ Valid German IBAN format');
|
|
|
|
|
|
|
|
// XRechnung profile requirements
|
|
|
|
const xrechnungProfiles = [
|
|
|
|
'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0',
|
|
|
|
'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.1',
|
|
|
|
'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.2'
|
|
|
|
];
|
|
|
|
|
|
|
|
expect(xrechnungProfiles.length).toBeGreaterThan(0);
|
|
|
|
console.log('✓ XRechnung profile identifiers defined');
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 2: Italian FatturaPA Extensions
|
|
|
|
tap.test('STD-10: Italian FatturaPA specific requirements', async () => {
|
|
|
|
// FatturaPA specific structure
|
|
|
|
const fatturapaRequirements = {
|
|
|
|
transmissionFormat: {
|
|
|
|
FormatoTrasmissione: 'FPR12', // Private B2B
|
|
|
|
CodiceDestinatario: '0000000', // 7 digits
|
|
|
|
PECDestinatario: 'pec@example.it'
|
|
|
|
},
|
|
|
|
cedentePrestatore: {
|
|
|
|
DatiAnagrafici: {
|
|
|
|
IdFiscaleIVA: {
|
|
|
|
IdPaese: 'IT',
|
|
|
|
IdCodice: '12345678901' // 11 digits
|
|
|
|
},
|
|
|
|
CodiceFiscale: 'RSSMRA80A01H501U' // 16 chars
|
2025-05-26 05:16:32 +00:00
|
|
|
}
|
2025-05-30 06:31:02 +00:00
|
|
|
},
|
|
|
|
documentType: '1.2.1' // Version
|
|
|
|
};
|
|
|
|
|
|
|
|
// Validate Italian VAT number
|
|
|
|
const italianVATPattern = /^IT\d{11}$/;
|
|
|
|
const testVAT = 'IT' + fatturapaRequirements.cedentePrestatore.DatiAnagrafici.IdFiscaleIVA.IdCodice;
|
|
|
|
expect(italianVATPattern.test(testVAT)).toBeTrue();
|
|
|
|
console.log('✓ Valid Italian VAT number format');
|
|
|
|
|
|
|
|
// Validate Codice Fiscale
|
|
|
|
const codiceFiscalePattern = /^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$/;
|
|
|
|
expect(codiceFiscalePattern.test(fatturapaRequirements.cedentePrestatore.DatiAnagrafici.CodiceFiscale)).toBeTrue();
|
|
|
|
console.log('✓ Valid Italian Codice Fiscale format');
|
|
|
|
|
|
|
|
// Validate Codice Destinatario
|
|
|
|
expect(fatturapaRequirements.transmissionFormat.CodiceDestinatario).toMatch(/^\d{7}$/);
|
|
|
|
console.log('✓ Valid Codice Destinatario format');
|
|
|
|
|
|
|
|
// Document numbering requirements
|
|
|
|
const italianInvoiceNumber = '2024/001';
|
|
|
|
expect(italianInvoiceNumber).toMatch(/^\d{4}\/\d+$/);
|
|
|
|
console.log('✓ Valid Italian invoice number format');
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 3: French Factur-X Extensions
|
|
|
|
tap.test('STD-10: French Factur-X specific requirements', async () => {
|
|
|
|
const invoice = new EInvoice();
|
|
|
|
invoice.id = 'FX-FR-001';
|
|
|
|
invoice.issueDate = new Date();
|
|
|
|
|
|
|
|
// French specific requirements
|
|
|
|
const frenchExtensions = {
|
|
|
|
siret: '12345678901234', // 14 digits
|
|
|
|
naf: '6201Z', // NAF/APE code
|
|
|
|
tvaIntracommunautaire: 'FR12345678901',
|
|
|
|
mentionsLegales: 'SARL au capital de 10000 EUR',
|
|
|
|
chorus: {
|
|
|
|
serviceCode: 'SERVICE123',
|
|
|
|
engagementNumber: 'ENG123456'
|
2025-05-26 05:16:32 +00:00
|
|
|
}
|
2025-05-30 06:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Validate SIRET (14 digits)
|
|
|
|
expect(frenchExtensions.siret).toMatch(/^\d{14}$/);
|
|
|
|
console.log('✓ Valid French SIRET format');
|
|
|
|
|
|
|
|
// Validate French VAT number
|
|
|
|
const frenchVATPattern = /^FR[0-9A-Z]{2}\d{9}$/;
|
|
|
|
expect(frenchVATPattern.test(frenchExtensions.tvaIntracommunautaire)).toBeTrue();
|
|
|
|
console.log('✓ Valid French VAT number format');
|
|
|
|
|
|
|
|
// Validate NAF/APE code
|
|
|
|
expect(frenchExtensions.naf).toMatch(/^\d{4}[A-Z]$/);
|
|
|
|
console.log('✓ Valid French NAF/APE code format');
|
|
|
|
|
|
|
|
// Chorus Pro integration (French public sector)
|
|
|
|
if (frenchExtensions.chorus.serviceCode) {
|
|
|
|
console.log('✓ Chorus Pro service code present');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 4: Belgian Extensions
|
|
|
|
tap.test('STD-10: Belgian e-invoicing extensions', async () => {
|
|
|
|
const belgianExtensions = {
|
|
|
|
merchantAgreementReference: 'BE-MERCH-001',
|
|
|
|
vatNumber: 'BE0123456789',
|
|
|
|
bancontact: {
|
|
|
|
enabled: true,
|
|
|
|
reference: 'BC123456'
|
|
|
|
},
|
|
|
|
languages: ['nl', 'fr', 'de'], // Belgium has 3 official languages
|
|
|
|
regionalCodes: {
|
|
|
|
flanders: 'VL',
|
|
|
|
wallonia: 'WA',
|
|
|
|
brussels: 'BR'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Validate Belgian VAT number (BE followed by 10 digits)
|
|
|
|
expect(belgianExtensions.vatNumber).toMatch(/^BE\d{10}$/);
|
|
|
|
console.log('✓ Valid Belgian VAT number format');
|
|
|
|
|
|
|
|
// Language requirements
|
|
|
|
expect(belgianExtensions.languages).toContain('nl');
|
|
|
|
expect(belgianExtensions.languages).toContain('fr');
|
|
|
|
console.log('✓ Supports required Belgian languages');
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 5: Nordic Countries Extensions
|
|
|
|
tap.test('STD-10: Nordic countries specific requirements', async () => {
|
|
|
|
// Swedish requirements
|
|
|
|
const swedishExtensions = {
|
|
|
|
organisationNumber: '1234567890', // 10 digits
|
|
|
|
vatNumber: 'SE123456789001',
|
|
|
|
bankgiro: '123-4567',
|
|
|
|
plusgiro: '12 34 56-7',
|
|
|
|
referenceType: 'OCR', // Swedish payment reference
|
|
|
|
ocrReference: '12345678901234567890'
|
|
|
|
};
|
|
|
|
|
|
|
|
// Norwegian requirements
|
|
|
|
const norwegianExtensions = {
|
|
|
|
organisationNumber: '123456789', // 9 digits
|
|
|
|
vatNumber: 'NO123456789MVA',
|
|
|
|
kidNumber: '1234567890123', // Payment reference
|
|
|
|
iban: 'NO9386011117947'
|
|
|
|
};
|
|
|
|
|
|
|
|
// Danish requirements
|
|
|
|
const danishExtensions = {
|
|
|
|
cvrNumber: '12345678', // 8 digits
|
|
|
|
eanLocation: '5790000123456', // 13 digits
|
|
|
|
vatNumber: 'DK12345678',
|
|
|
|
nemKonto: true // Danish public payment system
|
|
|
|
};
|
|
|
|
|
|
|
|
// Validate formats
|
|
|
|
expect(swedishExtensions.vatNumber).toMatch(/^SE\d{12}$/);
|
|
|
|
console.log('✓ Valid Swedish VAT format');
|
|
|
|
|
|
|
|
expect(norwegianExtensions.vatNumber).toMatch(/^NO\d{9}MVA$/);
|
|
|
|
console.log('✓ Valid Norwegian VAT format');
|
|
|
|
|
|
|
|
expect(danishExtensions.cvrNumber).toMatch(/^\d{8}$/);
|
|
|
|
console.log('✓ Valid Danish CVR format');
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 6: PEPPOL BIS Country Variations
|
|
|
|
tap.test('STD-10: PEPPOL BIS country-specific profiles', async () => {
|
|
|
|
const peppolProfiles = {
|
|
|
|
'PEPPOL-BIS-3.0': 'Base profile',
|
|
|
|
'PEPPOL-BIS-3.0-AU': 'Australian extension',
|
|
|
|
'PEPPOL-BIS-3.0-NZ': 'New Zealand extension',
|
|
|
|
'PEPPOL-BIS-3.0-SG': 'Singapore extension',
|
|
|
|
'PEPPOL-BIS-3.0-MY': 'Malaysian extension'
|
|
|
|
};
|
|
|
|
|
|
|
|
// Country-specific identifiers
|
|
|
|
const countryIdentifiers = {
|
|
|
|
AU: { scheme: '0151', name: 'ABN' }, // Australian Business Number
|
|
|
|
NZ: { scheme: '0088', name: 'NZBN' }, // NZ Business Number
|
|
|
|
SG: { scheme: '0195', name: 'UEN' }, // Unique Entity Number
|
|
|
|
MY: { scheme: '0199', name: 'MyBRN' } // Malaysian Business Registration
|
|
|
|
};
|
|
|
|
|
|
|
|
// Test identifier schemes
|
|
|
|
for (const [country, identifier] of Object.entries(countryIdentifiers)) {
|
|
|
|
expect(identifier.scheme).toMatch(/^\d{4}$/);
|
|
|
|
console.log(`✓ ${country}: Valid PEPPOL identifier scheme ${identifier.scheme} (${identifier.name})`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 7: Tax Regime Variations
|
|
|
|
tap.test('STD-10: Country-specific tax requirements', async () => {
|
|
|
|
const countryTaxRequirements = {
|
|
|
|
DE: {
|
|
|
|
standardRate: 19,
|
|
|
|
reducedRate: 7,
|
|
|
|
reverseCharge: 'Steuerschuldnerschaft des Leistungsempfängers'
|
|
|
|
},
|
|
|
|
FR: {
|
|
|
|
standardRate: 20,
|
|
|
|
reducedRates: [10, 5.5, 2.1],
|
|
|
|
autoliquidation: 'Autoliquidation de la TVA'
|
|
|
|
},
|
|
|
|
IT: {
|
|
|
|
standardRate: 22,
|
|
|
|
reducedRates: [10, 5, 4],
|
|
|
|
splitPayment: true // Italian split payment mechanism
|
|
|
|
},
|
|
|
|
ES: {
|
|
|
|
standardRate: 21,
|
|
|
|
reducedRates: [10, 4],
|
|
|
|
canaryIslands: 'IGIC', // Different tax system
|
|
|
|
recargo: true // Equivalence surcharge
|
2025-05-26 05:16:32 +00:00
|
|
|
}
|
2025-05-30 06:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Validate tax rates
|
|
|
|
for (const [country, tax] of Object.entries(countryTaxRequirements)) {
|
|
|
|
expect(tax.standardRate).toBeGreaterThan(0);
|
|
|
|
expect(tax.standardRate).toBeLessThan(30);
|
|
|
|
console.log(`✓ ${country}: Valid tax rates defined`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 8: Country-Specific Validation Rules
|
|
|
|
tap.test('STD-10: Country-specific validation rules', async () => {
|
|
|
|
// Test with real corpus files
|
|
|
|
const countryFiles = {
|
2025-05-30 18:08:27 +00:00
|
|
|
DE: await CorpusLoader.loadCategory('CII_XMLRECHNUNG'),
|
|
|
|
IT: await CorpusLoader.loadCategory('FATTURAPA_OFFICIAL')
|
2025-05-30 06:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// German validation rules
|
|
|
|
if (countryFiles.DE.length > 0) {
|
|
|
|
const germanFile = countryFiles.DE[0];
|
2025-05-30 18:08:27 +00:00
|
|
|
const xmlBuffer = await CorpusLoader.loadFile(germanFile.path);
|
2025-05-30 06:31:02 +00:00
|
|
|
const xmlString = xmlBuffer.toString('utf-8');
|
2025-05-26 05:16:32 +00:00
|
|
|
|
2025-05-30 06:31:02 +00:00
|
|
|
// Check for German-specific elements
|
|
|
|
const hasLeitwegId = xmlString.includes('BuyerReference') ||
|
|
|
|
xmlString.includes('BT-10');
|
|
|
|
|
|
|
|
if (hasLeitwegId) {
|
|
|
|
console.log('✓ German invoice contains buyer reference (Leitweg-ID)');
|
2025-05-26 05:16:32 +00:00
|
|
|
}
|
2025-05-30 06:31:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Italian validation rules
|
|
|
|
if (countryFiles.IT.length > 0) {
|
|
|
|
const italianFile = countryFiles.IT[0];
|
2025-05-30 18:08:27 +00:00
|
|
|
const xmlBuffer = await CorpusLoader.loadFile(italianFile.path);
|
2025-05-30 06:31:02 +00:00
|
|
|
const xmlString = xmlBuffer.toString('utf-8');
|
2025-05-26 05:16:32 +00:00
|
|
|
|
2025-05-30 06:31:02 +00:00
|
|
|
// Check for Italian-specific structure
|
|
|
|
const hasFatturaPA = xmlString.includes('FatturaElettronica') ||
|
|
|
|
xmlString.includes('FormatoTrasmissione');
|
|
|
|
|
|
|
|
if (hasFatturaPA) {
|
|
|
|
console.log('✓ Italian invoice follows FatturaPA structure');
|
2025-05-26 05:16:32 +00:00
|
|
|
}
|
2025-05-30 06:31:02 +00:00
|
|
|
}
|
2025-05-26 05:16:32 +00:00
|
|
|
|
|
|
|
// Performance summary
|
2025-05-30 06:31:02 +00:00
|
|
|
const tracker = new PerformanceTracker('country-extensions');
|
|
|
|
const perfSummary = await tracker.getSummary();
|
2025-05-26 05:16:32 +00:00
|
|
|
if (perfSummary) {
|
|
|
|
console.log('\nCountry Extensions Test Performance:');
|
2025-05-30 06:31:02 +00:00
|
|
|
console.log(` Average: ${perfSummary.average}ms`);
|
2025-05-26 05:16:32 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
tap.start();
|