taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

commit feb1b676ef62094c52f9b1bc9c83eaf60517bd04
parent 0d4ce0e1b6e62655552be8de29d90b9e3a5677f0
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Wed, 29 Oct 2025 14:33:22 +0100

dd072

Diffstat:
Adesign-documents/072-products-units.rst | 376+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdesign-documents/index.rst | 3++-
2 files changed, 378 insertions(+), 1 deletion(-)

diff --git a/design-documents/072-products-units.rst b/design-documents/072-products-units.rst @@ -0,0 +1,376 @@ +DD 72: Products Units +##################### + +Summary +======= + +Introduce canonical ``unit_*`` metadata for merchant inventory so prices and +stock levels can be expressed with fractional precision while retaining legacy +integer fields for backwards compatibility. Provide guidance to wallets, PoS +terminals, and merchant tooling to keep UX coherent across integrations. + +Motivation +========== + +Bug reports revealed two conflicting requirements: + +* Products sold by measurable attributes (for example potatoes by kilogram) + need fractional support so customers can order 1.5 kg without hacks. +* Discrete products (for example “pieces” of cheese) must remain integer-only; + allowing 1.2 pc would break inventory management. + +The existing API exposes only integer fields (``quantity``, ``total_stock``, +``price``). Simply switching to floating-point values would enable nonsensical +orders and introduce rounding issues. After team discussion it was decided +that explicit ``unit_*`` metadata can be introduced for overall cleanliness of the +code base. + +Requirements +============ + +* **Preserve compatibility:** accept and emit the legacy integer fields while + marking them deprecated once ``unit_*`` alternatives exist. When both are + supplied the backend must check that values match. +* **Use a predictable format:** fixed-point decimal strings + ``INTEGER[.FRACTION]`` with up to eight fractional digits; reject scientific + notation and special floating-point tokens. +* **Provide backend-chosen defaults per unit identifier** so new front-ends + can present appropriate UI without manual configuration. +* **Allow merchants to override** the default policy through explicit fields. +* **Update every affected endpoint** (GET/POST/PATCH products, PoS inventory, + lock, order creation, contract products) to expose and accept the new + metadata. +* **Document expectations** for merchant back-ends, PoS clients, and wallets + to ensure consistent behaviour across the ecosystem. + +Proposed Solution +================= + +1. **Extend product schemas** with optional metadata: + + * ``unit_allow_fraction`` (boolean) + * ``unit_precision_level`` (integer 0–6) + * ``unit_price`` (fixed-point decimal string) + * ``unit_total_stock`` (fixed-point decimal string, ``-1`` keeps the + “infinite” semantics) + + Legacy ``price`` and ``total_stock`` remain, but become compatibility shims and + must match the new values whenever present. + +2. **Accept** ``unit_quantity`` wherever clients submit quantities (inventory + locks, ``inventory_products``). The backend converts the decimal string into + the legacy ``quantity`` and new ``quantity_frac`` pair for storage so existing + clients keep working. + +3. **Return both representations** in all read APIs so integrators can migrate + at their own pace. + +4. **Default backend policies** + + Any backend unit identifier that is **not** listed in the table below SHALL + be treated as *Custom*: ``unit_allow_fraction`` = false, + ``unit_precision_level`` = 0, and both default labels (long/short) echo the + merchant-supplied string. + + .. list-table:: Default backend policies + :widths: 20 10 10 30 30 + :header-rows: 1 + + * - BackendStr + - Type + - Precision + - Default label (long) + - Default label (short) + * - Piece + - int + - 0 + - piece + - pc + * - Set + - int + - 0 + - set + - set + * - SizeUnitCm + - float + - 1 + - centimetre + - cm + * - SizeUnitDm + - float + - 3 + - decimetre + - dm + * - SizeUnitFoot + - float + - 3 + - foot + - ft + * - SizeUnitInch + - float + - 2 + - inch + - in + * - SizeUnitM + - float + - 3 + - metre + - m + * - SizeUnitMm + - int + - 0 + - millimetre + - mm + * - SurfaceUnitCm2 + - float + - 2 + - square centimetre + - cm² + * - SurfaceUnitDm2 + - float + - 3 + - square decimetre + - dm² + * - SurfaceUnitFoot2 + - float + - 3 + - square foot + - ft² + * - SurfaceUnitInch2 + - float + - 4 + - square inch + - in² + * - SurfaceUnitM2 + - float + - 4 + - square metre + - m² + * - SurfaceUnitMm2 + - float + - 1 + - square millimetre + - mm² + * - TimeUnitDay + - float + - 3 + - day + - d + * - TimeUnitHour + - float + - 2 + - hour + - h + * - TimeUnitMinute + - float + - 3 + - minute + - min + * - TimeUnitMonth + - float + - 2 + - month + - mo + * - TimeUnitSecond + - float + - 3 + - second + - s + * - TimeUnitWeek + - float + - 3 + - week + - wk + * - TimeUnitYear + - float + - 4 + - year + - yr + * - VolumeUnitCm3 + - float + - 3 + - cubic centimetre + - cm³ + * - VolumeUnitDm3 + - float + - 5 + - cubic decimetre + - dm³ + * - VolumeUnitFoot3 + - float + - 5 + - cubic foot + - ft³ + * - VolumeUnitGallon + - float + - 3 + - gallon + - gal + * - VolumeUnitInch3 + - float + - 2 + - cubic inch + - in³ + * - VolumeUnitLitre + - float + - 3 + - litre + - L + * - VolumeUnitM3 + - float + - 6 + - cubic metre + - m³ + * - VolumeUnitMm3 + - float + - 1 + - cubic millimetre + - mm³ + * - VolumeUnitOunce + - float + - 2 + - fluid ounce + - fl oz + * - WeightUnitG + - float + - 1 + - gram + - g + * - WeightUnitKg + - float + - 3 + - kilogram + - kg + * - WeightUnitMg + - int + - 0 + - milligram + - mg + * - WeightUnitOunce + - float + - 2 + - ounce + - oz + * - WeightUnitPound + - float + - 3 + - pound + - lb + * - WeightUnitTon + - float + - 3 + - metric tonne + - t + * - **Custom** + - int + - 0 + - *merchant string* + - *merchant string* + +5. **Quantity presentation in wallets and orders** + + When displaying order details or cart lines, wallet and POS front-ends + **MUST use the short unit label** from the table above, appended to the + numeric value with a non-breaking thin space (U+202F). Trailing zeros + *up to* the declared ``unit_precision_level`` **MUST be trimmed**, but the + displayed precision **MUST NOT** exceed the declared level. Examples:: + + 1.500 kg → shown as 1.5 kg + 3.00 pc → shown as 3 pc + + For precision 0 units the fractional part is omitted entirely. + +6. **Locale-aware unit translation rules for wallets** + + Wallets **SHOULD** offer users the option to view quantities in familiar + measurement systems. The following guidance applies: + + * Detect the buyer locale using the platform-standard mechanism (e.g. + ``navigator.language`` in browsers or OS locale on mobile). Only when the + locale **primary region** is in the CLDR “IU-customary group” + (``US``, ``LR``, ``MM``, ``GB``) **SHALL** conversions default to + imperial/US-customary, and vice-versa when the merchant lists imperial + units but the buyer locale is SI-centred. + + * Supported automatic conversions and factors (SI -> US and US -> SI): + + .. list-table:: Supported automatic conversions and factors + :widths: 40 30 30 + :header-rows: 1 + + * - kilogram (``kg``) + - pound (``lb``) + - 2.204 62 + * - gram (``g``) + - ounce (``oz``) + - 0.035 274 + * - litre (``L``) + - fluid ounce (``fl oz``) + - 33.814 + * - metre (``m``) + - foot (``ft``) + - 3.280 84 + * - square metre (``m²``) + - square foot (``ft²``) + - 10.763 9 + * - cubic metre (``m³``) + - cubic foot (``ft³``) + - 35.314 7 + + * Conversions **MUST** round to the wallet's target + ``unit_precision_level`` using bankers-rounding to minimise cumulative + error. + + * When a converted value is displayed it **SHOULD** be prefixed with + “ca.” (or ``≈`` symbol) and rendered in a visually subdued style (e.g. 60% opacity) to + signal approximation; the merchant-provided unit remains the authoritative + primary value. + + * The original backend value **MUST** be preserved in the contract terms; + conversions are *presentation-only*. + + * Wallets **SHOULD** expose a global *numeric‑system* setting in their + preferences with the values ``off``, ``automatic``, ``SI``, and ``imperial``. + + * **off** – never perform unit conversions; display exactly the merchant‑supplied units. + + * **automatic** – apply the locale heuristic described above (imperial for ``US``, ``GB``, ``LR``, ``MM``; SI otherwise). + + * **SI** – always display quantities in SI units (no conversion if the merchant already uses SI). + + * **imperial** – always display quantities converted to imperial/US‑customary units (no conversion if the merchant already uses imperial). + +Definition of Done +================== + +(Only applicable to design documents that describe a new feature. While the +DoD is not satisfied yet, a user-facing feature **must** be behind a feature +flag or dev-mode flag.) + +* Merchant backend accepts and emits the new metadata for product CRUD, + inventory locks, and order creation. +* Merchant SPAA has updated screen for product creation, which facilitates the + default units. +* POS and wallet reference implementations render fractional quantities + according to ``unit_allow_fraction`` / ``unit_precision_level``. +* Legacy clients continue to function using the integer fields, with + automated tests ensuring that canonical and legacy values stay in sync. +* Wallets follows points 5 and 6 of Proposed Solution. + +Alternatives +============ + +* Replace integers with floating-point numbers. This was ruled out because it + cannot prevent semantically invalid requests (for example 1.2 pieces) and + reintroduces floating-point rounding issues into price calculations. + +Drawbacks +========= + +* Payloads grow slightly because responses include both canonical decimal + strings and legacy integers. +* Integrations must update their tooling to emit and validate decimal strings, + which adds complexity compared to sending plain integers. + +Discussion / Q&A +=============== diff --git a/design-documents/index.rst b/design-documents/index.rst @@ -82,5 +82,6 @@ Design documents that start with "XX" are considered deprecated. 068-tokens-roadmap 069-exchange-base-url-completion 070-alias-directory-mailbox - 070-auto-refresh + 071-auto-refresh + 072-products-units 999-template