641 lines
28 KiB
TypeScript
641 lines
28 KiB
TypeScript
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||
|
import * as plugins from '../../../ts/plugins.ts';
|
||
|
import { EInvoice } from '../../../ts/classes.xinvoice.ts';
|
||
|
import { CorpusLoader } from '../../helpers/corpus.loader.ts';
|
||
|
import { PerformanceTracker } from '../../helpers/performance.tracker.ts';
|
||
|
|
||
|
const testTimeout = 300000; // 5 minutes timeout for conversion processing
|
||
|
|
||
|
// CONV-03: ZUGFeRD to XRechnung Conversion
|
||
|
// Tests conversion from ZUGFeRD format to XRechnung (German CIUS of EN16931)
|
||
|
// including profile adaptation, compliance checking, and German-specific requirements
|
||
|
|
||
|
tap.test('CONV-03: ZUGFeRD to XRechnung Conversion - Basic Conversion', async (tools) => {
|
||
|
const startTime = Date.now();
|
||
|
|
||
|
try {
|
||
|
// Create a sample ZUGFeRD invoice for conversion testing
|
||
|
const sampleZugferdXml = `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<CrossIndustryInvoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
||
|
<ExchangedDocumentContext>
|
||
|
<GuidelineSpecifiedDocumentContextParameter>
|
||
|
<ID>urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p1:comfort</ID>
|
||
|
</GuidelineSpecifiedDocumentContextParameter>
|
||
|
</ExchangedDocumentContext>
|
||
|
<ExchangedDocument>
|
||
|
<ID>ZUGFERD-TO-XRECHNUNG-001</ID>
|
||
|
<TypeCode>380</TypeCode>
|
||
|
<IssueDateTime>
|
||
|
<DateTimeString format="102">20240115</DateTimeString>
|
||
|
</IssueDateTime>
|
||
|
<IncludedNote>
|
||
|
<Content>ZUGFeRD to XRechnung conversion test</Content>
|
||
|
</IncludedNote>
|
||
|
</ExchangedDocument>
|
||
|
<SupplyChainTradeTransaction>
|
||
|
<IncludedSupplyChainTradeLineItem>
|
||
|
<AssociatedDocumentLineDocument>
|
||
|
<LineID>1</LineID>
|
||
|
</AssociatedDocumentLineDocument>
|
||
|
<SpecifiedTradeProduct>
|
||
|
<Name>ZUGFeRD Test Product</Name>
|
||
|
<Description>Product for ZUGFeRD to XRechnung conversion</Description>
|
||
|
</SpecifiedTradeProduct>
|
||
|
<SpecifiedLineTradeAgreement>
|
||
|
<NetPriceProductTradePrice>
|
||
|
<ChargeAmount>50.00</ChargeAmount>
|
||
|
</NetPriceProductTradePrice>
|
||
|
</SpecifiedLineTradeAgreement>
|
||
|
<SpecifiedLineTradeDelivery>
|
||
|
<BilledQuantity unitCode="C62">2</BilledQuantity>
|
||
|
</SpecifiedLineTradeDelivery>
|
||
|
<SpecifiedLineTradeSettlement>
|
||
|
<ApplicableTradeTax>
|
||
|
<TypeCode>VAT</TypeCode>
|
||
|
<RateApplicablePercent>19.00</RateApplicablePercent>
|
||
|
</ApplicableTradeTax>
|
||
|
<SpecifiedTradeSettlementLineMonetarySummation>
|
||
|
<LineTotalAmount>100.00</LineTotalAmount>
|
||
|
</SpecifiedTradeSettlementLineMonetarySummation>
|
||
|
</SpecifiedLineTradeSettlement>
|
||
|
</IncludedSupplyChainTradeLineItem>
|
||
|
<ApplicableHeaderTradeAgreement>
|
||
|
<SellerTradeParty>
|
||
|
<Name>ZUGFeRD Test Supplier GmbH</Name>
|
||
|
<PostalTradeAddress>
|
||
|
<PostcodeCode>10115</PostcodeCode>
|
||
|
<LineOne>Friedrichstraße 123</LineOne>
|
||
|
<CityName>Berlin</CityName>
|
||
|
<CountryID>DE</CountryID>
|
||
|
</PostalTradeAddress>
|
||
|
<SpecifiedTaxRegistration>
|
||
|
<ID schemeID="VA">DE123456789</ID>
|
||
|
</SpecifiedTaxRegistration>
|
||
|
</SellerTradeParty>
|
||
|
<BuyerTradeParty>
|
||
|
<Name>XRechnung Test Customer GmbH</Name>
|
||
|
<PostalTradeAddress>
|
||
|
<PostcodeCode>80331</PostcodeCode>
|
||
|
<LineOne>Marienplatz 1</LineOne>
|
||
|
<CityName>München</CityName>
|
||
|
<CountryID>DE</CountryID>
|
||
|
</PostalTradeAddress>
|
||
|
</BuyerTradeParty>
|
||
|
</ApplicableHeaderTradeAgreement>
|
||
|
<ApplicableHeaderTradeDelivery>
|
||
|
<ActualDeliverySupplyChainEvent>
|
||
|
<OccurrenceDateTime>
|
||
|
<DateTimeString format="102">20240115</DateTimeString>
|
||
|
</OccurrenceDateTime>
|
||
|
</ActualDeliverySupplyChainEvent>
|
||
|
</ApplicableHeaderTradeDelivery>
|
||
|
<ApplicableHeaderTradeSettlement>
|
||
|
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
|
||
|
<ApplicableTradeTax>
|
||
|
<CalculatedAmount>19.00</CalculatedAmount>
|
||
|
<TypeCode>VAT</TypeCode>
|
||
|
<BasisAmount>100.00</BasisAmount>
|
||
|
<RateApplicablePercent>19.00</RateApplicablePercent>
|
||
|
</ApplicableTradeTax>
|
||
|
<SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
|
<LineTotalAmount>100.00</LineTotalAmount>
|
||
|
<TaxBasisTotalAmount>100.00</TaxBasisTotalAmount>
|
||
|
<TaxTotalAmount currencyID="EUR">19.00</TaxTotalAmount>
|
||
|
<GrandTotalAmount>119.00</GrandTotalAmount>
|
||
|
<DuePayableAmount>119.00</DuePayableAmount>
|
||
|
</SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
|
</ApplicableHeaderTradeSettlement>
|
||
|
</SupplyChainTradeTransaction>
|
||
|
</CrossIndustryInvoice>`;
|
||
|
|
||
|
const invoice = new EInvoice();
|
||
|
const parseResult = await invoice.fromXmlString(sampleZugferdXml);
|
||
|
expect(parseResult).toBeTruthy();
|
||
|
|
||
|
// Test ZUGFeRD to XRechnung conversion if supported
|
||
|
if (typeof invoice.convertTo === 'function') {
|
||
|
tools.log('Testing ZUGFeRD to XRechnung conversion...');
|
||
|
|
||
|
try {
|
||
|
const conversionResult = await invoice.convertTo('XRECHNUNG');
|
||
|
|
||
|
if (conversionResult) {
|
||
|
tools.log('✓ ZUGFeRD to XRechnung conversion completed');
|
||
|
|
||
|
// Verify the converted format
|
||
|
const convertedXml = await conversionResult.toXmlString();
|
||
|
expect(convertedXml).toBeTruthy();
|
||
|
expect(convertedXml.length).toBeGreaterThan(100);
|
||
|
|
||
|
// Check for XRechnung format characteristics
|
||
|
const xrechnungChecks = {
|
||
|
hasXrechnungCustomization: convertedXml.includes('urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung') ||
|
||
|
convertedXml.includes('XRechnung') ||
|
||
|
convertedXml.includes('xrechnung'),
|
||
|
hasUblNamespace: convertedXml.includes('urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'),
|
||
|
hasPeppolProfile: convertedXml.includes('urn:fdc:peppol.eu:2017:poacc:billing:01:1.0'),
|
||
|
hasOriginalId: convertedXml.includes('ZUGFERD-TO-XRECHNUNG-001'),
|
||
|
hasGermanVat: convertedXml.includes('DE123456789'),
|
||
|
hasEurocurrency: convertedXml.includes('EUR')
|
||
|
};
|
||
|
|
||
|
tools.log('XRechnung Format Verification:');
|
||
|
tools.log(` XRechnung Customization: ${xrechnungChecks.hasXrechnungCustomization}`);
|
||
|
tools.log(` UBL Namespace: ${xrechnungChecks.hasUblNamespace}`);
|
||
|
tools.log(` PEPPOL Profile: ${xrechnungChecks.hasPeppolProfile}`);
|
||
|
tools.log(` Original ID preserved: ${xrechnungChecks.hasOriginalId}`);
|
||
|
tools.log(` German VAT preserved: ${xrechnungChecks.hasGermanVat}`);
|
||
|
tools.log(` Euro currency preserved: ${xrechnungChecks.hasEurourrency}`);
|
||
|
|
||
|
if (xrechnungChecks.hasUblNamespace || xrechnungChecks.hasXrechnungCustomization) {
|
||
|
tools.log('✓ Valid XRechnung format structure detected');
|
||
|
} else {
|
||
|
tools.log('⚠ XRechnung format structure not clearly detected');
|
||
|
}
|
||
|
|
||
|
// Validate the converted invoice
|
||
|
try {
|
||
|
const validationResult = await conversionResult.validate();
|
||
|
if (validationResult.valid) {
|
||
|
tools.log('✓ Converted XRechnung invoice passes validation');
|
||
|
} else {
|
||
|
tools.log(`⚠ Converted XRechnung validation issues: ${validationResult.errors?.length || 0} errors`);
|
||
|
if (validationResult.errors && validationResult.errors.length > 0) {
|
||
|
tools.log(` First error: ${validationResult.errors[0].message}`);
|
||
|
}
|
||
|
}
|
||
|
} catch (validationError) {
|
||
|
tools.log(`⚠ Converted XRechnung validation failed: ${validationError.message}`);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
tools.log('⚠ ZUGFeRD to XRechnung conversion returned no result');
|
||
|
}
|
||
|
|
||
|
} catch (conversionError) {
|
||
|
tools.log(`⚠ ZUGFeRD to XRechnung conversion failed: ${conversionError.message}`);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
tools.log('⚠ ZUGFeRD to XRechnung conversion not supported (convertTo method not available)');
|
||
|
|
||
|
// Test alternative conversion approach if available
|
||
|
if (typeof invoice.toXRechnung === 'function') {
|
||
|
try {
|
||
|
const xrechnungResult = await invoice.toXRechnung();
|
||
|
if (xrechnungResult) {
|
||
|
tools.log('✓ Alternative ZUGFeRD to XRechnung conversion successful');
|
||
|
}
|
||
|
} catch (alternativeError) {
|
||
|
tools.log(`⚠ Alternative conversion failed: ${alternativeError.message}`);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} catch (error) {
|
||
|
tools.log(`Basic ZUGFeRD to XRechnung conversion test failed: ${error.message}`);
|
||
|
}
|
||
|
|
||
|
const duration = Date.now() - startTime;
|
||
|
PerformanceTracker.recordMetric('conversion-zugferd-to-xrechnung-basic', duration);
|
||
|
});
|
||
|
|
||
|
tap.test('CONV-03: ZUGFeRD to XRechnung Conversion - Profile Adaptation', async (tools) => {
|
||
|
const startTime = Date.now();
|
||
|
|
||
|
// Test conversion of different ZUGFeRD profiles to XRechnung
|
||
|
const profileTests = [
|
||
|
{
|
||
|
name: 'ZUGFeRD MINIMUM to XRechnung',
|
||
|
zugferdXml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<CrossIndustryInvoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
||
|
<ExchangedDocumentContext>
|
||
|
<GuidelineSpecifiedDocumentContextParameter>
|
||
|
<ID>urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p1:minimum</ID>
|
||
|
</GuidelineSpecifiedDocumentContextParameter>
|
||
|
</ExchangedDocumentContext>
|
||
|
<ExchangedDocument>
|
||
|
<ID>MIN-TO-XRECHNUNG-001</ID>
|
||
|
<TypeCode>380</TypeCode>
|
||
|
<IssueDateTime>
|
||
|
<DateTimeString format="102">20240115</DateTimeString>
|
||
|
</IssueDateTime>
|
||
|
</ExchangedDocument>
|
||
|
<SupplyChainTradeTransaction>
|
||
|
<ApplicableHeaderTradeSettlement>
|
||
|
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
|
||
|
<SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
|
<DuePayableAmount>119.00</DuePayableAmount>
|
||
|
</SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
|
</ApplicableHeaderTradeSettlement>
|
||
|
</SupplyChainTradeTransaction>
|
||
|
</CrossIndustryInvoice>`
|
||
|
},
|
||
|
{
|
||
|
name: 'ZUGFeRD BASIC to XRechnung',
|
||
|
zugferdXml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<CrossIndustryInvoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
||
|
<ExchangedDocumentContext>
|
||
|
<GuidelineSpecifiedDocumentContextParameter>
|
||
|
<ID>urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p1:basic</ID>
|
||
|
</GuidelineSpecifiedDocumentContextParameter>
|
||
|
</ExchangedDocumentContext>
|
||
|
<ExchangedDocument>
|
||
|
<ID>BASIC-TO-XRECHNUNG-001</ID>
|
||
|
<TypeCode>380</TypeCode>
|
||
|
<IssueDateTime>
|
||
|
<DateTimeString format="102">20240115</DateTimeString>
|
||
|
</IssueDateTime>
|
||
|
</ExchangedDocument>
|
||
|
<SupplyChainTradeTransaction>
|
||
|
<ApplicableHeaderTradeAgreement>
|
||
|
<SellerTradeParty>
|
||
|
<Name>BASIC Supplier GmbH</Name>
|
||
|
</SellerTradeParty>
|
||
|
<BuyerTradeParty>
|
||
|
<Name>BASIC Customer GmbH</Name>
|
||
|
</BuyerTradeParty>
|
||
|
</ApplicableHeaderTradeAgreement>
|
||
|
<ApplicableHeaderTradeSettlement>
|
||
|
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
|
||
|
<SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
|
<TaxBasisTotalAmount>100.00</TaxBasisTotalAmount>
|
||
|
<TaxTotalAmount currencyID="EUR">19.00</TaxTotalAmount>
|
||
|
<GrandTotalAmount>119.00</GrandTotalAmount>
|
||
|
<DuePayableAmount>119.00</DuePayableAmount>
|
||
|
</SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
|
</ApplicableHeaderTradeSettlement>
|
||
|
</SupplyChainTradeTransaction>
|
||
|
</CrossIndustryInvoice>`
|
||
|
},
|
||
|
{
|
||
|
name: 'ZUGFeRD COMFORT to XRechnung',
|
||
|
zugferdXml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<CrossIndustryInvoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
||
|
<ExchangedDocumentContext>
|
||
|
<GuidelineSpecifiedDocumentContextParameter>
|
||
|
<ID>urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p1:comfort</ID>
|
||
|
</GuidelineSpecifiedDocumentContextParameter>
|
||
|
</ExchangedDocumentContext>
|
||
|
<ExchangedDocument>
|
||
|
<ID>COMFORT-TO-XRECHNUNG-001</ID>
|
||
|
<TypeCode>380</TypeCode>
|
||
|
<IssueDateTime>
|
||
|
<DateTimeString format="102">20240115</DateTimeString>
|
||
|
</IssueDateTime>
|
||
|
</ExchangedDocument>
|
||
|
<SupplyChainTradeTransaction>
|
||
|
<IncludedSupplyChainTradeLineItem>
|
||
|
<AssociatedDocumentLineDocument>
|
||
|
<LineID>1</LineID>
|
||
|
</AssociatedDocumentLineDocument>
|
||
|
<SpecifiedTradeProduct>
|
||
|
<Name>COMFORT Test Product</Name>
|
||
|
</SpecifiedTradeProduct>
|
||
|
<SpecifiedLineTradeSettlement>
|
||
|
<SpecifiedTradeSettlementLineMonetarySummation>
|
||
|
<LineTotalAmount>100.00</LineTotalAmount>
|
||
|
</SpecifiedTradeSettlementLineMonetarySummation>
|
||
|
</SpecifiedLineTradeSettlement>
|
||
|
</IncludedSupplyChainTradeLineItem>
|
||
|
<ApplicableHeaderTradeSettlement>
|
||
|
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
|
||
|
<SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
|
<LineTotalAmount>100.00</LineTotalAmount>
|
||
|
<TaxBasisTotalAmount>100.00</TaxBasisTotalAmount>
|
||
|
<TaxTotalAmount currencyID="EUR">19.00</TaxTotalAmount>
|
||
|
<GrandTotalAmount>119.00</GrandTotalAmount>
|
||
|
<DuePayableAmount>119.00</DuePayableAmount>
|
||
|
</SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
|
</ApplicableHeaderTradeSettlement>
|
||
|
</SupplyChainTradeTransaction>
|
||
|
</CrossIndustryInvoice>`
|
||
|
}
|
||
|
];
|
||
|
|
||
|
for (const profileTest of profileTests) {
|
||
|
tools.log(`Testing ${profileTest.name}...`);
|
||
|
|
||
|
try {
|
||
|
const invoice = new EInvoice();
|
||
|
const parseResult = await invoice.fromXmlString(profileTest.zugferdXml);
|
||
|
|
||
|
if (parseResult) {
|
||
|
if (typeof invoice.convertTo === 'function') {
|
||
|
const conversionResult = await invoice.convertTo('XRECHNUNG');
|
||
|
|
||
|
if (conversionResult) {
|
||
|
tools.log(`✓ ${profileTest.name} conversion completed`);
|
||
|
|
||
|
const convertedXml = await conversionResult.toXmlString();
|
||
|
|
||
|
// Check profile-specific adaptations
|
||
|
const profileAdaptations = {
|
||
|
hasXrechnungProfile: convertedXml.includes('xrechnung') ||
|
||
|
convertedXml.includes('XRechnung'),
|
||
|
retainsOriginalId: convertedXml.includes('TO-XRECHNUNG-001'),
|
||
|
hasRequiredStructure: convertedXml.includes('<Invoice') ||
|
||
|
convertedXml.includes('<CrossIndustryInvoice'),
|
||
|
hasGermanContext: convertedXml.includes('urn:xoev-de:kosit') ||
|
||
|
convertedXml.includes('xrechnung')
|
||
|
};
|
||
|
|
||
|
tools.log(` Profile adaptation results:`);
|
||
|
tools.log(` XRechnung profile: ${profileAdaptations.hasXrechnungProfile}`);
|
||
|
tools.log(` Original ID retained: ${profileAdaptations.retainsOriginalId}`);
|
||
|
tools.log(` Required structure: ${profileAdaptations.hasRequiredStructure}`);
|
||
|
tools.log(` German context: ${profileAdaptations.hasGermanContext}`);
|
||
|
|
||
|
if (profileAdaptations.hasRequiredStructure && profileAdaptations.retainsOriginalId) {
|
||
|
tools.log(` ✓ Successful profile adaptation`);
|
||
|
} else {
|
||
|
tools.log(` ⚠ Profile adaptation issues detected`);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
tools.log(`⚠ ${profileTest.name} conversion returned no result`);
|
||
|
}
|
||
|
} else {
|
||
|
tools.log(`⚠ ${profileTest.name} conversion not supported`);
|
||
|
}
|
||
|
} else {
|
||
|
tools.log(`⚠ ${profileTest.name} ZUGFeRD parsing failed`);
|
||
|
}
|
||
|
|
||
|
} catch (error) {
|
||
|
tools.log(`✗ ${profileTest.name} test failed: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const duration = Date.now() - startTime;
|
||
|
PerformanceTracker.recordMetric('conversion-zugferd-to-xrechnung-profiles', duration);
|
||
|
});
|
||
|
|
||
|
tap.test('CONV-03: ZUGFeRD to XRechnung Conversion - German Compliance', async (tools) => {
|
||
|
const startTime = Date.now();
|
||
|
|
||
|
// Test German-specific compliance requirements for XRechnung
|
||
|
const germanComplianceXml = `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<CrossIndustryInvoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
||
|
<ExchangedDocumentContext>
|
||
|
<GuidelineSpecifiedDocumentContextParameter>
|
||
|
<ID>urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p1:comfort</ID>
|
||
|
</GuidelineSpecifiedDocumentContextParameter>
|
||
|
</ExchangedDocumentContext>
|
||
|
<ExchangedDocument>
|
||
|
<ID>DE-COMPLIANCE-001</ID>
|
||
|
<TypeCode>380</TypeCode>
|
||
|
<IssueDateTime>
|
||
|
<DateTimeString format="102">20240115</DateTimeString>
|
||
|
</IssueDateTime>
|
||
|
</ExchangedDocument>
|
||
|
<SupplyChainTradeTransaction>
|
||
|
<ApplicableHeaderTradeAgreement>
|
||
|
<BuyerReference>BUYER-REF-12345</BuyerReference>
|
||
|
<SellerTradeParty>
|
||
|
<Name>Deutsche Lieferant GmbH</Name>
|
||
|
<PostalTradeAddress>
|
||
|
<PostcodeCode>10115</PostcodeCode>
|
||
|
<LineOne>Unter den Linden 1</LineOne>
|
||
|
<CityName>Berlin</CityName>
|
||
|
<CountryID>DE</CountryID>
|
||
|
</PostalTradeAddress>
|
||
|
<SpecifiedTaxRegistration>
|
||
|
<ID schemeID="VA">DE987654321</ID>
|
||
|
</SpecifiedTaxRegistration>
|
||
|
</SellerTradeParty>
|
||
|
<BuyerTradeParty>
|
||
|
<Name>Deutscher Kunde GmbH</Name>
|
||
|
<PostalTradeAddress>
|
||
|
<PostcodeCode>80331</PostcodeCode>
|
||
|
<LineOne>Maximilianstraße 1</LineOne>
|
||
|
<CityName>München</CityName>
|
||
|
<CountryID>DE</CountryID>
|
||
|
</PostalTradeAddress>
|
||
|
</BuyerTradeParty>
|
||
|
</ApplicableHeaderTradeAgreement>
|
||
|
<ApplicableHeaderTradeSettlement>
|
||
|
<PaymentReference>PAYMENT-REF-67890</PaymentReference>
|
||
|
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
|
||
|
<ApplicableTradeTax>
|
||
|
<CalculatedAmount>19.00</CalculatedAmount>
|
||
|
<TypeCode>VAT</TypeCode>
|
||
|
<BasisAmount>100.00</BasisAmount>
|
||
|
<RateApplicablePercent>19.00</RateApplicablePercent>
|
||
|
<CategoryCode>S</CategoryCode>
|
||
|
</ApplicableTradeTax>
|
||
|
<SpecifiedTradePaymentTerms>
|
||
|
<Description>Zahlbar innerhalb 30 Tagen ohne Abzug</Description>
|
||
|
<DueDateDateTime>
|
||
|
<DateTimeString format="102">20240214</DateTimeString>
|
||
|
</DueDateDateTime>
|
||
|
</SpecifiedTradePaymentTerms>
|
||
|
<SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
|
<LineTotalAmount>100.00</LineTotalAmount>
|
||
|
<TaxBasisTotalAmount>100.00</TaxBasisTotalAmount>
|
||
|
<TaxTotalAmount currencyID="EUR">19.00</TaxTotalAmount>
|
||
|
<GrandTotalAmount>119.00</GrandTotalAmount>
|
||
|
<DuePayableAmount>119.00</DuePayableAmount>
|
||
|
</SpecifiedTradeSettlementHeaderMonetarySummation>
|
||
|
</ApplicableHeaderTradeSettlement>
|
||
|
</SupplyChainTradeTransaction>
|
||
|
</CrossIndustryInvoice>`;
|
||
|
|
||
|
try {
|
||
|
const invoice = new EInvoice();
|
||
|
const parseResult = await invoice.fromXmlString(germanComplianceXml);
|
||
|
|
||
|
if (parseResult) {
|
||
|
tools.log('Testing German compliance requirements during conversion...');
|
||
|
|
||
|
if (typeof invoice.convertTo === 'function') {
|
||
|
const conversionResult = await invoice.convertTo('XRECHNUNG');
|
||
|
|
||
|
if (conversionResult) {
|
||
|
const convertedXml = await conversionResult.toXmlString();
|
||
|
|
||
|
// Check German-specific compliance requirements
|
||
|
const germanComplianceChecks = {
|
||
|
hasBuyerReference: convertedXml.includes('BUYER-REF-12345'),
|
||
|
hasPaymentReference: convertedXml.includes('PAYMENT-REF-67890'),
|
||
|
hasGermanVatNumber: convertedXml.includes('DE987654321'),
|
||
|
hasGermanAddresses: convertedXml.includes('Berlin') && convertedXml.includes('München'),
|
||
|
hasGermanPostCodes: convertedXml.includes('10115') && convertedXml.includes('80331'),
|
||
|
hasEuroCurrency: convertedXml.includes('EUR'),
|
||
|
hasStandardVatRate: convertedXml.includes('19.00'),
|
||
|
hasPaymentTerms: convertedXml.includes('30 Tagen') || convertedXml.includes('payment')
|
||
|
};
|
||
|
|
||
|
tools.log('German Compliance Verification:');
|
||
|
tools.log(` Buyer reference preserved: ${germanComplianceChecks.hasBuyerReference}`);
|
||
|
tools.log(` Payment reference preserved: ${germanComplianceChecks.hasPaymentReference}`);
|
||
|
tools.log(` German VAT number preserved: ${germanComplianceChecks.hasGermanVatNumber}`);
|
||
|
tools.log(` German addresses preserved: ${germanComplianceChecks.hasGermanAddresses}`);
|
||
|
tools.log(` German postal codes preserved: ${germanComplianceChecks.hasGermanPostCodes}`);
|
||
|
tools.log(` Euro currency preserved: ${germanComplianceChecks.hasEuroCurrency}`);
|
||
|
tools.log(` Standard VAT rate preserved: ${germanComplianceChecks.hasStandardVatRate}`);
|
||
|
tools.log(` Payment terms preserved: ${germanComplianceChecks.hasPaymentTerms}`);
|
||
|
|
||
|
const complianceScore = Object.values(germanComplianceChecks).filter(Boolean).length;
|
||
|
const totalChecks = Object.values(germanComplianceChecks).length;
|
||
|
const compliancePercentage = (complianceScore / totalChecks) * 100;
|
||
|
|
||
|
tools.log(`German compliance score: ${complianceScore}/${totalChecks} (${compliancePercentage.toFixed(1)}%)`);
|
||
|
|
||
|
if (compliancePercentage >= 80) {
|
||
|
tools.log('✓ Good German compliance maintained');
|
||
|
} else {
|
||
|
tools.log('⚠ German compliance issues detected');
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
tools.log('⚠ German compliance conversion returned no result');
|
||
|
}
|
||
|
} else {
|
||
|
tools.log('⚠ German compliance conversion not supported');
|
||
|
}
|
||
|
} else {
|
||
|
tools.log('⚠ German compliance test - ZUGFeRD parsing failed');
|
||
|
}
|
||
|
|
||
|
} catch (error) {
|
||
|
tools.log(`German compliance test failed: ${error.message}`);
|
||
|
}
|
||
|
|
||
|
const duration = Date.now() - startTime;
|
||
|
PerformanceTracker.recordMetric('conversion-zugferd-to-xrechnung-german-compliance', duration);
|
||
|
});
|
||
|
|
||
|
tap.test('CONV-03: ZUGFeRD to XRechnung Conversion - Corpus Testing', { timeout: testTimeout }, async (tools) => {
|
||
|
const startTime = Date.now();
|
||
|
|
||
|
let processedFiles = 0;
|
||
|
let successfulConversions = 0;
|
||
|
let conversionErrors = 0;
|
||
|
let totalConversionTime = 0;
|
||
|
|
||
|
try {
|
||
|
const zugferdFiles = await CorpusLoader.getFiles('ZUGFERD_V2');
|
||
|
tools.log(`Testing ZUGFeRD to XRechnung conversion with ${zugferdFiles.length} ZUGFeRD files`);
|
||
|
|
||
|
if (zugferdFiles.length === 0) {
|
||
|
tools.log('⚠ No ZUGFeRD files found in corpus for conversion testing');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Process a subset of files for performance
|
||
|
const filesToProcess = zugferdFiles.slice(0, Math.min(6, zugferdFiles.length));
|
||
|
|
||
|
for (const filePath of filesToProcess) {
|
||
|
const fileName = plugins.path.basename(filePath);
|
||
|
const fileConversionStart = Date.now();
|
||
|
|
||
|
try {
|
||
|
processedFiles++;
|
||
|
|
||
|
const invoice = new EInvoice();
|
||
|
const parseResult = await invoice.fromFile(filePath);
|
||
|
|
||
|
if (parseResult) {
|
||
|
// Attempt conversion to XRechnung
|
||
|
if (typeof invoice.convertTo === 'function') {
|
||
|
const conversionResult = await invoice.convertTo('XRECHNUNG');
|
||
|
|
||
|
const fileConversionTime = Date.now() - fileConversionStart;
|
||
|
totalConversionTime += fileConversionTime;
|
||
|
|
||
|
if (conversionResult) {
|
||
|
successfulConversions++;
|
||
|
|
||
|
tools.log(`✓ ${fileName}: Converted to XRechnung (${fileConversionTime}ms)`);
|
||
|
|
||
|
// Quick validation of converted content
|
||
|
const convertedXml = await conversionResult.toXmlString();
|
||
|
if (convertedXml && convertedXml.length > 100) {
|
||
|
tools.log(` Converted content length: ${convertedXml.length} chars`);
|
||
|
|
||
|
// Check for XRechnung characteristics
|
||
|
const xrechnungMarkers = {
|
||
|
hasXrechnungId: convertedXml.includes('xrechnung') || convertedXml.includes('XRechnung'),
|
||
|
hasUblStructure: convertedXml.includes('Invoice') && convertedXml.includes('urn:oasis:names'),
|
||
|
hasGermanElements: convertedXml.includes('DE') || convertedXml.includes('EUR')
|
||
|
};
|
||
|
|
||
|
if (Object.values(xrechnungMarkers).some(Boolean)) {
|
||
|
tools.log(` ✓ XRechnung characteristics detected`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
conversionErrors++;
|
||
|
tools.log(`⚠ ${fileName}: Conversion returned no result`);
|
||
|
}
|
||
|
} else {
|
||
|
conversionErrors++;
|
||
|
tools.log(`⚠ ${fileName}: Conversion method not available`);
|
||
|
}
|
||
|
} else {
|
||
|
conversionErrors++;
|
||
|
tools.log(`⚠ ${fileName}: Failed to parse original ZUGFeRD`);
|
||
|
}
|
||
|
|
||
|
} catch (error) {
|
||
|
conversionErrors++;
|
||
|
const fileConversionTime = Date.now() - fileConversionStart;
|
||
|
totalConversionTime += fileConversionTime;
|
||
|
|
||
|
tools.log(`✗ ${fileName}: Conversion failed - ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Calculate statistics
|
||
|
const successRate = processedFiles > 0 ? (successfulConversions / processedFiles) * 100 : 0;
|
||
|
const averageConversionTime = processedFiles > 0 ? totalConversionTime / processedFiles : 0;
|
||
|
|
||
|
tools.log(`\nZUGFeRD to XRechnung Conversion Summary:`);
|
||
|
tools.log(`- Files processed: ${processedFiles}`);
|
||
|
tools.log(`- Successful conversions: ${successfulConversions} (${successRate.toFixed(1)}%)`);
|
||
|
tools.log(`- Conversion errors: ${conversionErrors}`);
|
||
|
tools.log(`- Average conversion time: ${averageConversionTime.toFixed(1)}ms`);
|
||
|
|
||
|
// Performance expectations
|
||
|
if (processedFiles > 0) {
|
||
|
expect(averageConversionTime).toBeLessThan(4000); // 4 seconds max per file
|
||
|
}
|
||
|
|
||
|
// We expect some conversions to work
|
||
|
if (processedFiles > 0) {
|
||
|
expect(successRate).toBeGreaterThan(0); // At least one conversion should work
|
||
|
}
|
||
|
|
||
|
} catch (error) {
|
||
|
tools.log(`ZUGFeRD to XRechnung corpus testing failed: ${error.message}`);
|
||
|
throw error;
|
||
|
}
|
||
|
|
||
|
const totalDuration = Date.now() - startTime;
|
||
|
PerformanceTracker.recordMetric('conversion-zugferd-to-xrechnung-corpus', totalDuration);
|
||
|
|
||
|
tools.log(`ZUGFeRD to XRechnung corpus testing completed in ${totalDuration}ms`);
|
||
|
});
|
||
|
|
||
|
tap.test('CONV-03: Performance Summary', async (tools) => {
|
||
|
const operations = [
|
||
|
'conversion-zugferd-to-xrechnung-basic',
|
||
|
'conversion-zugferd-to-xrechnung-profiles',
|
||
|
'conversion-zugferd-to-xrechnung-german-compliance',
|
||
|
'conversion-zugferd-to-xrechnung-corpus'
|
||
|
];
|
||
|
|
||
|
tools.log(`\n=== ZUGFeRD to XRechnung Conversion Performance Summary ===`);
|
||
|
|
||
|
for (const operation of operations) {
|
||
|
const summary = await PerformanceTracker.getSummary(operation);
|
||
|
if (summary) {
|
||
|
tools.log(`${operation}:`);
|
||
|
tools.log(` avg=${summary.average}ms, min=${summary.min}ms, max=${summary.max}ms, p95=${summary.p95}ms`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tools.log(`\nZUGFeRD to XRechnung conversion testing completed.`);
|
||
|
});
|