toBe(125000); // 0,25 months at 19,00 € — 4,75, not 4,74 and not 4,76 expect(InvoiceMath::lineNet(1900, 250))->toBe(475); }); it('applies a percentage to the sum, not line by line', function () { // Three lines at 3,33 € with 10 % off. Rounded per line, each discount is // 33 cents and the total is 99 — but 10 % of 9,99 € is 100 cents. One cent, // on the line the customer checks first. $lines = [ ['unit_net_cents' => 333, 'quantity_milli' => 1000], ['unit_net_cents' => 333, 'quantity_milli' => 1000], ['unit_net_cents' => 333, 'quantity_milli' => 1000], ]; $totals = InvoiceMath::totals($lines, ['type' => InvoiceMath::PERCENT, 'value' => -1000], 2000); expect($totals['lines_net'])->toBe(999) ->and($totals['adjustment'])->toBe(-100) ->and($totals['net'])->toBe(899); }); it('treats a surcharge as the same operation with the other sign', function () { // One rule, not two flags somebody has to remember the meaning of. $lines = [['unit_net_cents' => 10000, 'quantity_milli' => 1000]]; $discounted = InvoiceMath::totals($lines, ['type' => InvoiceMath::PERCENT, 'value' => -2000], 2000); $surcharged = InvoiceMath::totals($lines, ['type' => InvoiceMath::PERCENT, 'value' => 2000], 2000); expect($discounted['net'])->toBe(8000) ->and($surcharged['net'])->toBe(12000); }); it('takes a fixed amount off as readily as a percentage', function () { $lines = [['unit_net_cents' => 10000, 'quantity_milli' => 1000]]; $totals = InvoiceMath::totals($lines, ['type' => InvoiceMath::AMOUNT, 'value' => -1500], 2000); expect($totals['net'])->toBe(8500) ->and($totals['tax'])->toBe(1700) ->and($totals['gross'])->toBe(10200); }); it('adds up: net plus tax is gross, on the figures actually printed', function () { // The assertion that matters, because it is the one a reader performs. $lines = [ ['unit_net_cents' => 10000, 'quantity_milli' => 12000], ['unit_net_cents' => 31200, 'quantity_milli' => 20000], ]; $totals = InvoiceMath::totals($lines, ['type' => InvoiceMath::PERCENT, 'value' => -2000], 2000); expect($totals['lines_net'])->toBe(744000) ->and($totals['adjustment'])->toBe(-148800) ->and($totals['net'])->toBe(595200) ->and($totals['tax'])->toBe(119040) ->and($totals['gross'])->toBe(714240) ->and($totals['net'] + $totals['tax'])->toBe($totals['gross']); }); it('derives gross from net and never the other way round', function () { // 19,99 € net at 20 % is 23,99 €. Taking VAT back out of 23,99 gives 19,99 // here and 19,992 elsewhere — which is why only one direction is offered. $totals = InvoiceMath::totals([['unit_net_cents' => 1999, 'quantity_milli' => 1000]], null, 2000); expect($totals['tax'])->toBe(400) ->and($totals['gross'])->toBe(2399); }); it('formats money and quantities the way the document reads them', function () { expect(InvoiceMath::money(714240))->toBe('7.142,40') ->and(InvoiceMath::money(-14880))->toBe('-148,80') ->and(InvoiceMath::money(0))->toBe('0,00'); // "12,000 Std." reads as a defect. expect(InvoiceMath::quantity(12000))->toBe('12') ->and(InvoiceMath::quantity(1500))->toBe('1,5') ->and(InvoiceMath::quantity(250))->toBe('0,25'); });