2025-05-25 19:45:37 +00:00
|
|
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
|
|
|
import { promises as fs } from 'fs';
|
|
|
|
|
import * as path from 'path';
|
|
|
|
|
import { CorpusLoader } from '../../helpers/corpus.loader.js';
|
|
|
|
|
import { PerformanceTracker } from '../../helpers/performance.tracker.js';
|
|
|
|
|
|
|
|
|
|
tap.test('VAL-05: Calculation Validation - should validate invoice calculations and totals', async () => {
|
2025-05-27 12:23:50 +00:00
|
|
|
|
// Get XML-Rechnung test files which contain various calculation scenarios
|
|
|
|
|
const ublFiles = await CorpusLoader.getFiles('UBL_XMLRECHNUNG');
|
|
|
|
|
const ciiFiles = await CorpusLoader.getFiles('CII_XMLRECHNUNG');
|
|
|
|
|
const coFiles = [...ublFiles, ...ciiFiles].filter(f => f.endsWith('.xml')).slice(0, 10);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
2025-05-27 12:23:50 +00:00
|
|
|
|
console.log(`Testing calculation validation on ${coFiles.length} invoice files`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
|
|
const { EInvoice } = await import('../../../ts/index.js');
|
|
|
|
|
|
|
|
|
|
let validCalculations = 0;
|
|
|
|
|
let invalidCalculations = 0;
|
|
|
|
|
let errorCount = 0;
|
|
|
|
|
const calculationErrors: { file: string; errors: string[] }[] = [];
|
|
|
|
|
|
|
|
|
|
for (const filePath of coFiles.slice(0, 10)) { // Test first 10 calculation files
|
|
|
|
|
const fileName = path.basename(filePath);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const xmlContent = await fs.readFile(filePath, 'utf-8');
|
|
|
|
|
|
|
|
|
|
const { result: einvoice } = await PerformanceTracker.track(
|
|
|
|
|
'calculation-xml-loading',
|
|
|
|
|
async () => await EInvoice.fromXml(xmlContent)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const { result: validation } = await PerformanceTracker.track(
|
|
|
|
|
'calculation-validation',
|
|
|
|
|
async () => {
|
|
|
|
|
return await einvoice.validate(/* ValidationLevel.BUSINESS */);
|
|
|
|
|
},
|
|
|
|
|
{ file: fileName }
|
|
|
|
|
);
|
|
|
|
|
|
2025-05-27 12:23:50 +00:00
|
|
|
|
// These are valid files - calculations should be correct
|
|
|
|
|
if (validation.valid) {
|
|
|
|
|
validCalculations++;
|
|
|
|
|
console.log(`✓ ${fileName}: Calculations are valid`);
|
|
|
|
|
} else if (validation.errors) {
|
2025-05-25 19:45:37 +00:00
|
|
|
|
const calcErrors = validation.errors.filter(e =>
|
|
|
|
|
e.code && (
|
|
|
|
|
e.code.includes('BR-CO') ||
|
|
|
|
|
e.message && (
|
|
|
|
|
e.message.toLowerCase().includes('calculation') ||
|
|
|
|
|
e.message.toLowerCase().includes('sum') ||
|
|
|
|
|
e.message.toLowerCase().includes('total') ||
|
|
|
|
|
e.message.toLowerCase().includes('amount')
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (calcErrors.length > 0) {
|
2025-05-27 12:23:50 +00:00
|
|
|
|
invalidCalculations++;
|
|
|
|
|
console.log(`✗ ${fileName}: Calculation errors found (${calcErrors.length})`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
calculationErrors.push({
|
|
|
|
|
file: fileName,
|
|
|
|
|
errors: calcErrors.map(e => `${e.code}: ${e.message}`)
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
invalidCalculations++;
|
2025-05-27 12:23:50 +00:00
|
|
|
|
console.log(`✗ ${fileName}: Invalid but no calculation-specific errors found`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
errorCount++;
|
|
|
|
|
console.log(`✗ ${fileName}: Error - ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('\n=== CALCULATION VALIDATION SUMMARY ===');
|
2025-05-27 12:23:50 +00:00
|
|
|
|
console.log(`Files with valid calculations: ${validCalculations}`);
|
|
|
|
|
console.log(`Files with calculation errors: ${invalidCalculations}`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
console.log(`Processing errors: ${errorCount}`);
|
|
|
|
|
|
|
|
|
|
// Show sample calculation errors
|
|
|
|
|
if (calculationErrors.length > 0) {
|
|
|
|
|
console.log('\nSample calculation errors detected:');
|
|
|
|
|
calculationErrors.slice(0, 3).forEach(item => {
|
|
|
|
|
console.log(` ${item.file}:`);
|
|
|
|
|
item.errors.slice(0, 2).forEach(error => {
|
|
|
|
|
console.log(` - ${error}`);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Performance summary
|
|
|
|
|
const perfSummary = await PerformanceTracker.getSummary('calculation-validation');
|
|
|
|
|
if (perfSummary) {
|
|
|
|
|
console.log(`\nCalculation Validation Performance:`);
|
|
|
|
|
console.log(` Average: ${perfSummary.average.toFixed(2)}ms`);
|
|
|
|
|
console.log(` P95: ${perfSummary.p95.toFixed(2)}ms`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Expect some calculation validation to work
|
|
|
|
|
expect(validCalculations + invalidCalculations).toBeGreaterThan(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('VAL-05: Line Item Calculation Validation - should validate individual line calculations', async () => {
|
|
|
|
|
const { EInvoice } = await import('../../../ts/index.js');
|
|
|
|
|
|
|
|
|
|
const lineCalculationTests = [
|
|
|
|
|
{
|
|
|
|
|
name: 'Correct line calculation',
|
|
|
|
|
xml: `<?xml version="1.0"?>
|
|
|
|
|
<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>LINE-CALC-001</cbc:ID>
|
|
|
|
|
<cac:InvoiceLine>
|
|
|
|
|
<cbc:ID>1</cbc:ID>
|
|
|
|
|
<cbc:InvoicedQuantity unitCode="EA">5</cbc:InvoicedQuantity>
|
|
|
|
|
<cbc:LineExtensionAmount currencyID="EUR">500.00</cbc:LineExtensionAmount>
|
|
|
|
|
<cac:Price>
|
|
|
|
|
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
|
|
|
|
|
</cac:Price>
|
|
|
|
|
</cac:InvoiceLine>
|
|
|
|
|
</Invoice>`,
|
|
|
|
|
shouldBeValid: true,
|
|
|
|
|
description: '5 × 100.00 = 500.00 (correct)'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Incorrect line calculation',
|
|
|
|
|
xml: `<?xml version="1.0"?>
|
|
|
|
|
<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>LINE-CALC-002</cbc:ID>
|
|
|
|
|
<cac:InvoiceLine>
|
|
|
|
|
<cbc:ID>1</cbc:ID>
|
|
|
|
|
<cbc:InvoicedQuantity unitCode="EA">5</cbc:InvoicedQuantity>
|
|
|
|
|
<cbc:LineExtensionAmount currencyID="EUR">600.00</cbc:LineExtensionAmount>
|
|
|
|
|
<cac:Price>
|
|
|
|
|
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
|
|
|
|
|
</cac:Price>
|
|
|
|
|
</cac:InvoiceLine>
|
|
|
|
|
</Invoice>`,
|
|
|
|
|
shouldBeValid: false,
|
|
|
|
|
description: '5 × 100.00 ≠ 600.00 (incorrect)'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Multiple line items with calculations',
|
|
|
|
|
xml: `<?xml version="1.0"?>
|
|
|
|
|
<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>LINE-CALC-003</cbc:ID>
|
|
|
|
|
<cac:InvoiceLine>
|
|
|
|
|
<cbc:ID>1</cbc:ID>
|
|
|
|
|
<cbc:InvoicedQuantity unitCode="EA">2</cbc:InvoicedQuantity>
|
|
|
|
|
<cbc:LineExtensionAmount currencyID="EUR">200.00</cbc:LineExtensionAmount>
|
|
|
|
|
<cac:Price>
|
|
|
|
|
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
|
|
|
|
|
</cac:Price>
|
|
|
|
|
</cac:InvoiceLine>
|
|
|
|
|
<cac:InvoiceLine>
|
|
|
|
|
<cbc:ID>2</cbc:ID>
|
|
|
|
|
<cbc:InvoicedQuantity unitCode="EA">3</cbc:InvoicedQuantity>
|
|
|
|
|
<cbc:LineExtensionAmount currencyID="EUR">150.00</cbc:LineExtensionAmount>
|
|
|
|
|
<cac:Price>
|
|
|
|
|
<cbc:PriceAmount currencyID="EUR">50.00</cbc:PriceAmount>
|
|
|
|
|
</cac:Price>
|
|
|
|
|
</cac:InvoiceLine>
|
|
|
|
|
</Invoice>`,
|
|
|
|
|
shouldBeValid: true,
|
|
|
|
|
description: 'Line 1: 2×100=200, Line 2: 3×50=150 (both correct)'
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const test of lineCalculationTests) {
|
|
|
|
|
try {
|
|
|
|
|
const { result: validation } = await PerformanceTracker.track(
|
|
|
|
|
'line-calculation-test',
|
|
|
|
|
async () => {
|
|
|
|
|
const einvoice = await EInvoice.fromXml(test.xml);
|
|
|
|
|
return await einvoice.validate();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
console.log(`${test.name}: ${validation.valid ? 'VALID' : 'INVALID'}`);
|
|
|
|
|
console.log(` ${test.description}`);
|
|
|
|
|
|
|
|
|
|
if (!test.shouldBeValid && !validation.valid) {
|
|
|
|
|
console.log(` ✓ Correctly detected calculation error`);
|
|
|
|
|
if (validation.errors) {
|
|
|
|
|
const calcErrors = validation.errors.filter(e =>
|
|
|
|
|
e.message && e.message.toLowerCase().includes('calculation')
|
|
|
|
|
);
|
|
|
|
|
console.log(` Calculation errors: ${calcErrors.length}`);
|
|
|
|
|
}
|
|
|
|
|
} else if (test.shouldBeValid && validation.valid) {
|
|
|
|
|
console.log(` ✓ Correctly validated calculation`);
|
|
|
|
|
} else {
|
|
|
|
|
console.log(` ○ Unexpected result (calculation validation may need implementation)`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(`${test.name}: Error - ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('VAL-05: Tax Calculation Validation - should validate VAT and tax calculations', async () => {
|
|
|
|
|
const { EInvoice } = await import('../../../ts/index.js');
|
|
|
|
|
|
|
|
|
|
const taxCalculationTests = [
|
|
|
|
|
{
|
|
|
|
|
name: 'Correct VAT calculation',
|
|
|
|
|
xml: `<?xml version="1.0"?>
|
|
|
|
|
<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>TAX-001</cbc:ID>
|
|
|
|
|
<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</cbc:Percent>
|
|
|
|
|
</cac:TaxCategory>
|
|
|
|
|
</cac:TaxSubtotal>
|
|
|
|
|
</cac:TaxTotal>
|
|
|
|
|
<cac:LegalMonetaryTotal>
|
|
|
|
|
<cbc:TaxExclusiveAmount currencyID="EUR">1000.00</cbc:TaxExclusiveAmount>
|
|
|
|
|
<cbc:TaxInclusiveAmount currencyID="EUR">1190.00</cbc:TaxInclusiveAmount>
|
|
|
|
|
</cac:LegalMonetaryTotal>
|
|
|
|
|
</Invoice>`,
|
|
|
|
|
shouldBeValid: true,
|
|
|
|
|
description: '1000.00 × 19% = 190.00, Total: 1190.00 (correct)'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Incorrect VAT calculation',
|
|
|
|
|
xml: `<?xml version="1.0"?>
|
|
|
|
|
<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>TAX-002</cbc:ID>
|
|
|
|
|
<cac:TaxTotal>
|
|
|
|
|
<cbc:TaxAmount currencyID="EUR">200.00</cbc:TaxAmount>
|
|
|
|
|
<cac:TaxSubtotal>
|
|
|
|
|
<cbc:TaxableAmount currencyID="EUR">1000.00</cbc:TaxableAmount>
|
|
|
|
|
<cbc:TaxAmount currencyID="EUR">200.00</cbc:TaxAmount>
|
|
|
|
|
<cac:TaxCategory>
|
|
|
|
|
<cbc:ID>S</cbc:ID>
|
|
|
|
|
<cbc:Percent>19</cbc:Percent>
|
|
|
|
|
</cac:TaxCategory>
|
|
|
|
|
</cac:TaxSubtotal>
|
|
|
|
|
</cac:TaxTotal>
|
|
|
|
|
<cac:LegalMonetaryTotal>
|
|
|
|
|
<cbc:TaxExclusiveAmount currencyID="EUR">1000.00</cbc:TaxExclusiveAmount>
|
|
|
|
|
<cbc:TaxInclusiveAmount currencyID="EUR">1200.00</cbc:TaxInclusiveAmount>
|
|
|
|
|
</cac:LegalMonetaryTotal>
|
|
|
|
|
</Invoice>`,
|
|
|
|
|
shouldBeValid: false,
|
|
|
|
|
description: '1000.00 × 19% = 190.00, not 200.00 (incorrect)'
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const test of taxCalculationTests) {
|
|
|
|
|
try {
|
|
|
|
|
const { result: validation } = await PerformanceTracker.track(
|
|
|
|
|
'tax-calculation-test',
|
|
|
|
|
async () => {
|
|
|
|
|
const einvoice = await EInvoice.fromXml(test.xml);
|
|
|
|
|
return await einvoice.validate();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
console.log(`${test.name}: ${validation.valid ? 'VALID' : 'INVALID'}`);
|
|
|
|
|
console.log(` ${test.description}`);
|
|
|
|
|
|
|
|
|
|
if (!test.shouldBeValid && !validation.valid) {
|
|
|
|
|
console.log(` ✓ Correctly detected tax calculation error`);
|
|
|
|
|
if (validation.errors) {
|
|
|
|
|
const taxErrors = validation.errors.filter(e =>
|
|
|
|
|
e.message && (
|
|
|
|
|
e.message.toLowerCase().includes('tax') ||
|
|
|
|
|
e.message.toLowerCase().includes('vat') ||
|
|
|
|
|
e.message.toLowerCase().includes('calculation')
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
console.log(` Tax calculation errors: ${taxErrors.length}`);
|
|
|
|
|
}
|
|
|
|
|
} else if (test.shouldBeValid && validation.valid) {
|
|
|
|
|
console.log(` ✓ Correctly validated tax calculation`);
|
|
|
|
|
} else {
|
|
|
|
|
console.log(` ○ Unexpected result (tax calculation validation may need implementation)`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(`${test.name}: Error - ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('VAL-05: Rounding and Precision Validation - should handle rounding correctly', async () => {
|
|
|
|
|
const { EInvoice } = await import('../../../ts/index.js');
|
|
|
|
|
|
|
|
|
|
const roundingTests = [
|
|
|
|
|
{
|
|
|
|
|
name: 'Proper rounding to 2 decimal places',
|
|
|
|
|
xml: `<?xml version="1.0"?>
|
|
|
|
|
<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-001</cbc:ID>
|
|
|
|
|
<cac:InvoiceLine>
|
|
|
|
|
<cbc:ID>1</cbc:ID>
|
|
|
|
|
<cbc:InvoicedQuantity unitCode="EA">3</cbc:InvoicedQuantity>
|
|
|
|
|
<cbc:LineExtensionAmount currencyID="EUR">10.00</cbc:LineExtensionAmount>
|
|
|
|
|
<cac:Price>
|
|
|
|
|
<cbc:PriceAmount currencyID="EUR">3.33</cbc:PriceAmount>
|
|
|
|
|
</cac:Price>
|
|
|
|
|
</cac:InvoiceLine>
|
|
|
|
|
</Invoice>`,
|
|
|
|
|
description: '3 × 3.33 = 9.99 ≈ 10.00 (acceptable rounding)'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Excessive precision',
|
|
|
|
|
xml: `<?xml version="1.0"?>
|
|
|
|
|
<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-002</cbc:ID>
|
|
|
|
|
<cac:InvoiceLine>
|
|
|
|
|
<cbc:ID>1</cbc:ID>
|
|
|
|
|
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
|
|
|
|
|
<cbc:LineExtensionAmount currencyID="EUR">10.123456789</cbc:LineExtensionAmount>
|
|
|
|
|
<cac:Price>
|
|
|
|
|
<cbc:PriceAmount currencyID="EUR">10.123456789</cbc:PriceAmount>
|
|
|
|
|
</cac:Price>
|
|
|
|
|
</cac:InvoiceLine>
|
|
|
|
|
</Invoice>`,
|
|
|
|
|
description: 'Amounts with excessive decimal precision'
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const test of roundingTests) {
|
|
|
|
|
try {
|
|
|
|
|
const { result: validation } = await PerformanceTracker.track(
|
|
|
|
|
'rounding-validation-test',
|
|
|
|
|
async () => {
|
|
|
|
|
const einvoice = await EInvoice.fromXml(test.xml);
|
|
|
|
|
return await einvoice.validate();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
console.log(`${test.name}: ${validation.valid ? 'VALID' : 'INVALID'}`);
|
|
|
|
|
console.log(` ${test.description}`);
|
|
|
|
|
|
|
|
|
|
if (!validation.valid && validation.errors) {
|
|
|
|
|
const roundingErrors = validation.errors.filter(e =>
|
|
|
|
|
e.message && (
|
|
|
|
|
e.message.toLowerCase().includes('rounding') ||
|
|
|
|
|
e.message.toLowerCase().includes('precision') ||
|
|
|
|
|
e.message.toLowerCase().includes('decimal')
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
console.log(` Rounding/precision errors: ${roundingErrors.length}`);
|
|
|
|
|
} else {
|
|
|
|
|
console.log(` No rounding/precision issues detected`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(`${test.name}: Error - ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('VAL-05: Complex Calculation Scenarios - should handle complex invoice calculations', async () => {
|
|
|
|
|
const { EInvoice } = await import('../../../ts/index.js');
|
|
|
|
|
|
|
|
|
|
// Test with a complex invoice involving discounts, allowances, and charges
|
|
|
|
|
const complexCalculationXml = `<?xml version="1.0"?>
|
|
|
|
|
<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>COMPLEX-CALC</cbc:ID>
|
|
|
|
|
<cac:InvoiceLine>
|
|
|
|
|
<cbc:ID>1</cbc:ID>
|
|
|
|
|
<cbc:InvoicedQuantity unitCode="EA">10</cbc:InvoicedQuantity>
|
|
|
|
|
<cbc:LineExtensionAmount currencyID="EUR">900.00</cbc:LineExtensionAmount>
|
|
|
|
|
<cac:Price>
|
|
|
|
|
<cbc:PriceAmount currencyID="EUR">100.00</cbc:PriceAmount>
|
|
|
|
|
</cac:Price>
|
|
|
|
|
<cac:AllowanceCharge>
|
|
|
|
|
<cbc:ChargeIndicator>false</cbc:ChargeIndicator>
|
|
|
|
|
<cbc:Amount currencyID="EUR">100.00</cbc:Amount>
|
|
|
|
|
</cac:AllowanceCharge>
|
|
|
|
|
</cac:InvoiceLine>
|
|
|
|
|
<cac:TaxTotal>
|
|
|
|
|
<cbc:TaxAmount currencyID="EUR">171.00</cbc:TaxAmount>
|
|
|
|
|
</cac:TaxTotal>
|
|
|
|
|
<cac:LegalMonetaryTotal>
|
|
|
|
|
<cbc:LineExtensionAmount currencyID="EUR">900.00</cbc:LineExtensionAmount>
|
|
|
|
|
<cbc:TaxExclusiveAmount currencyID="EUR">900.00</cbc:TaxExclusiveAmount>
|
|
|
|
|
<cbc:TaxInclusiveAmount currencyID="EUR">1071.00</cbc:TaxInclusiveAmount>
|
|
|
|
|
</cac:LegalMonetaryTotal>
|
|
|
|
|
</Invoice>`;
|
|
|
|
|
|
|
|
|
|
console.log('Testing complex calculation scenario');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const { result: validation, metric } = await PerformanceTracker.track(
|
|
|
|
|
'complex-calculation-test',
|
|
|
|
|
async () => {
|
|
|
|
|
const einvoice = await EInvoice.fromXml(complexCalculationXml);
|
|
|
|
|
return await einvoice.validate();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
console.log(`Complex calculation: ${validation.valid ? 'VALID' : 'INVALID'}`);
|
|
|
|
|
console.log(`Validation time: ${metric.duration.toFixed(2)}ms`);
|
|
|
|
|
console.log(`Calculation: 10×100 - 100 = 900, VAT: 171, Total: 1071`);
|
|
|
|
|
|
|
|
|
|
if (!validation.valid && validation.errors) {
|
|
|
|
|
const calcErrors = validation.errors.filter(e =>
|
|
|
|
|
e.message && e.message.toLowerCase().includes('calculation')
|
|
|
|
|
);
|
|
|
|
|
console.log(`Calculation issues found: ${calcErrors.length}`);
|
|
|
|
|
} else {
|
|
|
|
|
console.log(`Complex calculation validated successfully`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Should handle complex calculations efficiently
|
|
|
|
|
expect(metric.duration).toBeLessThan(100);
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(`Complex calculation test error: ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.start();
|