mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 06:56:35 +00:00
fix(budget): keep an uneven split from corrupting the expense total
163.21 across two people is 81.61 and 81.60. Both are exact to the cent — the client splits properly — but the server discarded the total it was sent and re-derived it by adding those two as doubles, which lands on 163.20999999999998. That is the number that went into the row. It looked like a display quirk because everything routed through Intl came out right; only the places that print a number as it stands showed it. The stored value was wrong all along, which is why reopening the expense showed it back, and why the price stamped onto a linked booking carried it too. The domain already had this rule and followed it everywhere else — the settlement maths converts to whole cents before it adds anything. The payer sum was the one arithmetic still done in euros, in both the create and the update path, so it now goes through the same rule. The client side is what makes rows that are already stored wrong stop looking wrong: the expense form and the mobile cost sheet seed their total from a cent-clean value, and the three panels that print a booking's price verbatim tidy it when it parses as a number. Free text an import wrote there passes through untouched rather than becoming NaN. Closes #1964
This commit is contained in:
@@ -85,6 +85,34 @@ export function localizeAmountInput(value: string | number | null | undefined, c
|
||||
return currencyDecimalSeparator(currency) === ',' ? s.replace('.', ',') : s
|
||||
}
|
||||
|
||||
/**
|
||||
* The same amount, without the float noise an uneven split leaves behind (#1964).
|
||||
*
|
||||
* A total that does not divide evenly is split into parts that are each exact
|
||||
* to the cent, but adding those parts back up in floating point does not land
|
||||
* on the total: 81.61 + 81.60 is 163.20999999999998. Anything that reaches
|
||||
* formatMoney is fine, since Intl rounds — this is for the places that print a
|
||||
* number as it stands, which is where the reporter saw it: the expense form
|
||||
* when reopened, and the price stamped onto a linked booking.
|
||||
*
|
||||
* In cents, matching the server's own money arithmetic rather than the
|
||||
* currency's decimal count, so the two agree on what a clean value is.
|
||||
*/
|
||||
export function cleanAmount(value: number): number {
|
||||
return Math.round(value * 100) / 100
|
||||
}
|
||||
|
||||
/**
|
||||
* A price held as free text, tidied only when it really is a number. Import
|
||||
* writes whatever the confirmation said into this field, so anything that does
|
||||
* not parse is passed through untouched rather than mangled into NaN.
|
||||
*/
|
||||
export function cleanAmountText(value: string | number | null | undefined): string {
|
||||
if (value == null) return ''
|
||||
const n = Number(value)
|
||||
return Number.isFinite(n) && String(value).trim() !== '' ? String(cleanAmount(n)) : String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Locale- and currency-correct money formatting via Intl: the symbol position,
|
||||
* thousands/decimal separators and decimal count all follow the user's locale
|
||||
|
||||
Reference in New Issue
Block a user