Repository navigation
Commit 6e68d8f
fix(plugin-detail): record page body tables total only declared-additive columns, not ordinals (#29966)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwB4xjiquevJqfBXiUoqfQwa1)_
Fixes #29943
Clause-②: no
Metadata-protocol: none
## 维护者速读
-
**改了什么**:记录页正文子表(全宽明细表)底部的「本页合计」不再把所有数字列相加:金额(`currency`)列在本页各行同一币种时合计并按该币种显示;普通数字列只有在父对象声明了对该列
`sum` 的汇总字段时才合计;其余列不合计,没有可合计的列时整行不画。
- **为什么改**:hotclm 合同页把版本号、期次也加了起来(「本页合计 版本号 10」「期次 15」),EUR
合同的金额还按无币种裸数相加;字段类型本身分不清「序号」和「数量」,只能读作者已经声明过的东西。
- **风险与回滚**:已发布页面的可见行为变化:以前被合计的普通数字列(数量等)若父对象没有 sum
汇总,合计会消失;币种混合或未知的金额列也不再合计。只改 `BodyTableTotals` 一处,不发新请求、不加 spec 键;回滚即还原本
PR。
- **先看**:`ui/packages/plugin-detail/src/RelatedList.tsx` 的
`BodyTableTotals` 与新增的 `rolledUpSumFields` /
`agreedRowCurrency`;`ui/packages/plugin-detail/src/__tests__/RelatedList.bodyTable-27055.test.tsx`
新增的 objectstack#29943 用例组。
- **两分钟验证**:在仓根运行 `pnpm --dir ui exec vitest run
packages/plugin-detail/src/__tests__/RelatedList.bodyTable-27055.test.tsx`,预期
12 个测试全部通过(序号列无合计、金额列按 € 合计、父对象 sum 汇总的数字列才合计、混合币种无合计)。
- **需要你做的**:无,席位落地。
## What changes
`BodyTableTotals` (the totals line under a body table of the default
record page, objectstack#27055 Target 8) totalled every column typed
`currency | number | integer | decimal`. Target 8 promised "a totals row
for numeric columns", and the type alone cannot tell an ordinal from a
quantity: `clm_contract_version.version_no` and `clm_payment_plan.seq`
are `Field.number` with `scale: 0`. The line now totals only what
something already declared additive:
| Column | Before | After |
|---|---|---|
| `currency`, every counted row in one known currency | summed, code
from `currencyConfig.defaultCurrency ?? currency` whatever the mode |
summed, formatted in that currency through `formatCurrency` (the cell's
formatter) |
| `currency`, rows in different currencies, or one row's currency
unknown | summed (bare, or in the field's `defaultCurrency`) | no total
|
| `number` / `integer` / `decimal`, the parent object rolls it up with
`sum` | summed | summed (unchanged) |
| `number` / `integer` / `decimal`, no such roll-up (ordinals, and
amounts stored as `number`) | summed | no total |
| anything else | no total | no total |
When no column qualifies the line is not drawn, as before.
**Which currency a row is in.** Each counted row resolves through
`@object-ui/i18n`'s `resolveFieldCurrency(def, tenantCurrency, row)`,
the reader the currency cell and the grid footer (`useColumnSummary`)
already share: the field's code under `currencyMode: 'fixed'`, the
tenant default (`useLocalization().currency`) under `dynamic`, the row's
sibling code field (`currencyConfig.currencyField`) under `record`. One
consequence for a fixture that carried `currencyConfig: {
defaultCurrency: 'USD' }` with no mode: the spec parses that to
`dynamic` and does not read `defaultCurrency` there, so the total now
shows the tenant default, the same currency the column's cells display.
The old reader ignored the mode and could print a total in a currency
the cells above it did not show. The existing #27055 fixture now
declares `currencyMode: 'fixed'`.
**Which number columns a roll-up declares additive (the reader used).**
`rolledUpSumFields` reads the record page's own object schema from
`useRecordContext().objectSchema`, which `RecordDetailView` provides
(`objectSchema={objectDef}`) around `RecordPageColumns`, the only place
a body table is drawn. A parent field counts when it is `type:
'summary'` with `summaryOperations.function === 'sum'`,
`summaryOperations.object` naming this list's object (`api ||
objectName`), and `summaryOperations.field` naming the column. Both
field-container shapes the metadata API serves are read.
`relationshipField` is not compared, because which foreign key a sum
runs through does not change whether the child column adds up. No fetch
was added: the parent schema was already in context (the PM's mechanism
assumption holds).
## Tests
New top-level `describe` in the existing body-table harness,
`RelatedList.bodyTable-27055.test.tsx` (hotclm's payment-plan fixture):
- ordinal columns (`seq` `number` with `scale: 0`, `version_no`
`integer`) with no roll-up: no totals line at all;
- CONTROL: a `fixed` EUR `currency` column beside the ordinals totals
`€60.25` and `seq` stays untotalled;
- a `number` column the parent rolls up with `sum`: totalled `383,000.5`
(map-shaped and array-shaped parent fields);
- CONTROL: the same column with no record context, an empty parent, a
`count` roll-up, a `max` roll-up, and a `sum` over another object: not
totalled;
- a `record`-mode amount whose rows all carry `EUR` totals `€600.00`;
mixed `EUR`/`USD` rows: no total; one row with a blank code: no total;
- a `dynamic` amount totals in the tenant default (`CNY` via
`LocalizationProvider`) and gets no total without one;
- the #27055 case updated: `quantity` (a `number` with no roll-up) is no
longer totalled, the `fixed` USD `total` reads `$65.50`.
**Ablation.** The fix commit restored to the old type set (the gate line
`if (!isCurrency && !(ROLLED_UP_FIELD_TYPES.has(def.type) &&
rolledUp.has(key))) continue;` replaced by `if (!isCurrency &&
!ROLLED_UP_FIELD_TYPES.has(def.type)) continue;`, i.e. every number
column totalled) through `scripts/ablation-replace.mjs`: anchor 1 → 0,
blob `4270c0b477d1` → `d31d47e2ca1d`; 5 tests red, including "totals no
ordinal column — and draws no line when nothing else adds up" and the
#27055 case. Restored: blob == HEAD `4270c0b477d1`, `git diff HEAD`
empty. The test imports `../RelatedList` from source, so no build sat
between the mutation and the run.
## Acceptance notes
- **hotclm's amounts (what the order asked me to report).** Per the
card, hotclm's payment-plan `planned_amount` / `actual_amount` are
`number` fields whose currency lives on the parent contract's
`currency_code`. Under this rule they get no total unless the contract
declares a `summary` field that sums them; with such a roll-up they
total as plain numbers, with no currency symbol, because a `number`
column carries no currency of its own. The way to get a
currency-formatted total is the spec's own: declare the amount as
`currency` with `currencyMode: 'record'` and a `currencyField` on the
child row. The parent's `currency_code` is not read, because ADR-0144 D2
puts a `record`-mode amount's code on the amount's own object. I could
not open the hotclm screenshots or metadata from this container (the
path in the order is not in this project's shared folder), so this
reading comes from the card text.
- **A `record`-mode amount whose code column is not drawn.** An
authored-column related list projects (`$select`) only the drawn columns
plus predicate operands, so a `record`-mode amount whose `currencyField`
is not among the columns arrives without its code. The total then reads
as unknown and is not drawn (honest, not wrong). The cell above it has
the same gap. Adding the code field to the projection is a fetch change
outside this card. Reported in the report, not filed.
- No new spec key; `Metadata-protocol: none`.
- In-flight overlap re-checked at PR time: #29897's PR #29958 touches
only `app-shell/.../ResourceEditPage.tsx`; no open PR touches
`ui/packages/plugin-detail/**` beside this one.
## Verification
All on head `ad05c644` (shared box; lock VERDICT lines quoted).
<details><summary>Build, type-check, vitest</summary>
- `pnpm --workspace-concurrency=2 --filter
"@object-ui/plugin-detail^..." build`: VERDICT command-exit 0 (6m04s)
- `pnpm --filter @object-ui/plugin-detail type-check` (`tsc --noEmit &&
tsc -p tsconfig.test.json`): VERDICT command-exit 0
- `pnpm --filter @object-ui/plugin-detail build`, then `pnpm --dir ui
exec vitest run --maxWorkers=2 packages/plugin-detail/`: VERDICT
command-exit 0 · Test Files 369 passed | 1 skipped (370) · Tests 3250
passed | 18 skipped (3268)
- `pnpm --dir ui exec vitest run
packages/plugin-detail/src/__tests__/RelatedList.bodyTable-27055.test.tsx`:
12 passed (12)
</details>
<details><summary>Derived gates (`dispatch-gates --commands`, 35 lines,
then `--ran`)</summary>
```text
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
```
`node scripts/pm/dispatch-gates.mjs --ran FILE --repo
objectstack-ai/objectstack`: exit 0, "Run reconciliation — 35 derived,
35 run, 0 NOT-MEASURED, 0 UNRUN."
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran. `pnpm lint` was not run locally: the
repo-wide lint is the CI run.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: n/a.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the 维护者速读 section is
the dispatch's required Chinese summary).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzfSkDtGdeCZgsqhyHH
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 509c40a commit 6e68d8f
3 files changed
Lines changed: 260 additions & 27 deletions
File tree
- .changeset
- ui/packages/plugin-detail/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | | - | |
| 88 | + | |
89 | 89 | | |
90 | | - | |
| 90 | + | |
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| |||
691 | 691 | | |
692 | 692 | | |
693 | 693 | | |
694 | | - | |
695 | | - | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
696 | 743 | | |
697 | 744 | | |
698 | | - | |
| 745 | + | |
699 | 746 | | |
700 | | - | |
701 | | - | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
702 | 755 | | |
703 | | - | |
| 756 | + | |
704 | 757 | | |
705 | 758 | | |
706 | 759 | | |
| 760 | + | |
707 | 761 | | |
708 | 762 | | |
709 | 763 | | |
710 | 764 | | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
711 | 770 | | |
712 | 771 | | |
713 | 772 | | |
714 | 773 | | |
715 | 774 | | |
716 | | - | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
717 | 778 | | |
718 | | - | |
| 779 | + | |
719 | 780 | | |
720 | 781 | | |
721 | 782 | | |
722 | 783 | | |
723 | | - | |
| 784 | + | |
724 | 785 | | |
725 | | - | |
726 | | - | |
| 786 | + | |
727 | 787 | | |
728 | | - | |
729 | | - | |
730 | | - | |
731 | | - | |
732 | | - | |
733 | | - | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
734 | 798 | | |
735 | 799 | | |
736 | 800 | | |
| |||
3466 | 3530 | | |
3467 | 3531 | | |
3468 | 3532 | | |
3469 | | - | |
| 3533 | + | |
3470 | 3534 | | |
3471 | 3535 | | |
3472 | 3536 | | |
| |||
3705 | 3769 | | |
3706 | 3770 | | |
3707 | 3771 | | |
3708 | | - | |
| 3772 | + | |
3709 | 3773 | | |
3710 | 3774 | | |
3711 | 3775 | | |
| |||
0 commit comments