Claim list data — design (Figma batch 2, project D)
Audit rows 3.16, 3.19, 4.3, 4.6, 5.2, 5.3 (2026-09-25-figma-alignment-audit.md); Figma
123:7183 My claims, 123:7184 My approvals, 135:11106 All claims.
Decisions
Section titled “Decisions”All new fields are additive on ExpenseClaim (list and single read) and
ExpenseClaimKpis; nothing existing changes shape. They are computed in the one
ClaimExtras query the list already runs for a page of ids (correlated subqueries per
claim, bounded by the page), so there is no N+1 and RLS plus explicit org_id predicates
apply as they do today. Visibility is untouched: extras are only ever loaded for claims the
list or the single read already decided the caller may see.
| Field | Meaning | Why |
|---|---|---|
merchant (string, nullable) |
supplier_name of the first active line (by line number) that names one |
“First line’s supplier” (Plan 10 §5.2); skipping supplier-less lines (mileage, per diem) keeps the subline useful. null → no subline. |
original_currency / original_total (nullable) |
Set when every active line is in one currency that is not the base currency; the sum of those lines’ amount |
Figma 3.19 “CNY 333.00” over “RM 210.55”. Summed in SQL NUMERIC, scanned into money.Amount, a string on the wire — the same exact-decimal path as total and the summary; the browser never adds. |
multiple_currencies (bool) |
Active lines are in more than one currency | Covers base + foreign too: one foreign figure above a base total that also includes base-currency lines would misstate what was spent. Rendered as a translated “Multiple currencies”. |
claimant_unit_name (string, nullable) |
Name of the org unit of the claimant’s primary position on the day of the latest submission (the most recent draft→non-draft decision, org timezone); while the claim is in draft (a returned one included), today | The data model records it: member_position is dated and never deleted. So an approver sees the department the claim was last sent from, not where the claimant sits after a transfer; a claim returned and resubmitted after a transfer shows the new unit. null when the claimant had no primary position. |
project_names (string[], never null) |
Distinct analytic accounts on the project axis named by the active lines’ shares, in order of first line, then name |
Names, not ids: resolving ids client-side would need accounting.ledger.read, which approvers do not all hold. Analytic account name is the same record name AnalyticAccount.name already exposes. The web shows the first and “+N”. |
kpis.rejected |
Count and base total of claims whose state semantic is rejected |
Fifth card 已拒绝 (red tone, SquareX). Computed in the same SQL as the other cards, so it agrees with them. |
Employee (All claims) needs no new field: claimant_member_id + the existing member
directory (useMemberNames), which carries no permission.
- Title cell: title over a 12 px muted merchant line when present.
- Amount cell: original amount (or “Multiple currencies”) in a caption line above the bold base total.
- My approvals: Department and Projects columns after Category. All claims: Employee and Projects after Category. My claims: unchanged columns.
- KPI row: 4 cards on My claims (Figma 123:7183 shows 4), 5 on My approvals and All claims.
- The last card is named for the tab: 总提交 Total submitted / 审批总数 Total to approve / 报销总数 Total claims.
- Mobile cards get the merchant and original amount; no new columns (cards have none).
Not done
Section titled “Not done”- The
projectaxis is found by its seeded key, as the web’sPROJECT_AXIS_KEYalready does. An org that renames the key loses the column, not the data.