Skip to content

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.

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).
  • The project axis is found by its seeded key, as the web’s PROJECT_AXIS_KEY already does. An org that renames the key loses the column, not the data.