595 lines
23 KiB
TypeScript
595 lines
23 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import * as plugins from '../../../ts/plugins.js';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
import { CorpusLoader } from '../../helpers/corpus.loader.js';
|
|
|
|
// CONV-04: Verify accurate field mapping during format conversion
|
|
// This test ensures data is correctly transferred between different formats
|
|
|
|
const testTimeout = 300000; // 5 minutes timeout
|
|
|
|
tap.test('CONV-04: Field Mapping - Basic field mapping UBL to CII', async () => {
|
|
// UBL invoice with comprehensive fields
|
|
const ublInvoice = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
|
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
|
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
|
<cbc:UBLVersionID>2.1</cbc:UBLVersionID>
|
|
<cbc:CustomizationID>urn:cen.eu:en16931:2017</cbc:CustomizationID>
|
|
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
|
|
<cbc:ID>FIELD-MAP-001</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:DueDate>2025-02-25</cbc:DueDate>
|
|
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
|
<cbc:Note>Field mapping test invoice</cbc:Note>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>Test Supplier Ltd</cbc:Name>
|
|
</cac:PartyName>
|
|
<cac:PostalAddress>
|
|
<cbc:StreetName>Main Street</cbc:StreetName>
|
|
<cbc:BuildingNumber>123</cbc:BuildingNumber>
|
|
<cbc:CityName>Copenhagen</cbc:CityName>
|
|
<cbc:PostalZone>1050</cbc:PostalZone>
|
|
<cac:Country>
|
|
<cbc:IdentificationCode>DK</cbc:IdentificationCode>
|
|
</cac:Country>
|
|
</cac:PostalAddress>
|
|
<cac:PartyTaxScheme>
|
|
<cbc:CompanyID>DK12345678</cbc:CompanyID>
|
|
<cac:TaxScheme>
|
|
<cbc:ID>VAT</cbc:ID>
|
|
</cac:TaxScheme>
|
|
</cac:PartyTaxScheme>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:AccountingCustomerParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>Test Customer GmbH</cbc:Name>
|
|
</cac:PartyName>
|
|
<cac:PostalAddress>
|
|
<cbc:StreetName>Bahnhofstraße</cbc:StreetName>
|
|
<cbc:BuildingNumber>456</cbc:BuildingNumber>
|
|
<cbc:CityName>Berlin</cbc:CityName>
|
|
<cbc:PostalZone>10115</cbc:PostalZone>
|
|
<cac:Country>
|
|
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
|
|
</cac:Country>
|
|
</cac:PostalAddress>
|
|
</cac:Party>
|
|
</cac:AccountingCustomerParty>
|
|
<cac:InvoiceLine>
|
|
<cbc:ID>1</cbc:ID>
|
|
<cbc:InvoicedQuantity unitCode="C62">10</cbc:InvoicedQuantity>
|
|
<cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount>
|
|
<cac:Item>
|
|
<cbc:Name>Test Product</cbc:Name>
|
|
<cbc:Description>Product for field mapping test</cbc:Description>
|
|
</cac:Item>
|
|
<cac:Price>
|
|
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
|
|
</cac:Price>
|
|
</cac:InvoiceLine>
|
|
<cac:LegalMonetaryTotal>
|
|
<cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount>
|
|
<cbc:TaxExclusiveAmount currencyID="EUR">1000.00</cbc:TaxExclusiveAmount>
|
|
<cbc:TaxInclusiveAmount currencyID="EUR">1190.00</cbc:TaxInclusiveAmount>
|
|
<cbc:PayableAmount currencyID="EUR">1190.00</cbc:PayableAmount>
|
|
</cac:LegalMonetaryTotal>
|
|
</Invoice>`;
|
|
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadXml(ublInvoice);
|
|
|
|
// Check if key fields are loaded correctly
|
|
console.log('Testing UBL to CII field mapping...');
|
|
|
|
// Basic fields
|
|
expect(einvoice.id).toEqual('FIELD-MAP-001');
|
|
expect(einvoice.currency).toEqual('EUR');
|
|
expect(einvoice.date).toBeTypeofNumber();
|
|
expect(einvoice.notes).toContain('Field mapping test invoice');
|
|
|
|
// Party information
|
|
expect(einvoice.from.name).toEqual('Test Supplier Ltd');
|
|
expect(einvoice.from.address.streetName).toEqual('Main Street');
|
|
expect(einvoice.from.address.city).toEqual('Copenhagen');
|
|
expect(einvoice.from.address.postalCode).toEqual('1050');
|
|
expect(einvoice.from.address.countryCode).toEqual('DK');
|
|
|
|
expect(einvoice.to.name).toEqual('Test Customer GmbH');
|
|
expect(einvoice.to.address.city).toEqual('Berlin');
|
|
|
|
// Line items
|
|
expect(einvoice.items.length).toEqual(1);
|
|
expect(einvoice.items[0].name).toEqual('Test Product');
|
|
expect(einvoice.items[0].unitQuantity).toEqual(10);
|
|
expect(einvoice.items[0].unitNetPrice).toEqual(100);
|
|
|
|
// Convert to CII
|
|
const ciiXml = await einvoice.toXmlString('cii');
|
|
|
|
// Verify CII contains mapped fields
|
|
console.log('Verifying CII output contains mapped fields...');
|
|
expect(ciiXml).toContain('FIELD-MAP-001');
|
|
expect(ciiXml).toContain('Test Supplier Ltd');
|
|
expect(ciiXml).toContain('Test Customer GmbH');
|
|
expect(ciiXml).toContain('Test Product');
|
|
expect(ciiXml).toContain('EUR');
|
|
expect(ciiXml).toContain('1000.00');
|
|
|
|
console.log('✓ Basic field mapping test passed');
|
|
} catch (error) {
|
|
console.error('Field mapping test failed:', error);
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
tap.test('CONV-04: Field Mapping - Complex nested field mapping', async () => {
|
|
// CII invoice with nested structures
|
|
const ciiInvoice = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
|
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
|
|
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
|
|
<rsm:ExchangedDocumentContext>
|
|
<ram:GuidelineSpecifiedDocumentContextParameter>
|
|
<ram:ID>urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p1:basic</ram:ID>
|
|
</ram:GuidelineSpecifiedDocumentContextParameter>
|
|
</rsm:ExchangedDocumentContext>
|
|
<rsm:ExchangedDocument>
|
|
<ram:ID>NESTED-MAP-001</ram:ID>
|
|
<ram:TypeCode>380</ram:TypeCode>
|
|
<ram:IssueDateTime>
|
|
<udt:DateTimeString format="102">20250125</udt:DateTimeString>
|
|
</ram:IssueDateTime>
|
|
<ram:IncludedNote>
|
|
<ram:Content>Complex nested structure test</ram:Content>
|
|
</ram:IncludedNote>
|
|
</rsm:ExchangedDocument>
|
|
<rsm:SupplyChainTradeTransaction>
|
|
<ram:IncludedSupplyChainTradeLineItem>
|
|
<ram:AssociatedDocumentLineDocument>
|
|
<ram:LineID>1</ram:LineID>
|
|
</ram:AssociatedDocumentLineDocument>
|
|
<ram:SpecifiedTradeProduct>
|
|
<ram:SellerAssignedID>PROD-001</ram:SellerAssignedID>
|
|
<ram:GlobalID schemeID="0160">1234567890123</ram:GlobalID>
|
|
<ram:Name>Nested Product</ram:Name>
|
|
<ram:Description>Product with nested attributes</ram:Description>
|
|
</ram:SpecifiedTradeProduct>
|
|
<ram:SpecifiedLineTradeAgreement>
|
|
<ram:NetPriceProductTradePrice>
|
|
<ram:ChargeAmount>120.00</ram:ChargeAmount>
|
|
</ram:NetPriceProductTradePrice>
|
|
</ram:SpecifiedLineTradeAgreement>
|
|
<ram:SpecifiedLineTradeDelivery>
|
|
<ram:BilledQuantity unitCode="C62">9</ram:BilledQuantity>
|
|
</ram:SpecifiedLineTradeDelivery>
|
|
<ram:SpecifiedLineTradeSettlement>
|
|
<ram:ApplicableTradeTax>
|
|
<ram:TypeCode>VAT</ram:TypeCode>
|
|
<ram:CategoryCode>S</ram:CategoryCode>
|
|
<ram:RateApplicablePercent>20</ram:RateApplicablePercent>
|
|
</ram:ApplicableTradeTax>
|
|
<ram:SpecifiedLineTradeSettlementMonetarySummation>
|
|
<ram:LineTotalAmount>1080.00</ram:LineTotalAmount>
|
|
</ram:SpecifiedLineTradeSettlementMonetarySummation>
|
|
</ram:SpecifiedLineTradeSettlement>
|
|
</ram:IncludedSupplyChainTradeLineItem>
|
|
<ram:ApplicableHeaderTradeAgreement>
|
|
<ram:SellerTradeParty>
|
|
<ram:Name>Nested Seller Corp</ram:Name>
|
|
<ram:PostalTradeAddress>
|
|
<ram:LineOne>Complex Street 789</ram:LineOne>
|
|
<ram:CityName>Amsterdam</ram:CityName>
|
|
<ram:PostcodeCode>1011</ram:PostcodeCode>
|
|
<ram:CountryID>NL</ram:CountryID>
|
|
</ram:PostalTradeAddress>
|
|
</ram:SellerTradeParty>
|
|
<ram:BuyerTradeParty>
|
|
<ram:Name>Nested Buyer Inc</ram:Name>
|
|
<ram:PostalTradeAddress>
|
|
<ram:LineOne>Simple Road 321</ram:LineOne>
|
|
<ram:CityName>Paris</ram:CityName>
|
|
<ram:PostcodeCode>75001</ram:PostcodeCode>
|
|
<ram:CountryID>FR</ram:CountryID>
|
|
</ram:PostalTradeAddress>
|
|
</ram:BuyerTradeParty>
|
|
</ram:ApplicableHeaderTradeAgreement>
|
|
<ram:ApplicableHeaderTradeSettlement>
|
|
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
|
|
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
<ram:LineTotalAmount>1080.00</ram:LineTotalAmount>
|
|
<ram:TaxBasisTotalAmount>1080.00</ram:TaxBasisTotalAmount>
|
|
<ram:TaxTotalAmount currencyID="EUR">216.00</ram:TaxTotalAmount>
|
|
<ram:GrandTotalAmount>1296.00</ram:GrandTotalAmount>
|
|
<ram:DuePayableAmount>1296.00</ram:DuePayableAmount>
|
|
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
</ram:ApplicableHeaderTradeSettlement>
|
|
</rsm:SupplyChainTradeTransaction>
|
|
</rsm:CrossIndustryInvoice>`;
|
|
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadXml(ciiInvoice);
|
|
|
|
console.log('Testing CII nested structure mapping...');
|
|
|
|
// Verify nested structures are loaded
|
|
expect(einvoice.id).toEqual('NESTED-MAP-001');
|
|
expect(einvoice.notes).toContain('Complex nested structure test');
|
|
|
|
// Nested product information
|
|
expect(einvoice.items[0].articleNumber).toEqual('PROD-001');
|
|
expect(einvoice.items[0].name).toEqual('Nested Product');
|
|
// Note: description field not currently extracted from CII
|
|
// expect(einvoice.items[0].description).toEqual('Product with nested attributes');
|
|
expect(einvoice.items[0].unitNetPrice).toEqual(120);
|
|
expect(einvoice.items[0].unitQuantity).toEqual(9);
|
|
expect(einvoice.items[0].vatPercentage).toEqual(20);
|
|
|
|
// Nested party information
|
|
expect(einvoice.from.name).toEqual('Nested Seller Corp');
|
|
expect(einvoice.from.address.streetName).toEqual('Complex Street 789');
|
|
expect(einvoice.from.address.city).toEqual('Amsterdam');
|
|
expect(einvoice.from.address.countryCode).toEqual('NL');
|
|
|
|
expect(einvoice.to.name).toEqual('Nested Buyer Inc');
|
|
expect(einvoice.to.address.city).toEqual('Paris');
|
|
expect(einvoice.to.address.countryCode).toEqual('FR');
|
|
|
|
// Convert to UBL
|
|
const ublXml = await einvoice.toXmlString('ubl');
|
|
|
|
// Verify UBL contains mapped nested fields
|
|
console.log('Verifying UBL output contains nested fields...');
|
|
expect(ublXml).toContain('NESTED-MAP-001');
|
|
expect(ublXml).toContain('PROD-001');
|
|
expect(ublXml).toContain('Nested Product');
|
|
expect(ublXml).toContain('Nested Seller Corp');
|
|
expect(ublXml).toContain('Amsterdam');
|
|
|
|
console.log('✓ Complex nested field mapping test passed');
|
|
} catch (error) {
|
|
console.error('Nested field mapping test failed:', error);
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
tap.test('CONV-04: Field Mapping - Field mapping with missing optional fields', async () => {
|
|
// Minimal UBL invoice with only mandatory fields
|
|
const minimalUbl = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
|
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
|
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
|
<cbc:ID>MINIMAL-001</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>Minimal Supplier</cbc:Name>
|
|
</cac:PartyName>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:AccountingCustomerParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>Minimal Customer</cbc:Name>
|
|
</cac:PartyName>
|
|
</cac:Party>
|
|
</cac:AccountingCustomerParty>
|
|
<cac:LegalMonetaryTotal>
|
|
<cbc:PayableAmount currencyID="EUR">100.00</cbc:PayableAmount>
|
|
</cac:LegalMonetaryTotal>
|
|
</Invoice>`;
|
|
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadXml(minimalUbl);
|
|
|
|
console.log('Testing minimal field mapping...');
|
|
|
|
// Verify mandatory fields are mapped
|
|
expect(einvoice.id).toEqual('MINIMAL-001');
|
|
expect(einvoice.currency).toEqual('EUR');
|
|
expect(einvoice.date).toBeTypeofNumber();
|
|
|
|
// Verify optional fields have defaults
|
|
expect(einvoice.notes).toEqual([]);
|
|
expect(einvoice.items).toEqual([]);
|
|
expect(einvoice.dueInDays).toEqual(30); // Default value
|
|
|
|
// Convert to CII
|
|
const ciiXml = await einvoice.toXmlString('cii');
|
|
|
|
// Verify CII is valid even with minimal data
|
|
console.log('Verifying minimal CII output...');
|
|
expect(ciiXml).toContain('MINIMAL-001');
|
|
expect(ciiXml).toContain('Minimal Supplier');
|
|
expect(ciiXml).toContain('Minimal Customer');
|
|
expect(ciiXml).toContain('EUR');
|
|
|
|
console.log('✓ Minimal field mapping test passed');
|
|
} catch (error) {
|
|
console.error('Minimal field mapping test failed:', error);
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
tap.test('CONV-04: Field Mapping - Special characters and encoding', async () => {
|
|
// UBL invoice with special characters
|
|
const specialCharsUbl = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
|
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
|
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
|
<cbc:ID>SPECIAL-001</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
|
<cbc:Note>Special chars: äöüß €£¥ <>& "quotes" 'apostrophe'</cbc:Note>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>Müller & Söhne GmbH</cbc:Name>
|
|
</cac:PartyName>
|
|
<cac:PostalAddress>
|
|
<cbc:StreetName>Königsstraße</cbc:StreetName>
|
|
<cbc:CityName>Düsseldorf</cbc:CityName>
|
|
<cbc:PostalZone>40212</cbc:PostalZone>
|
|
<cac:Country>
|
|
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
|
|
</cac:Country>
|
|
</cac:PostalAddress>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:AccountingCustomerParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>François & Associés</cbc:Name>
|
|
</cac:PartyName>
|
|
</cac:Party>
|
|
</cac:AccountingCustomerParty>
|
|
<cac:InvoiceLine>
|
|
<cbc:ID>1</cbc:ID>
|
|
<cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity>
|
|
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
|
|
<cac:Item>
|
|
<cbc:Name>Spëcíål Prödüct™</cbc:Name>
|
|
<cbc:Description>Unicode test: 中文 日本語 한국어 🌍</cbc:Description>
|
|
</cac:Item>
|
|
<cac:Price>
|
|
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
|
|
</cac:Price>
|
|
</cac:InvoiceLine>
|
|
<cac:LegalMonetaryTotal>
|
|
<cbc:PayableAmount currencyID="EUR">100.00</cbc:PayableAmount>
|
|
</cac:LegalMonetaryTotal>
|
|
</Invoice>`;
|
|
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadXml(specialCharsUbl);
|
|
|
|
console.log('Testing special character mapping...');
|
|
|
|
// Verify special characters are preserved
|
|
expect(einvoice.notes[0]).toContain('äöüß');
|
|
expect(einvoice.notes[0]).toContain('€£¥');
|
|
expect(einvoice.notes[0]).toContain('<>&');
|
|
expect(einvoice.notes[0]).toContain('"quotes"');
|
|
|
|
expect(einvoice.from.name).toEqual('Müller & Söhne GmbH');
|
|
expect(einvoice.from.address.streetName).toEqual('Königsstraße');
|
|
expect(einvoice.from.address.city).toEqual('Düsseldorf');
|
|
|
|
expect(einvoice.to.name).toEqual('François & Associés');
|
|
|
|
expect(einvoice.items[0].name).toEqual('Spëcíål Prödüct™');
|
|
// Note: description field not currently extracted
|
|
// expect(einvoice.items[0].description).toContain('中文');
|
|
// expect(einvoice.items[0].description).toContain('日本語');
|
|
// expect(einvoice.items[0].description).toContain('🌍');
|
|
|
|
// Convert to CII
|
|
const ciiXml = await einvoice.toXmlString('cii');
|
|
|
|
// Verify special characters in CII
|
|
console.log('Verifying special characters in CII...');
|
|
expect(ciiXml).toContain('Müller & Söhne GmbH');
|
|
expect(ciiXml).toContain('Königsstraße');
|
|
expect(ciiXml).toContain('François & Associés');
|
|
expect(ciiXml).toContain('Spëcíål Prödüct™');
|
|
|
|
console.log('✓ Special character mapping test passed');
|
|
} catch (error) {
|
|
console.error('Special character mapping test failed:', error);
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
tap.test('CONV-04: Field Mapping - Round-trip conversion', async () => {
|
|
// Original UBL invoice
|
|
const originalUbl = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
|
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
|
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
|
<cbc:ID>ROUND-TRIP-001</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:DueDate>2025-02-25</cbc:DueDate>
|
|
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
|
<cbc:Note>Round-trip conversion test</cbc:Note>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>Round Trip Supplier</cbc:Name>
|
|
</cac:PartyName>
|
|
<cac:PostalAddress>
|
|
<cbc:StreetName>Test Street</cbc:StreetName>
|
|
<cbc:BuildingNumber>42</cbc:BuildingNumber>
|
|
<cbc:CityName>Test City</cbc:CityName>
|
|
<cbc:PostalZone>12345</cbc:PostalZone>
|
|
<cac:Country>
|
|
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
|
|
</cac:Country>
|
|
</cac:PostalAddress>
|
|
<cac:PartyTaxScheme>
|
|
<cbc:CompanyID>DE987654321</cbc:CompanyID>
|
|
<cac:TaxScheme>
|
|
<cbc:ID>VAT</cbc:ID>
|
|
</cac:TaxScheme>
|
|
</cac:PartyTaxScheme>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:AccountingCustomerParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>Round Trip Customer</cbc:Name>
|
|
</cac:PartyName>
|
|
</cac:Party>
|
|
</cac:AccountingCustomerParty>
|
|
<cac:InvoiceLine>
|
|
<cbc:ID>1</cbc:ID>
|
|
<cbc:InvoicedQuantity unitCode="C62">5</cbc:InvoicedQuantity>
|
|
<cbc:LineExtensionAmount currencyID="EUR">500.00</cbc:LineExtensionAmount>
|
|
<cac:Item>
|
|
<cbc:Name>Round Trip Product</cbc:Name>
|
|
</cac:Item>
|
|
<cac:Price>
|
|
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
|
|
</cac:Price>
|
|
</cac:InvoiceLine>
|
|
<cac:LegalMonetaryTotal>
|
|
<cbc:LineExtensionAmount currencyID="EUR">500.00</cbc:LineExtensionAmount>
|
|
<cbc:TaxExclusiveAmount currencyID="EUR">500.00</cbc:TaxExclusiveAmount>
|
|
<cbc:TaxInclusiveAmount currencyID="EUR">595.00</cbc:TaxInclusiveAmount>
|
|
<cbc:PayableAmount currencyID="EUR">595.00</cbc:PayableAmount>
|
|
</cac:LegalMonetaryTotal>
|
|
</Invoice>`;
|
|
|
|
try {
|
|
// Load original
|
|
const einvoice1 = new EInvoice();
|
|
await einvoice1.loadXml(originalUbl);
|
|
|
|
console.log('Testing round-trip conversion UBL → CII → UBL...');
|
|
|
|
// Convert to CII
|
|
const ciiXml = await einvoice1.toXmlString('cii');
|
|
|
|
// Load CII into new instance
|
|
const einvoice2 = new EInvoice();
|
|
await einvoice2.loadXml(ciiXml);
|
|
|
|
// Convert back to UBL
|
|
const roundTripUbl = await einvoice2.toXmlString('ubl');
|
|
|
|
// Load round-trip result
|
|
const einvoice3 = new EInvoice();
|
|
await einvoice3.loadXml(roundTripUbl);
|
|
|
|
// Verify key fields survived round-trip
|
|
console.log('Verifying round-trip preservation...');
|
|
expect(einvoice3.id).toEqual('ROUND-TRIP-001');
|
|
expect(einvoice3.currency).toEqual('EUR');
|
|
expect(einvoice3.notes).toContain('Round-trip conversion test');
|
|
|
|
expect(einvoice3.from.name).toEqual('Round Trip Supplier');
|
|
expect(einvoice3.from.address.streetName).toEqual('Test Street');
|
|
expect(einvoice3.from.address.houseNumber).toEqual('42');
|
|
expect(einvoice3.from.address.city).toEqual('Test City');
|
|
expect(einvoice3.from.address.postalCode).toEqual('12345');
|
|
expect(einvoice3.from.registrationDetails?.vatId).toEqual('DE987654321');
|
|
|
|
expect(einvoice3.to.name).toEqual('Round Trip Customer');
|
|
|
|
expect(einvoice3.items.length).toEqual(1);
|
|
expect(einvoice3.items[0].name).toEqual('Round Trip Product');
|
|
expect(einvoice3.items[0].unitQuantity).toEqual(5);
|
|
expect(einvoice3.items[0].unitNetPrice).toEqual(100);
|
|
|
|
console.log('✓ Round-trip conversion test passed');
|
|
} catch (error) {
|
|
console.error('Round-trip conversion test failed:', error);
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
tap.test('CONV-04: Field Mapping - Corpus field mapping validation', async () => {
|
|
console.log('Testing field mapping with corpus files...');
|
|
|
|
// Get a sample of UBL files
|
|
const corpusFiles = await CorpusLoader.createTestDataset({
|
|
formats: ['UBL'],
|
|
categories: ['UBL_XMLRECHNUNG', 'PEPPOL']
|
|
});
|
|
|
|
let successCount = 0;
|
|
let failureCount = 0;
|
|
let totalFields = 0;
|
|
let mappedFields = 0;
|
|
|
|
// Test a sample of files
|
|
const sampleSize = Math.min(5, corpusFiles.length);
|
|
console.log(`Testing ${sampleSize} corpus files...`);
|
|
|
|
for (let i = 0; i < sampleSize; i++) {
|
|
const file = corpusFiles[i];
|
|
|
|
try {
|
|
const content = await CorpusLoader.loadFile(file.path);
|
|
|
|
if (content instanceof Buffer) {
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadXml(content.toString('utf-8'));
|
|
|
|
// Check critical fields
|
|
const criticalFields = [
|
|
{ field: 'id', value: einvoice.id },
|
|
{ field: 'currency', value: einvoice.currency },
|
|
{ field: 'from.name', value: einvoice.from?.name },
|
|
{ field: 'to.name', value: einvoice.to?.name },
|
|
{ field: 'items', value: einvoice.items?.length > 0 }
|
|
];
|
|
|
|
criticalFields.forEach(check => {
|
|
totalFields++;
|
|
if (check.value) {
|
|
mappedFields++;
|
|
}
|
|
});
|
|
|
|
// Try conversion
|
|
const ciiXml = await einvoice.toXmlString('cii');
|
|
if (ciiXml && ciiXml.length > 100) {
|
|
successCount++;
|
|
} else {
|
|
failureCount++;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(`Failed to process ${file.path}:`, error.message);
|
|
failureCount++;
|
|
}
|
|
}
|
|
|
|
const mappingRate = (mappedFields / totalFields) * 100;
|
|
console.log(`\nCorpus field mapping results:`);
|
|
console.log(`- Files processed: ${sampleSize}`);
|
|
console.log(`- Successful conversions: ${successCount}`);
|
|
console.log(`- Failed conversions: ${failureCount}`);
|
|
console.log(`- Field mapping rate: ${mappingRate.toFixed(1)}%`);
|
|
|
|
expect(successCount).toBeGreaterThan(0);
|
|
expect(mappingRate).toBeGreaterThan(80); // At least 80% of critical fields should be mapped
|
|
|
|
console.log('✓ Corpus field mapping validation passed');
|
|
});
|
|
|
|
tap.start(); |