Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Decision records

Explanation

Decimal money

Decision record for the rule that every money value in Cluster ERP is a Decimal, stored as Numeric, sent as a JSON string, and rounded with banker's rounding.

Status: Accepted, stated in the project's written engineering rules. This record covers how Cluster ERP represents money: Python Decimal, database Numeric, JSON strings, and ROUND_HALF_EVEN. The mechanisms, including currency precision from reference data, are explained on money and Decimal.

Context

An ERP computes prices, totals, costs, and exchange rates. The project's written engineering rules forbid float for money and list the representation for each layer. A test docstring calls it "an absolute project rule" (tests/test_analytics_contributions.py:121-123). The rules also require that currency symbols, decimals, and formats come from reference data, not from code. Rationale for choosing banker's rounding is not determined from code.

Decision

  • Every monetary value is a Decimal in Python and Numeric in the database.
  • JSON carries money as a string.
  • Rounding uses ROUND_HALF_EVEN.
  • The currency and its number of decimal places come from reference data.

The flowchart shows the representation at each layer.

flowchart LR
 db["PostgreSQL Numeric column"] --> py["Python Decimal"]
 py --> calc["arithmetic and quantize with ROUND_HALF_EVEN"]
 calc --> ser["field_serializer returns str"]
 ser --> json["JSON string value"]
 json --> client["client keeps the string"]

In prose: a value leaves the database as Decimal, is computed and rounded as Decimal, and is turned into a string by the schema before it reaches JSON. ExchangeRateRead and ConvertResult show the serialiser pattern (src/cluster_erp/core/currencies.py:186-204).

Consequences

  • No file under src/cluster_erp declares a Mapped[float] column (searched with grep -rn "Mapped\[float\]").
  • ROUND_HALF_EVEN appears in 32 files under src/cluster_erp, and ROUND_HALF_UP appears in none (counted with grep -rln).
  • The change history writes Decimal values as strings in its JSON diff (src/cluster_erp/core/change_history.py:108-109).
  • A tenant-defined field of type decimal is stored in JSONB as a string (src/cluster_erp/core/tenant_fields.py:875-877).
  • assert_known_currency rejects a currency code that is not live, active reference data (src/cluster_erp/core/currencies.py:30-40).
  • Money samples in this wiki are written as strings, for example "amount": "1250.00".

Where the code enforces it

Mechanism What it checks Source
test_monetary_measures_are_not_float_backed Every monetary analytics measure sits on a Numeric column that is not a Float tests/test_analytics_contributions.py:115-134
tests/test_tenant_fields.py A decimal tenant field returns a string, never a float tests/test_tenant_fields.py:61-62
field_serializer on read schemas Decimal fields leave the API as strings src/cluster_erp/core/currencies.py:186-204
assert_known_currency Currency codes are live reference data src/cluster_erp/core/currencies.py:30-40

Not determined from code: a general test that every Pydantic schema with a money field serialises it as a string. None was found in tests/.


Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13