Repository navigation
docs(currencies): spec for arbitrary-precision money and fx - #144
patrykbojczuk wants to merge 7 commits into
Conversation
Frshy
left a comment
There was a problem hiding this comment.
Summary
Adds a design spec with no code changes. It plans how Open Mercato would store and calculate money and exchange rates exactly, with no fixed decimal limit.
🎯 Money and FX rates lose digits
Today money columns have a fixed number of decimals (2 or 4), and exchange rates have 8. Calculations run on JavaScript floats with hard-coded rounding. Crypto currencies such as BTC (8 decimals) and ETH (18) cannot be stored correctly. Very small or inverted exchange rates get cut down to a few significant digits. Exchange rates also have nowhere to keep extra context, such as a provider's table number.
🔧 Unlimited columns, exact decimal math
The spec makes about 100 money columns and both exchange-rate columns unlimited Postgres numeric. All money math moves to exact decimal arithmetic using big.js, wrapped in a new @open-mercato/shared/lib/decimal toolkit. Only division gets rounded: amounts to max(4, currency decimals), FX to 50 decimals. Exchange rates gain a free-form metadata jsonb column.
🔍 Details
- Compatibility: existing
numberfields in TS types and API responses stay. Each gets a<field>Exact: stringsibling. - Validators: they keep
numberoutput. Commands read the exact digits from the raw request throughwithExactAmounts. - Old calculation hooks: a "hook bridge" spots hooks that only change the
numberfield and rebuilds the exact value from it. - Precision lookup: a new
currencyPrecisionServicesupplies each currency's decimal places. ThedecimalPlacescap rises from 8 to 50. - Staff exception: staff amounts round to the currency's own decimals (2 for fiat), matching today's
round2instaff/lib/time-tracking/cost.ts.
⚠️ Worth knowing
- The implementation is on a separate branch,
pb/exchange-rate-metadata. This PR only lands the document. - Rows written after the change store values unpadded (
40instead of40.0000), so string comparisons may need updating. - Negative half values will round away from zero, which differs from
Math.round.
Walkthrough
Arbitrary-precision money and FX spec
A single new spec covering the column changes, the decimal toolkit, the compatibility plan and the risks. Check the sections against each other: the Decisions table and Changelog were updated after implementation, but the TLDR and the Migration & Backward Compatibility table still describe the earlier plan.
Reviewed with cerber 🐕 — drafted by AI, sent by a human.
| | DB schema | `numeric(p,s)` → `numeric` on money/FX columns; new nullable `exchange_rates.metadata` | ✓ ADDITIVE (widening) | | ||
| | TS types (`sales/lib/types.ts`, tax service, providers, `shared/modules/payment_gateways/types.ts`, staff `TimeRateResolver`) | `<field>Exact: string` added; `number` fields `@deprecated`; resolvers may return `number | string` | ✓ ADDITIVE + deprecation | | ||
| | Calculation hooks | Legacy number-only hooks keep working through the hook bridge | ✓ Behavior-preserving | | ||
| | Zod validators | Input widened to `number | string`; parsed output for amount fields changes from `number` to `string` | ⚠ Type change of `z.infer` output - documented in UPGRADE_NOTES | |
There was a problem hiding this comment.
The row says validators now output string and that this z.infer change is documented in UPGRADE_NOTES. D8 says output types stay number, and the changelog says validators keep number output. The BC table is what reviewers check under BACKWARD_COMPATIBILITY.md, so it should match what shipped. If output really stays number, change the row to "input widened to number | string; output unchanged" and mark it ADDITIVE.
There was a problem hiding this comment.
Agreed, the row was left over from the first plan. Validator output stays number (D8), so the row now says: parsed output unchanged; inputs that only took JSON numbers (gateway amounts, staff rate override) also accept decimal strings; bounds that mirrored the old column precision are removed. Classified ✓ ADDITIVE, which matches BACKWARD_COMPATIBILITY.md.
|
Re the walkthrough note that the TLDR and the BC table still described the earlier plan: agreed and fixed. Both now match D7/D8 (float copies, validators keep |
|
Superseded with implementation #145 |
Summary
Spec only:
.ai/specs/2026-10-08-arbitrary-precision-money-and-fx.md. No code changes.numericfor exchange rates (exchange_rates.rate,sales_orders.exchange_rate) and about 100 money columns.big.jsvia@open-mercato/shared/lib/decimal). Only division is cut: amounts at the currency precision, FX at 50 decimals, and never fewer than 17 significant digits.metadataon exchange rates.numberfields stay, with new<field>Exactstring siblings.The implementation lives on
pb/exchange-rate-metadata.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.