Files
einvoice/test/test.decimal-currency-calculator.ts
Juergen Kunz cbb297b0b1 feat: Implement PEPPOL and XRechnung validators for compliance with e-invoice specifications
- Added PeppolValidator class to validate PEPPOL BIS 3.0 invoices, including checks for endpoint IDs, document type IDs, process IDs, party identification, and business rules.
- Implemented validation for GLN check digits, document types, and transport protocols specific to PEPPOL.
- Added XRechnungValidator class to validate XRechnung 3.0 invoices, focusing on German-specific requirements such as Leitweg-ID, payment details, seller contact, and tax registration.
- Included validation for IBAN and BIC formats, ensuring compliance with SEPA regulations.
- Established methods for checking B2G invoice indicators and validating mandatory fields for both validators.
2025-08-11 18:07:01 +00:00

184 lines
6.4 KiB
TypeScript

import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DecimalCurrencyCalculator } from '../ts/formats/utils/currency.calculator.decimal.js';
import { Decimal } from '../ts/formats/utils/decimal.js';
tap.test('DecimalCurrencyCalculator - EUR calculations', async () => {
const calculator = new DecimalCurrencyCalculator('EUR');
// Line calculation
const lineNet = calculator.calculateLineNet('3', '33.333', '0');
expect(lineNet.toString()).toEqual('100'); // calculateLineNet rounds the result
// VAT calculation
const vat = calculator.calculateVAT('100', '19');
expect(vat.toString()).toEqual('19');
// Gross amount
const gross = calculator.calculateGrossAmount('100', '19');
expect(gross.toString()).toEqual('119');
});
tap.test('DecimalCurrencyCalculator - JPY calculations (no decimals)', async () => {
const calculator = new DecimalCurrencyCalculator('JPY');
// Should round to 0 decimal places
const amount = calculator.round('1234.56');
expect(amount.toString()).toEqual('1235');
// VAT calculation
const vat = calculator.calculateVAT('1000', '10');
expect(vat.toString()).toEqual('100');
});
tap.test('DecimalCurrencyCalculator - KWD calculations (3 decimals)', async () => {
const calculator = new DecimalCurrencyCalculator('KWD');
// Should maintain 3 decimal places
const amount = calculator.round('123.4567');
expect(amount.toString()).toEqual('123.457');
// VAT calculation
const vat = calculator.calculateVAT('100.000', '5');
expect(vat.toString()).toEqual('5');
});
tap.test('DecimalCurrencyCalculator - sum line items', async () => {
const calculator = new DecimalCurrencyCalculator('EUR');
const items = [
{ quantity: '2', unitPrice: '50.00', discount: '5.00' },
{ quantity: '3', unitPrice: '33.33', discount: '0' },
{ quantity: '1', unitPrice: '100.00', discount: '10.00' }
];
const total = calculator.sumLineItems(items);
expect(total.toString()).toEqual('284.99');
});
tap.test('DecimalCurrencyCalculator - VAT breakdown', async () => {
const calculator = new DecimalCurrencyCalculator('EUR');
const items = [
{ netAmount: '100.00', vatRate: '19' },
{ netAmount: '50.00', vatRate: '19' },
{ netAmount: '200.00', vatRate: '7' }
];
const breakdown = calculator.calculateVATBreakdown(items);
expect(breakdown).toHaveLength(2);
const vat19 = breakdown.find(b => b.rate.toString() === '19');
expect(vat19?.baseAmount.toString()).toEqual('150');
expect(vat19?.vatAmount.toString()).toEqual('28.5');
const vat7 = breakdown.find(b => b.rate.toString() === '7');
expect(vat7?.baseAmount.toString()).toEqual('200');
expect(vat7?.vatAmount.toString()).toEqual('14');
});
tap.test('DecimalCurrencyCalculator - distribute amount', async () => {
const calculator = new DecimalCurrencyCalculator('EUR');
// Distribute 100 EUR across three items
const items = [
{ value: '30' }, // 30%
{ value: '50' }, // 50%
{ value: '20' } // 20%
];
const distributed = calculator.distributeAmount('100', items);
expect(distributed[0].toString()).toEqual('30');
expect(distributed[1].toString()).toEqual('50');
expect(distributed[2].toString()).toEqual('20');
// Sum should equal total
const sum = Decimal.sum(distributed);
expect(sum.toString()).toEqual('100');
});
tap.test('DecimalCurrencyCalculator - compound adjustments', async () => {
const calculator = new DecimalCurrencyCalculator('EUR');
const adjustments = [
{ type: 'allowance' as const, value: '10', isPercentage: true }, // -10%
{ type: 'charge' as const, value: '5', isPercentage: false }, // +5 EUR
{ type: 'allowance' as const, value: '2', isPercentage: false } // -2 EUR
];
const result = calculator.calculateCompoundAmount('100', adjustments);
// 100 - 10% = 90, + 5 = 95, - 2 = 93
expect(result.toString()).toEqual('93');
});
tap.test('DecimalCurrencyCalculator - validation', async () => {
const calculator = new DecimalCurrencyCalculator('EUR');
// Valid calculation
const result1 = calculator.validateCalculation('119.00', '119.00', 'BR-CO-15');
expect(result1.valid).toBeTrue();
expect(result1.expected).toEqual('119.00');
expect(result1.calculated).toEqual('119.00');
// Invalid calculation
const result2 = calculator.validateCalculation('119.00', '118.99', 'BR-CO-15');
expect(result2.valid).toBeFalse();
expect(result2.difference).toEqual('0.01');
});
tap.test('DecimalCurrencyCalculator - different rounding modes', async () => {
// HALF_DOWN for specific requirements
const calculator = new DecimalCurrencyCalculator('EUR', 'HALF_DOWN');
const amount1 = calculator.round('10.125'); // Should round down
expect(amount1.toString()).toEqual('10.12');
const amount2 = calculator.round('10.135'); // Should round down with HALF_DOWN
expect(amount2.toString()).toEqual('10.13');
// HALF_EVEN (Banker's rounding) for statistical accuracy
const bankerCalc = new DecimalCurrencyCalculator('EUR', 'HALF_EVEN');
const amount3 = bankerCalc.round('10.125'); // Round to even (down)
expect(amount3.toString()).toEqual('10.12');
const amount4 = bankerCalc.round('10.135'); // Round to even (up)
expect(amount4.toString()).toEqual('10.14');
});
tap.test('DecimalCurrencyCalculator - real invoice scenario', async () => {
const calculator = new DecimalCurrencyCalculator('EUR');
// Invoice lines
const lines = [
{ quantity: '2.5', unitPrice: '45.60', discount: '5.00' },
{ quantity: '10', unitPrice: '12.34', discount: '0' },
{ quantity: '1', unitPrice: '250.00', discount: '25.00' }
];
// Calculate line totals
const lineTotal = calculator.sumLineItems(lines);
expect(lineTotal.toString()).toEqual('457.4');
// Apply document-level allowance (2%)
const allowance = calculator.calculatePaymentDiscount(lineTotal, '2');
expect(allowance.toString()).toEqual('9.15');
const netAfterAllowance = lineTotal.subtract(allowance);
expect(calculator.round(netAfterAllowance).toString()).toEqual('448.25');
// Calculate VAT at 19%
const vat = calculator.calculateVAT(netAfterAllowance, '19');
expect(vat.toString()).toEqual('85.17');
// Total with VAT
const total = calculator.calculateGrossAmount(netAfterAllowance, vat);
expect(total.toString()).toEqual('533.42');
// Format for display
const formatted = calculator.formatAmount(total);
expect(formatted).toEqual('533.42 EUR');
});
export default tap.start();