This page explains how money is handled in Cluster ERP: the project rule, the code in core/currencies.py and in the services that compute amounts, and the tests that guard the rule. It covers currencies and exchange rates only as far as they affect amounts; the currencies module page covers the rest.
In short¶
- The project's written engineering rules require
Decimalfor every money value,Numericin the database, strings in JSON, and banker's rounding (ROUND_HALF_EVEN). - A currency's number of decimal places is reference data in
core_currencies.decimal_places, not a constant (src/cluster_erp/core/currencies.py:86). - Exchange rates are
Numeric(18, 6)columns and are serialised as strings (src/cluster_erp/core/currencies.py:110-121,src/cluster_erp/core/currencies.py:186-190). ROUND_HALF_EVENis imported in 16 Python files undersrc/cluster_erp(counted withgrep -rln ROUND_HALF_EVEN);ROUND_HALF_UPappears in none.- A test checks that every monetary analytics measure sits on a
Numericcolumn, not aFloatcolumn (tests/test_analytics_contributions.py:120-133).
How it works¶
Currency precision from reference data¶
Currency rows carry code, numeric_code, name, symbol, decimal_places, translations, and is_active (src/cluster_erp/core/currencies.py:57-89). The rows are seeded from the ISO 4217 set: a curated list with symbols and translations, completed from the pycountry package (src/cluster_erp/core/currencies.py:559, src/cluster_erp/core/currencies.py:1811-1852). See reference data and core i18n.
Services that round a money total read the precision from this table. money_scale in the quotations module looks up Currency.decimal_places for the document currency and returns Decimal(1).scaleb(-decimals); it falls back to 2 places when the currency is unknown or unset (src/cluster_erp/modules/quotations/rollup.py:31-46). The sales_orders module has its own money_scale (src/cluster_erp/modules/sales_orders/rollup.py:24).
Rounding¶
The files that quantize money import ROUND_HALF_EVEN, for example src/cluster_erp/modules/quotations/rollup.py:21. The modules with the most occurrences are procurement (MRP engine and routes), costing, projects, nesting, and bom (counted with grep -rn ROUND_HALF_EVEN, grouped by file).
Exchange-rate conversion quantizes to six decimal places with quantize(Decimal("0.000001")) and no explicit rounding argument (src/cluster_erp/core/currencies.py:423-443). The rounding mode then comes from the active decimal context. No code in src/cluster_erp changes the decimal context (searched for getcontext, localcontext, and setcontext). The default context of Python's decimal module is described in the Python documentation.
Quantities are not money and use other modes where the code needs them, for example ROUND_CEILING for the number of stock units needed (src/cluster_erp/core/stock_conversion.py:65-67).
JSON transport as strings¶
ExchangeRateReadandConvertResultdeclare field serialisers that turnDecimalvalues into strings (src/cluster_erp/core/currencies.py:173-204).- A tenant-defined field of type
decimalis stored in JSONB as a string, never as a float (src/cluster_erp/core/tenant_fields.py:875-877). The testtests/test_tenant_fields.py:59-61checks thatcoerce_valuereturns a string for both"12.50"and3. - The change history turns
Decimalvalues into strings in its JSON diff (src/cluster_erp/core/change_history.py:108-109).
Currency codes must exist¶
assert_known_currency rejects a currency code that is not an active row of core_currencies, with error code CURRENCY_UNKNOWN and HTTP 422 (src/cluster_erp/core/currencies.py:27-49). Its docstring states the reason: a format check alone would let a direct API call store a code such as ZZZ (src/cluster_erp/core/currencies.py:31-36).
Errors from numeric input¶
A decimal.InvalidOperation raised while processing a request becomes VALIDATION_ERROR with HTTP 422, and a database DataError (for example a numeric overflow) also becomes VALIDATION_ERROR with HTTP 422 (src/cluster_erp/server/error_handler.py:133-148). See errors and exceptions.
Why it is built this way¶
The project's written engineering rules state the rule without a further reason beyond the rule itself. The docstring of test_monetary_measures_are_not_float_backed calls it "an absolute project rule" (tests/test_analytics_contributions.py:121-123). The docstring of assert_known_currency states why currency codes must come from reference data (src/cluster_erp/core/currencies.py:31-36). Rationale for choosing banker's rounding is not determined from code.
Rules and boundaries¶
- Money is
Decimalin Python andNumericin the database. The analytics test enforcesNumericfor monetary measures (tests/test_analytics_contributions.py:120-133). - Currency symbols, decimal places, and names come from
core_currencies(src/cluster_erp/core/currencies.py:57-89). - Money values in API samples in this wiki are JSON strings, following the documentation standard.
Interaction with other parts¶
- The
currenciesmodule mounts the currency routes and owns exchange rates and payment terms (src/cluster_erp/modules/currencies/module.py:64-82). See currencies. - Analytics declares monetary measures with a
currency_field(tests/test_analytics_contributions.py:127-128). See analytics facts.
Limits and open points¶
- Some settings that look like money have other types:
pr_approval_threshold_amountis anintandmrp_eoq_ordering_costis astr(src/cluster_erp/config/settings.py:603,src/cluster_erp/config/settings.py:623). How each is converted where it is read was not traced for this page. - Whether every Pydantic schema that carries money serialises it as a string was not audited for this page. It is not determined from code here.
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| banker's rounding | ROUND_HALF_EVEN |
Rounding to the nearest value, with ties going to the even digit. | bankacı yuvarlaması | src/cluster_erp/modules/quotations/rollup.py:21 |
| money scale | money_scale |
The quantize step derived from a currency's decimal places. | para ölçeği | src/cluster_erp/modules/quotations/rollup.py:35-46 |
| currency | Currency |
A reference-data row for an ISO 4217 currency with its code, symbol, and decimal places. | para birimi | src/cluster_erp/core/currencies.py:57 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.