Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Platform

Explanation

Money and Decimal

How Cluster ERP keeps money as Decimal, stores it as Numeric, sends it as JSON strings, rounds it, and takes currency precision from reference data.

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 Decimal for every money value, Numeric in 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_EVEN is imported in 16 Python files under src/cluster_erp (counted with grep -rln ROUND_HALF_EVEN); ROUND_HALF_UP appears in none.
  • A test checks that every monetary analytics measure sits on a Numeric column, not a Float column (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

  • ExchangeRateRead and ConvertResult declare field serialisers that turn Decimal values into strings (src/cluster_erp/core/currencies.py:173-204).
  • A tenant-defined field of type decimal is stored in JSONB as a string, never as a float (src/cluster_erp/core/tenant_fields.py:875-877). The test tests/test_tenant_fields.py:59-61 checks that coerce_value returns a string for both "12.50" and 3.
  • The change history turns Decimal values 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 Decimal in Python and Numeric in the database. The analytics test enforces Numeric for 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 currencies module 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_amount is an int and mrp_eoq_ordering_cost is a str (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

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

Verified against commit 92f8051 · 2026-11-13