621 lines
24 KiB
TypeScript
621 lines
24 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import * as plugins from '../plugins.js';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
import { CorpusLoader } from '../corpus.loader.js';
|
|
import { PerformanceTracker } from '../performance.tracker.js';
|
|
|
|
tap.test('CONV-04: Field Mapping - should correctly map fields between formats', async (t) => {
|
|
// CONV-04: Verify accurate field mapping during format conversion
|
|
// This test ensures data is correctly transferred between different formats
|
|
|
|
const performanceTracker = new PerformanceTracker('CONV-04: Field Mapping');
|
|
const corpusLoader = new CorpusLoader();
|
|
|
|
t.test('Basic field mapping UBL to CII', async () => {
|
|
const startTime = performance.now();
|
|
|
|
// 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:TaxPointDate>2025-01-25</cbc:TaxPointDate>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cbc:TaxCurrencyCode>EUR</cbc:TaxCurrencyCode>
|
|
<cbc:BuyerReference>PO-2025-001</cbc:BuyerReference>
|
|
<cac:OrderReference>
|
|
<cbc:ID>ORDER-123</cbc:ID>
|
|
</cac:OrderReference>
|
|
<cac:BillingReference>
|
|
<cac:InvoiceDocumentReference>
|
|
<cbc:ID>PREV-INV-001</cbc:ID>
|
|
<cbc:IssueDate>2025-01-01</cbc:IssueDate>
|
|
</cac:InvoiceDocumentReference>
|
|
</cac:BillingReference>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cbc:EndpointID schemeID="0088">5790000435975</cbc:EndpointID>
|
|
<cac:PartyIdentification>
|
|
<cbc:ID schemeID="0184">DK12345678</cbc:ID>
|
|
</cac:PartyIdentification>
|
|
<cac:PartyName>
|
|
<cbc:Name>Supplier Company A/S</cbc:Name>
|
|
</cac:PartyName>
|
|
<cac:PostalAddress>
|
|
<cbc:StreetName>Main Street</cbc:StreetName>
|
|
<cbc:BuildingNumber>1</cbc:BuildingNumber>
|
|
<cbc:CityName>Copenhagen</cbc:CityName>
|
|
<cbc:PostalZone>1234</cbc:PostalZone>
|
|
<cbc:CountrySubentity>Capital Region</cbc:CountrySubentity>
|
|
<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:PartyLegalEntity>
|
|
<cbc:RegistrationName>Supplier Company A/S</cbc:RegistrationName>
|
|
<cbc:CompanyID schemeID="0184">DK12345678</cbc:CompanyID>
|
|
</cac:PartyLegalEntity>
|
|
<cac:Contact>
|
|
<cbc:Name>John Doe</cbc:Name>
|
|
<cbc:Telephone>+45 12345678</cbc:Telephone>
|
|
<cbc:ElectronicMail>john@supplier.dk</cbc:ElectronicMail>
|
|
</cac:Contact>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:AccountingCustomerParty>
|
|
<cac:Party>
|
|
<cbc:EndpointID schemeID="0088">5790000435982</cbc:EndpointID>
|
|
<cac:PartyIdentification>
|
|
<cbc:ID schemeID="0184">DK87654321</cbc:ID>
|
|
</cac:PartyIdentification>
|
|
<cac:PartyName>
|
|
<cbc:Name>Customer Company B/V</cbc:Name>
|
|
</cac:PartyName>
|
|
<cac:PostalAddress>
|
|
<cbc:StreetName>Market Street</cbc:StreetName>
|
|
<cbc:BuildingNumber>100</cbc:BuildingNumber>
|
|
<cbc:CityName>Aarhus</cbc:CityName>
|
|
<cbc:PostalZone>8000</cbc:PostalZone>
|
|
<cac:Country>
|
|
<cbc:IdentificationCode>DK</cbc:IdentificationCode>
|
|
</cac:Country>
|
|
</cac:PostalAddress>
|
|
<cac:PartyTaxScheme>
|
|
<cbc:CompanyID>DK87654321</cbc:CompanyID>
|
|
<cac:TaxScheme>
|
|
<cbc:ID>VAT</cbc:ID>
|
|
</cac:TaxScheme>
|
|
</cac:PartyTaxScheme>
|
|
<cac:Contact>
|
|
<cbc:Name>Jane Smith</cbc:Name>
|
|
<cbc:ElectronicMail>jane@customer.dk</cbc:ElectronicMail>
|
|
</cac:Contact>
|
|
</cac:Party>
|
|
</cac:AccountingCustomerParty>
|
|
<cac:PaymentMeans>
|
|
<cbc:PaymentMeansCode name="Credit transfer">30</cbc:PaymentMeansCode>
|
|
<cbc:PaymentID>PAY-2025-001</cbc:PaymentID>
|
|
<cac:PayeeFinancialAccount>
|
|
<cbc:ID>DK5000400440116243</cbc:ID>
|
|
<cbc:Name>Supplier Bank Account</cbc:Name>
|
|
<cac:FinancialInstitutionBranch>
|
|
<cbc:ID>DANBDK22</cbc:ID>
|
|
<cbc:Name>Danske Bank</cbc:Name>
|
|
</cac:FinancialInstitutionBranch>
|
|
</cac:PayeeFinancialAccount>
|
|
</cac:PaymentMeans>
|
|
</Invoice>`;
|
|
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadFromString(ublInvoice);
|
|
|
|
// Check if key fields are preserved
|
|
const invoiceData = einvoice.getInvoiceData();
|
|
if (invoiceData) {
|
|
// Basic fields
|
|
expect(invoiceData.invoiceNumber).toBe('FIELD-MAP-001');
|
|
expect(invoiceData.issueDate).toContain('2025-01-25');
|
|
expect(invoiceData.dueDate).toContain('2025-02-25');
|
|
expect(invoiceData.currency).toBe('EUR');
|
|
|
|
// Supplier fields
|
|
if (invoiceData.supplier) {
|
|
expect(invoiceData.supplier.name).toContain('Supplier Company');
|
|
expect(invoiceData.supplier.vatNumber).toContain('DK12345678');
|
|
expect(invoiceData.supplier.address?.street).toContain('Main Street');
|
|
expect(invoiceData.supplier.address?.city).toBe('Copenhagen');
|
|
expect(invoiceData.supplier.address?.postalCode).toBe('1234');
|
|
expect(invoiceData.supplier.address?.country).toBe('DK');
|
|
}
|
|
|
|
// Customer fields
|
|
if (invoiceData.customer) {
|
|
expect(invoiceData.customer.name).toContain('Customer Company');
|
|
expect(invoiceData.customer.vatNumber).toContain('DK87654321');
|
|
expect(invoiceData.customer.address?.city).toBe('Aarhus');
|
|
}
|
|
|
|
console.log('Basic field mapping verified');
|
|
} else {
|
|
console.log('Field mapping through invoice data not available');
|
|
}
|
|
|
|
const elapsed = performance.now() - startTime;
|
|
performanceTracker.addMeasurement('basic-mapping', elapsed);
|
|
});
|
|
|
|
t.test('Complex nested field mapping', async () => {
|
|
const startTime = performance.now();
|
|
|
|
// 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</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:SubjectCode>AAI</ram:SubjectCode>
|
|
</ram:IncludedNote>
|
|
<ram:IncludedNote>
|
|
<ram:Content>Second note for testing</ram:Content>
|
|
<ram:SubjectCode>REG</ram:SubjectCode>
|
|
</ram:IncludedNote>
|
|
</rsm:ExchangedDocument>
|
|
<rsm:SupplyChainTradeTransaction>
|
|
<ram:IncludedSupplyChainTradeLineItem>
|
|
<ram:AssociatedDocumentLineDocument>
|
|
<ram:LineID>1</ram:LineID>
|
|
<ram:IncludedNote>
|
|
<ram:Content>Line item note</ram:Content>
|
|
</ram:IncludedNote>
|
|
</ram:AssociatedDocumentLineDocument>
|
|
<ram:SpecifiedTradeProduct>
|
|
<ram:GlobalID schemeID="0160">1234567890123</ram:GlobalID>
|
|
<ram:SellerAssignedID>PROD-001</ram:SellerAssignedID>
|
|
<ram:BuyerAssignedID>CUST-PROD-001</ram:BuyerAssignedID>
|
|
<ram:Name>Complex Product</ram:Name>
|
|
<ram:Description>Product with multiple identifiers and attributes</ram:Description>
|
|
<ram:ApplicableProductCharacteristic>
|
|
<ram:Description>Color</ram:Description>
|
|
<ram:Value>Blue</ram:Value>
|
|
</ram:ApplicableProductCharacteristic>
|
|
<ram:ApplicableProductCharacteristic>
|
|
<ram:Description>Size</ram:Description>
|
|
<ram:Value>Large</ram:Value>
|
|
</ram:ApplicableProductCharacteristic>
|
|
</ram:SpecifiedTradeProduct>
|
|
<ram:SpecifiedLineTradeAgreement>
|
|
<ram:BuyerOrderReferencedDocument>
|
|
<ram:LineID>PO-LINE-001</ram:LineID>
|
|
</ram:BuyerOrderReferencedDocument>
|
|
<ram:GrossPriceProductTradePrice>
|
|
<ram:ChargeAmount>120.00</ram:ChargeAmount>
|
|
<ram:AppliedTradeAllowanceCharge>
|
|
<ram:ChargeIndicator>
|
|
<udt:Indicator>false</udt:Indicator>
|
|
</ram:ChargeIndicator>
|
|
<ram:CalculationPercent>10.00</ram:CalculationPercent>
|
|
<ram:ActualAmount>12.00</ram:ActualAmount>
|
|
<ram:Reason>Volume discount</ram:Reason>
|
|
</ram:AppliedTradeAllowanceCharge>
|
|
</ram:GrossPriceProductTradePrice>
|
|
<ram:NetPriceProductTradePrice>
|
|
<ram:ChargeAmount>108.00</ram:ChargeAmount>
|
|
</ram:NetPriceProductTradePrice>
|
|
</ram:SpecifiedLineTradeAgreement>
|
|
<ram:SpecifiedLineTradeDelivery>
|
|
<ram:BilledQuantity unitCode="C62">10</ram:BilledQuantity>
|
|
</ram:SpecifiedLineTradeDelivery>
|
|
<ram:SpecifiedLineTradeSettlement>
|
|
<ram:ApplicableTradeTax>
|
|
<ram:TypeCode>VAT</ram:TypeCode>
|
|
<ram:CategoryCode>S</ram:CategoryCode>
|
|
<ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>
|
|
</ram:ApplicableTradeTax>
|
|
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
|
<ram:LineTotalAmount>1080.00</ram:LineTotalAmount>
|
|
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
|
</ram:SpecifiedLineTradeSettlement>
|
|
</ram:IncludedSupplyChainTradeLineItem>
|
|
</rsm:SupplyChainTradeTransaction>
|
|
</rsm:CrossIndustryInvoice>`;
|
|
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadFromString(ciiInvoice);
|
|
|
|
const xmlString = einvoice.getXmlString();
|
|
|
|
// Verify nested structures are preserved
|
|
expect(xmlString).toContain('NESTED-MAP-001');
|
|
expect(xmlString).toContain('Complex nested structure test');
|
|
expect(xmlString).toContain('PROD-001');
|
|
expect(xmlString).toContain('1234567890123');
|
|
expect(xmlString).toContain('Color');
|
|
expect(xmlString).toContain('Blue');
|
|
expect(xmlString).toContain('Volume discount');
|
|
|
|
console.log('Complex nested field mapping tested');
|
|
|
|
const elapsed = performance.now() - startTime;
|
|
performanceTracker.addMeasurement('nested-mapping', elapsed);
|
|
});
|
|
|
|
t.test('Field mapping with missing optional fields', async () => {
|
|
const startTime = performance.now();
|
|
|
|
// Minimal UBL invoice
|
|
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:PartyLegalEntity>
|
|
<cbc:RegistrationName>Minimal Supplier</cbc:RegistrationName>
|
|
</cac:PartyLegalEntity>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:AccountingCustomerParty>
|
|
<cac:Party>
|
|
<cac:PartyLegalEntity>
|
|
<cbc:RegistrationName>Minimal Customer</cbc:RegistrationName>
|
|
</cac:PartyLegalEntity>
|
|
</cac:Party>
|
|
</cac:AccountingCustomerParty>
|
|
<cac:LegalMonetaryTotal>
|
|
<cbc:PayableAmount currencyID="EUR">100.00</cbc:PayableAmount>
|
|
</cac:LegalMonetaryTotal>
|
|
</Invoice>`;
|
|
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadFromString(minimalUbl);
|
|
|
|
const invoiceData = einvoice.getInvoiceData();
|
|
|
|
// Verify mandatory fields are mapped
|
|
expect(invoiceData?.invoiceNumber).toBe('MINIMAL-001');
|
|
expect(invoiceData?.issueDate).toContain('2025-01-25');
|
|
expect(invoiceData?.currency).toBe('EUR');
|
|
expect(invoiceData?.totalAmount).toBe(100.00);
|
|
|
|
// Optional fields should be undefined or have defaults
|
|
expect(invoiceData?.dueDate).toBeUndefined();
|
|
expect(invoiceData?.notes).toBeUndefined();
|
|
expect(invoiceData?.supplier?.vatNumber).toBeUndefined();
|
|
|
|
console.log('Minimal field mapping verified');
|
|
|
|
const elapsed = performance.now() - startTime;
|
|
performanceTracker.addMeasurement('minimal-mapping', elapsed);
|
|
});
|
|
|
|
t.test('Field type conversion mapping', async () => {
|
|
const startTime = performance.now();
|
|
|
|
// Invoice with various data types
|
|
const typeTestInvoice = `<?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>TYPE-TEST-001</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:IssueTime>14:30:00</cbc:IssueTime>
|
|
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cbc:LineCountNumeric>5</cbc:LineCountNumeric>
|
|
<cbc:TaxPointDate>2025-01-25</cbc:TaxPointDate>
|
|
<cac:InvoicePeriod>
|
|
<cbc:StartDate>2025-01-01</cbc:StartDate>
|
|
<cbc:EndDate>2025-01-31</cbc:EndDate>
|
|
</cac:InvoicePeriod>
|
|
<cac:OrderReference>
|
|
<cbc:ID>ORDER-123</cbc:ID>
|
|
<cbc:SalesOrderID>SO-456</cbc:SalesOrderID>
|
|
</cac:OrderReference>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyLegalEntity>
|
|
<cbc:RegistrationName>Type Test Supplier</cbc:RegistrationName>
|
|
</cac:PartyLegalEntity>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:AccountingCustomerParty>
|
|
<cac:Party>
|
|
<cac:PartyLegalEntity>
|
|
<cbc:RegistrationName>Type Test Customer</cbc:RegistrationName>
|
|
</cac:PartyLegalEntity>
|
|
</cac:Party>
|
|
</cac:AccountingCustomerParty>
|
|
<cac:AllowanceCharge>
|
|
<cbc:ChargeIndicator>false</cbc:ChargeIndicator>
|
|
<cbc:MultiplierFactorNumeric>0.05</cbc:MultiplierFactorNumeric>
|
|
<cbc:Amount currencyID="EUR">50.00</cbc:Amount>
|
|
<cbc:BaseAmount currencyID="EUR">1000.00</cbc:BaseAmount>
|
|
</cac:AllowanceCharge>
|
|
<cac:TaxTotal>
|
|
<cbc:TaxAmount currencyID="EUR">190.00</cbc:TaxAmount>
|
|
<cac:TaxSubtotal>
|
|
<cbc:TaxableAmount currencyID="EUR">1000.00</cbc:TaxableAmount>
|
|
<cbc:TaxAmount currencyID="EUR">190.00</cbc:TaxAmount>
|
|
<cac:TaxCategory>
|
|
<cbc:ID>S</cbc:ID>
|
|
<cbc:Percent>19.00</cbc:Percent>
|
|
<cbc:TaxExemptionReasonCode>VATEX-EU-O</cbc:TaxExemptionReasonCode>
|
|
</cac:TaxCategory>
|
|
</cac:TaxSubtotal>
|
|
</cac:TaxTotal>
|
|
</Invoice>`;
|
|
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadFromString(typeTestInvoice);
|
|
|
|
const xmlString = einvoice.getXmlString();
|
|
|
|
// Verify different data types are preserved
|
|
expect(xmlString).toContain('TYPE-TEST-001'); // String
|
|
expect(xmlString).toContain('2025-01-25'); // Date
|
|
expect(xmlString).toContain('14:30:00'); // Time
|
|
expect(xmlString).toContain('5'); // Integer
|
|
expect(xmlString).toContain('19.00'); // Decimal
|
|
expect(xmlString).toContain('false'); // Boolean
|
|
expect(xmlString).toContain('0.05'); // Float
|
|
|
|
console.log('Field type conversion mapping verified');
|
|
|
|
const elapsed = performance.now() - startTime;
|
|
performanceTracker.addMeasurement('type-conversion', elapsed);
|
|
});
|
|
|
|
t.test('Array field mapping', async () => {
|
|
const startTime = performance.now();
|
|
|
|
// Invoice with multiple repeated elements
|
|
const arrayInvoice = `<?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>ARRAY-TEST-001</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
|
<cbc:Note>First note</cbc:Note>
|
|
<cbc:Note>Second note</cbc:Note>
|
|
<cbc:Note>Third note with special chars: €£¥</cbc:Note>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cac:AdditionalDocumentReference>
|
|
<cbc:ID>DOC-001</cbc:ID>
|
|
<cbc:DocumentType>Contract</cbc:DocumentType>
|
|
</cac:AdditionalDocumentReference>
|
|
<cac:AdditionalDocumentReference>
|
|
<cbc:ID>DOC-002</cbc:ID>
|
|
<cbc:DocumentType>Purchase Order</cbc:DocumentType>
|
|
</cac:AdditionalDocumentReference>
|
|
<cac:AdditionalDocumentReference>
|
|
<cbc:ID>DOC-003</cbc:ID>
|
|
<cbc:DocumentType>Delivery Note</cbc:DocumentType>
|
|
</cac:AdditionalDocumentReference>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyIdentification>
|
|
<cbc:ID schemeID="GLN">1234567890123</cbc:ID>
|
|
</cac:PartyIdentification>
|
|
<cac:PartyIdentification>
|
|
<cbc:ID schemeID="VAT">DK12345678</cbc:ID>
|
|
</cac:PartyIdentification>
|
|
<cac:PartyIdentification>
|
|
<cbc:ID schemeID="DUNS">123456789</cbc:ID>
|
|
</cac:PartyIdentification>
|
|
<cac:PartyLegalEntity>
|
|
<cbc:RegistrationName>Array Test Supplier</cbc:RegistrationName>
|
|
</cac:PartyLegalEntity>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:AccountingCustomerParty>
|
|
<cac:Party>
|
|
<cac:PartyLegalEntity>
|
|
<cbc:RegistrationName>Array Test Customer</cbc:RegistrationName>
|
|
</cac:PartyLegalEntity>
|
|
</cac:Party>
|
|
</cac:AccountingCustomerParty>
|
|
<cac:PaymentMeans>
|
|
<cbc:PaymentMeansCode>30</cbc:PaymentMeansCode>
|
|
<cbc:PaymentID>PAY-001</cbc:PaymentID>
|
|
</cac:PaymentMeans>
|
|
<cac:PaymentMeans>
|
|
<cbc:PaymentMeansCode>31</cbc:PaymentMeansCode>
|
|
<cbc:PaymentID>PAY-002</cbc:PaymentID>
|
|
</cac:PaymentMeans>
|
|
</Invoice>`;
|
|
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadFromString(arrayInvoice);
|
|
|
|
const xmlString = einvoice.getXmlString();
|
|
|
|
// Verify arrays are preserved
|
|
expect(xmlString).toContain('First note');
|
|
expect(xmlString).toContain('Second note');
|
|
expect(xmlString).toContain('Third note with special chars: €£¥');
|
|
expect(xmlString).toContain('DOC-001');
|
|
expect(xmlString).toContain('DOC-002');
|
|
expect(xmlString).toContain('DOC-003');
|
|
expect(xmlString).toContain('1234567890123');
|
|
expect(xmlString).toContain('DK12345678');
|
|
expect(xmlString).toContain('123456789');
|
|
|
|
console.log('Array field mapping verified');
|
|
|
|
const elapsed = performance.now() - startTime;
|
|
performanceTracker.addMeasurement('array-mapping', elapsed);
|
|
});
|
|
|
|
t.test('Cross-reference field mapping', async () => {
|
|
const startTime = performance.now();
|
|
|
|
// Invoice with cross-references between sections
|
|
const crossRefInvoice = `<?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>XREF-TEST-001</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cac:ProjectReference>
|
|
<cbc:ID>PROJ-2025-001</cbc:ID>
|
|
</cac:ProjectReference>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyLegalEntity>
|
|
<cbc:RegistrationName>Cross Reference Supplier</cbc:RegistrationName>
|
|
</cac:PartyLegalEntity>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:AccountingCustomerParty>
|
|
<cac:Party>
|
|
<cac:PartyLegalEntity>
|
|
<cbc:RegistrationName>Cross Reference Customer</cbc:RegistrationName>
|
|
</cac:PartyLegalEntity>
|
|
</cac:Party>
|
|
</cac:AccountingCustomerParty>
|
|
<cac:Delivery>
|
|
<cbc:ActualDeliveryDate>2025-01-20</cbc:ActualDeliveryDate>
|
|
<cac:DeliveryLocation>
|
|
<cbc:ID schemeID="GLN">5790000435999</cbc:ID>
|
|
<cac:Address>
|
|
<cbc:StreetName>Delivery Street</cbc:StreetName>
|
|
<cbc:CityName>Copenhagen</cbc:CityName>
|
|
</cac:Address>
|
|
</cac:DeliveryLocation>
|
|
</cac:Delivery>
|
|
<cac:InvoiceLine>
|
|
<cbc:ID>1</cbc:ID>
|
|
<cbc:Note>Delivered to GLN: 5790000435999</cbc:Note>
|
|
<cbc:InvoicedQuantity unitCode="C62">10</cbc:InvoicedQuantity>
|
|
<cbc:LineExtensionAmount currencyID="EUR">1000.00</cbc:LineExtensionAmount>
|
|
<cac:OrderLineReference>
|
|
<cbc:LineID>ORDER-LINE-001</cbc:LineID>
|
|
</cac:OrderLineReference>
|
|
<cac:Item>
|
|
<cbc:Name>Product for PROJ-2025-001</cbc:Name>
|
|
</cac:Item>
|
|
<cac:Price>
|
|
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
|
|
</cac:Price>
|
|
</cac:InvoiceLine>
|
|
</Invoice>`;
|
|
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadFromString(crossRefInvoice);
|
|
|
|
const xmlString = einvoice.getXmlString();
|
|
|
|
// Verify cross-references are maintained
|
|
expect(xmlString).toContain('PROJ-2025-001');
|
|
expect(xmlString).toContain('5790000435999');
|
|
expect(xmlString).toContain('Delivered to GLN: 5790000435999');
|
|
expect(xmlString).toContain('Product for PROJ-2025-001');
|
|
expect(xmlString).toContain('ORDER-LINE-001');
|
|
|
|
console.log('Cross-reference field mapping verified');
|
|
|
|
const elapsed = performance.now() - startTime;
|
|
performanceTracker.addMeasurement('cross-reference', elapsed);
|
|
});
|
|
|
|
t.test('Corpus field mapping validation', async () => {
|
|
const startTime = performance.now();
|
|
let processedCount = 0;
|
|
let mappingIssues = 0;
|
|
const criticalFields = ['ID', 'IssueDate', 'DocumentCurrencyCode', 'AccountingSupplierParty', 'AccountingCustomerParty'];
|
|
|
|
const files = await corpusLoader.getAllFiles();
|
|
const xmlFiles = files.filter(f => f.endsWith('.xml') && !f.includes('.pdf'));
|
|
|
|
// Test field mapping on corpus files
|
|
const sampleSize = Math.min(30, xmlFiles.length);
|
|
const sample = xmlFiles.slice(0, sampleSize);
|
|
|
|
for (const file of sample) {
|
|
try {
|
|
const content = await corpusLoader.readFile(file);
|
|
const einvoice = new EInvoice();
|
|
|
|
if (typeof content === 'string') {
|
|
await einvoice.loadFromString(content);
|
|
} else {
|
|
await einvoice.loadFromBuffer(content);
|
|
}
|
|
|
|
const xmlString = einvoice.getXmlString();
|
|
const invoiceData = einvoice.getInvoiceData();
|
|
|
|
// Check critical field mapping
|
|
let hasIssue = false;
|
|
|
|
if (invoiceData) {
|
|
if (!invoiceData.invoiceNumber && xmlString.includes('<cbc:ID>')) {
|
|
console.log(`${file}: Invoice number not mapped`);
|
|
hasIssue = true;
|
|
}
|
|
if (!invoiceData.issueDate && xmlString.includes('<cbc:IssueDate>')) {
|
|
console.log(`${file}: Issue date not mapped`);
|
|
hasIssue = true;
|
|
}
|
|
if (!invoiceData.currency && xmlString.includes('<cbc:DocumentCurrencyCode>')) {
|
|
console.log(`${file}: Currency not mapped`);
|
|
hasIssue = true;
|
|
}
|
|
}
|
|
|
|
if (hasIssue) mappingIssues++;
|
|
processedCount++;
|
|
} catch (error) {
|
|
console.log(`Field mapping error in ${file}:`, error.message);
|
|
}
|
|
}
|
|
|
|
console.log(`Corpus field mapping validation (${processedCount} files):`);
|
|
console.log(`- Files with potential mapping issues: ${mappingIssues}`);
|
|
|
|
const elapsed = performance.now() - startTime;
|
|
performanceTracker.addMeasurement('corpus-validation', elapsed);
|
|
});
|
|
|
|
// Print performance summary
|
|
performanceTracker.printSummary();
|
|
|
|
// Performance assertions
|
|
const avgTime = performanceTracker.getAverageTime();
|
|
expect(avgTime).toBeLessThan(300); // Field mapping should be reasonably fast
|
|
});
|
|
|
|
tap.start(); |