Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/round-cumulative-breakdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@epilot/pricing": minor
---

`computeCumulativeValue` rounds its `breakdown` for display like its totals: each tier's `quantityUsed` to 2 decimals and its `totalAmountDecimal` to 2 decimals (`precision: 2`, as `total` is). A quantity normalised from a yearly consumption used to show as `83.333333 kWh` with an amount of `€28.333333333333`. `tierAmountDecimal` keeps the tier's real precision, and `totalWithPrecision` / `subtotalWithPrecision` are unchanged.
28 changes: 26 additions & 2 deletions src/tiers/compute-cumulative-value.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ describe('computeCumulativeValue', () => {
${onRequestTiers} | ${5} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${true} | ${undefined} | ${{ rate: 19 }} | ${{ total: '€50.00', totalWithPrecision: '€50.00', average: '€10.00/kWh', subAverage: '€8.40/kWh', subtotal: '€42.02', subtotalWithPrecision: '€42.016806722689', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '€10.00/kWh', totalAmountDecimal: '€50.00' }] }}
${onRequestTiers} | ${15} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${true} | ${undefined} | ${{ rate: 19 }} | ${'Price on request'}
${onRequestTiers} | ${15} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${false} | ${undefined} | ${{ rate: 19 }} | ${{ total: '€145.00', totalWithPrecision: '€145.00', average: '€9.67/kWh', subAverage: '€8.12/kWh', subtotal: '€121.85', subtotalWithPrecision: '€121.848739495798', breakdown: [{ quantityUsed: '10 kWh', tierAmountDecimal: '€10.00/kWh', totalAmountDecimal: '€100.00' }, { quantityUsed: '5 kWh', tierAmountDecimal: '€9.00/kWh', totalAmountDecimal: '€45.00' }] }}
${decimalTiers} | ${7} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${false} | ${undefined} | ${{ rate: 19 }} | ${{ total: '38.59 cents', totalWithPrecision: '38.5915 cents', average: '5.51 cents/kWh', subAverage: '4.63 cents/kWh', subtotal: '32.43 cents', subtotalWithPrecision: '32.429832 cents', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '5.5451 cents/kWh', totalAmountDecimal: '27.7255 cents' }, { quantityUsed: '2 kWh', tierAmountDecimal: '5.433 cents/kWh', totalAmountDecimal: '10.866 cents' }] }}
${decimalTiers} | ${7} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${false} | ${true} | ${{ rate: 19 }} | ${{ total: 'Starts at 38.59 cents', totalWithPrecision: 'Starts at 38.5915 cents', average: '5.51 cents/kWh', subAverage: '4.63 cents/kWh', subtotal: 'Starts at 32.43 cents', subtotalWithPrecision: 'Starts at 32.429832 cents', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '5.5451 cents/kWh', totalAmountDecimal: '27.7255 cents' }, { quantityUsed: '2 kWh', tierAmountDecimal: '5.433 cents/kWh', totalAmountDecimal: '10.866 cents' }] }}
${decimalTiers} | ${7} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${false} | ${undefined} | ${{ rate: 19 }} | ${{ total: '38.59 cents', totalWithPrecision: '38.5915 cents', average: '5.51 cents/kWh', subAverage: '4.63 cents/kWh', subtotal: '32.43 cents', subtotalWithPrecision: '32.429832 cents', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '5.5451 cents/kWh', totalAmountDecimal: '27.73 cents' }, { quantityUsed: '2 kWh', tierAmountDecimal: '5.433 cents/kWh', totalAmountDecimal: '10.87 cents' }] }}
${decimalTiers} | ${7} | ${'kWh'} | ${'en'} | ${'EUR'} | ${undefined} | ${false} | ${true} | ${{ rate: 19 }} | ${{ total: 'Starts at 38.59 cents', totalWithPrecision: 'Starts at 38.5915 cents', average: '5.51 cents/kWh', subAverage: '4.63 cents/kWh', subtotal: 'Starts at 32.43 cents', subtotalWithPrecision: 'Starts at 32.429832 cents', breakdown: [{ quantityUsed: '5 kWh', tierAmountDecimal: '5.5451 cents/kWh', totalAmountDecimal: '27.73 cents' }, { quantityUsed: '2 kWh', tierAmountDecimal: '5.433 cents/kWh', totalAmountDecimal: '10.87 cents' }] }}
`(
'should compute cumulative value correctly when quantityToSelectTier=$quantityToSelectTier, isTaxInclusive=$isTaxInclusive and tax=$tax',
({
Expand Down Expand Up @@ -105,4 +105,28 @@ describe('computeCumulativeValue', () => {
expect(result).toEqual(expected);
},
);

it('rounds the breakdown of a fractional quantity like the totals', () => {
// 1000 kWh a year, normalised to a monthly price: 83.333… kWh
const result = computeCumulativeValue(
[
{ up_to: 50, unit_amount: 34, unit_amount_decimal: '0.3456' },
{ up_to: null, unit_amount: 30, unit_amount_decimal: '0.30' },
],
1000 / 12,
'kWh',
'en',
'EUR',
mockedTranslationFn,
);

expect(result).toMatchObject({
total: '€27.28',
breakdown: [
// The unit price keeps its real precision.
{ quantityUsed: '50 kWh', tierAmountDecimal: '34.56 cents/kWh', totalAmountDecimal: '€17.28' },
{ quantityUsed: '33.33 kWh', tierAmountDecimal: '30.00 cents/kWh', totalAmountDecimal: '€10.00' },
],
});
});
});
7 changes: 6 additions & 1 deletion src/tiers/compute-cumulative-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,21 @@ export const computeCumulativeValue = (
const unitAmountWithTax = isTaxInclusive ? unitAmount : unitAmount.multiply(1 + taxRate);

breakdown.push({
// Rounded for display like the totals: a quantity normalised from a yearly
// consumption is a repeating decimal (1000 / 12 = 83.333…).
quantityUsed: `${graduatedQuantity.toLocaleString(formatOptions.locale, {
maximumFractionDigits: 6,
maximumFractionDigits: 2,
})} ${formattedUnit}`,
tierAmountDecimal: `${formatAmountFromString({
decimalAmount: addSeparatorToDineroString(unitAmountWithTax.getAmount().toString()),
...formatOptions,
})}${formattedUnit ? `/${formattedUnit}` : ''}`,
// The tier's unit price keeps its real precision; its amount is rounded like `total`.
totalAmountDecimal: formatAmountFromString({
decimalAmount: addSeparatorToDineroString(tierAmountWithTax.getAmount().toString()),
...formatOptions,
precision: 2,
useRealPrecision: false,
}),
});

Expand Down
Loading