diff --git a/.changeset/28051-skills-derived-currency-refusal.md b/.changeset/28051-skills-derived-currency-refusal.md new file mode 100644 index 00000000000..d10646a456e --- /dev/null +++ b/.changeset/28051-skills-derived-currency-refusal.md @@ -0,0 +1,12 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): the skills teach the third `fixed`-currency refusal, a derived measure's own `currency` against its operands' one `fixed` code + +Clause-②: no +Metadata-protocol: none + +- `objectstack-data` `rules/field-types.md` (Currency resolution) and `objectstack-ui` `rules/dashboards.md` (the dataset currency comment) taught two `fixed` refusals and missed the third: a derived `sum` or `difference` whose monetary operands all resolve to one `fixed` code refuses another `currency` at dataset compile, with `DATASET_INVALID` / 400. Both files now teach it beside the other two, with the shipped prescription: delete the measure's `currency` (the column takes the operands' code), or restate that code. +- Paid for inside each file: `field-types.md` drops the `currency_code` naming convention its own example already shows; `dashboards.md` drops the retired `categoryGranularity` parenthetical, which the parse refusal and the Dataset-Bound Widgets paragraph already carry. Neither ratchet row moves. +- No schema, key or behaviour changes. diff --git a/content/docs/api/data-api.mdx b/content/docs/api/data-api.mdx index 973200b4e44..8b6b89c465a 100644 --- a/content/docs/api/data-api.mdx +++ b/content/docs/api/data-api.mdx @@ -518,12 +518,18 @@ monetary measure column takes the measure's own `currency`; then, only when the field is `currencyMode: 'fixed'`, that field's `currencyConfig.defaultCurrency`; then the tenant default currency (the `localization.currency` setting), if one is set. A `dynamic` field (the default mode) never lends its `defaultCurrency` — only `fixed` does — -so its column shows the tenant default. The cube query on this page carries no column -`currency`. +so its column shows the tenant default. A derived `sum` or `difference` whose monetary +operands all resolve to one `fixed` code takes that code at the field's step; any other +derived measure (a `ratio`, a `product`, operands in the tenant's currency) is monetary +only by its own `currency`, else its column carries none. The cube query on this page +carries no column `currency`. Over a `fixed` field the measure's `currency` may only restate the field's code. A different code is refused when the dataset compiles, with `DATASET_INVALID` / 400: delete -the measure's `currency`, and the column takes the field's code. A derived measure whose +the measure's `currency`, and the column takes the field's code. A derived `sum` or +`difference` whose operands resolve to one `fixed` code is held to the same rule: a +different `currency` is refused with `DATASET_INVALID` / 400 — delete the measure's +`currency` (the column takes the operands' code), or restate it. A derived measure whose monetary operands resolve to two different `fixed` codes is refused the same way, whatever the operation: derive it from measures in one currency, or drop the operand in the other. Neither a `fixed` code against the tenant default nor a measure `currency` over a diff --git a/content/docs/data-modeling/currency.mdx b/content/docs/data-modeling/currency.mdx index 7d19c6a88e8..d7a7a9f6de6 100644 --- a/content/docs/data-modeling/currency.mdx +++ b/content/docs/data-modeling/currency.mdx @@ -178,8 +178,12 @@ that column. A `count` column never carries `currencyField`. See the - **A derived measure whose monetary operands resolve to two different `fixed` codes** is refused at compile with `DATASET_INVALID` / 400, whatever the operation. Derive it from measures in one currency, or drop the operand in the other. A derived `sum` or - `difference` over one `fixed` code carries that code, so a measure built on it is judged - the same way. + `difference` over one `fixed` code carries that code, on its result column too, so a + measure built on it is judged the same way. +- **A derived `sum` or `difference` whose `currency` differs from its operands' one `fixed` + code** is refused at compile with `DATASET_INVALID` / 400: `currency: 'USD'` over two + measures fixed to `EUR` would print euros as dollars. Delete the measure's `currency`, and + the column takes the operands' code, or restate that code. - **Not judged:** a `fixed` code against the tenant currency, which compile does not know, and a measure `currency` over a `dynamic` field, which wins as the top of the currency chain. diff --git a/skills/objectstack-data/rules/field-types.md b/skills/objectstack-data/rules/field-types.md index 9221f0c4da1..2f353791bac 100644 --- a/skills/objectstack-data/rules/field-types.md +++ b/skills/objectstack-data/rules/field-types.md @@ -170,9 +170,10 @@ Stored as JSON on the parent row — no separate table / FK: `{ value, currency }`. Shown: `'fixed'`, `defaultCurrency`; `'dynamic'` (default), the tenant `localization.currency`, else a plain number (never `$`); `'record'`, each record's code in the `text` field `currencyField` -names (by convention `currency_code`), with `valueDomain: 'iso_4217_currency'`. A -dataset measure's own `currency` wins over a `'dynamic'` field; over a `'fixed'` one -it may only restate the code, else `DATASET_INVALID` (objectstack-ui → +names, with `valueDomain: 'iso_4217_currency'`. A +dataset measure's own `currency` wins over a `'dynamic'` field; over a `'fixed'` one, +or a derived sum/difference over one `'fixed'` code, it may only restate that code, +else `DATASET_INVALID`: delete it, and the column takes the code (objectstack-ui → Dashboards). A `'fixed'` code is not judged against the tenant's. Switched a stored field to `'record'`? Backfill: `os migrate record-currency-codes`. diff --git a/skills/objectstack-ui/rules/dashboards.md b/skills/objectstack-ui/rules/dashboards.md index 5c20f5ef605..c0d19a6d032 100644 --- a/skills/objectstack-ui/rules/dashboards.md +++ b/skills/objectstack-ui/rules/dashboards.md @@ -282,8 +282,7 @@ The **dataset dimension carries the default; a widget's `options.dateGranularity` overrides it for that widget only.** Give a date dimension a `dateGranularity` and any presentation that selects it groups by that bucket server-side — without it every distinct timestamp becomes its own -category, collapsing a 12-row seed into a 12-point flat line. (The old widget-level -`categoryGranularity` was removed in the single-form cutover.) +category, collapsing a 12-row seed into a 12-point flat line. ```typescript @@ -301,8 +300,9 @@ defineDataset({ // `currencyMode: 'record'` amount refuses `currency`, and sum/avg/min/max // unless grouped by its code field; then the column carries // `currencyField` (the per-row code column); a `count` never does. A `fixed` -// field refuses a different `currency` (delete it; restating is fine), and -// a derived measure refuses operands in two `fixed` codes (derive from one +// field, or a derived sum/difference over one `fixed` code, refuses another +// `currency` (delete it, the column takes that code; restating is fine); a +// derived measure refuses operands in two `fixed` codes (derive from one // currency or drop one). Not judged: `currency` over a `dynamic` field, a // `fixed` code against the tenant's.