fix(18-07): money accepts trailing zeros beyond the field scale

A scale-2 money field over a four-decimal column (the documented fee
example) was served "12.5000" and answered money.too_precise when the
untouched value was saved back. Trailing zeros carry no precision, so the
server check and the SPA parser count only significant fraction digits;
the value still reaches the column unchanged, never rounded or truncated.

- cabana validateMoneyValue and admin parseMoneyInput trim trailing zeros
  before the scale comparison
- forms docs and cabana README name the rule; embedded dist rebuilt
- MoneyField spec asserts 12.5000 at scale 2 is accepted, 12.5010 refused
This commit is contained in:
2026-10-13 16:53:58 +02:00
parent 5141059026
commit eb60c329d8
8 changed files with 20 additions and 12 deletions

View File

@@ -20,8 +20,10 @@ const digitsOnly = /^[0-9]+$/
* Turns typed text into the canonical money string: spaces and U+00A0 are
* stripped, one comma or dot is the decimal separator, an optional leading
* minus is kept, digits only otherwise. Empty text is null. More than
* `scale` fraction digits is `too_precise`: the value is never rounded or
* truncated. Anything else (letters, two separators, grouping) is `invalid`.
* `scale` significant fraction digits is `too_precise`: the value is never
* rounded or truncated, and trailing zeros beyond the scale are kept and
* accepted (they carry no precision), as the server does. Anything else
* (letters, two separators, grouping) is `invalid`.
*/
export function parseMoneyInput(text: string, scale: number): MoneyParseResult {
const compact = text.replace(/[\s ]/g, '')
@@ -42,7 +44,7 @@ export function parseMoneyInput(text: string, scale: number): MoneyParseResult {
if (!digitsOnly.test(whole ?? '') || (separators.length === 1 && !digitsOnly.test(fraction))) {
return { value: null, error: 'invalid' }
}
if (fraction.length > scale) {
if (fraction.replace(/0+$/, '').length > scale) {
return { value: null, error: 'too_precise' }
}
const canonical = fraction === '' ? whole : `${whole}.${fraction}`