Skip to content

fix(wallet): filter, sort and export the 点数 column by the value it renders - #196

Merged
argszero merged 1 commit into
mainfrom
fix/tx-pts-range-filter-sign
Sep 13, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/tx-pts-range-filter-sign

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

The transactions page renders the 点数 cell as a signed number — income positive, expense negative (signedPts(type, pts)) — but every other consumer of that column still compared the raw stored pts, and every production writer stores pts as a non-negative number. A filter or a sort is a claim about the numbers the user is looking at, so the two must agree.

Reproduced through the real handler (one consume pts=3.7 row and one earn pts=0.5 row, inserted exactly as billing.rs stores them):

the user filters… what the table shows what the filter returned
点数 ≤ 0 the rows whose cells read -3.7 / -0.6 0 rows
点数 3–4 no cell is in that range the -3.7 row
点数 -4–-3 (the -3.7 cell) one row 0 rows

Related Issue

None (no issue for this).

Changes

  • src/routes/wallet.rs — the pts_min/pts_max range condition now compares the rendered signed value, so the page a user gets matches the numbers they see.
  • src/routes/wallet.rs — the transaction direction set gets a single source (TX_INCOME_TYPES / TX_EXPENSE_TYPES, expanded by signed_pts_expr()), which also replaces the four inline literals in the summary / trend / dashboard aggregates. Same SQL semantics there, no response change.
  • ui/js/app.js — the pts column declares filterVal/sortVal (the value it renders), and buildDataTable's comparator honours an optional sortVal (default stays row[key], behaviour unchanged for the other six tables).
  • ui/js/app.js — the CSV export writes the same signed value the cell shows (the export claims to mirror the visible rows).
  • ui/index.html — app.js cache-bust.
  • 涉及配置/数据结构的改动已同步示例文件 — n/a (no config/schema change, no new i18n key).

Tests

  • cargo test — 186 passed, 0 failed

  • cargo fmt --check and cargo clippy --all-targets -- -D warnings — clean

  • New/updated unit tests

  • pts_range_filter_matches_the_rendered_signed_value (new): seeds one consume pts=3.7 and one earn pts=0.5 row exactly as billing.rs stores them, and asserts the filter returns exactly the rows whose rendered value is in range — pts_max=0 → the consume row, pts_min=-4&pts_max=-3 → the consume row, pts_min=3&pts_max=4 → empty, plus a positive control (pts_min=0&pts_max=1 → the earn row) and the trend endpoint on the same where-clause.

  • transactions_column_filters (updated): the pts range assertions now read the rendered value, with a negative control (the raw +20 no longer matches [15,25]) and a positive control (the income row still matches [25,35]).

  • A/B on the server condition only (revert signed_pts_expr → col("pts")): exactly 2 red / 184 green — the new assertions are the ones that notice.

  • A/B on the JS half via an extracted-source probe (the real filterRows, the real pts column literal, the real exportTxCsv and the real sort block sliced out of app.js): removing filterVal makes all four ranges disagree with the rendered values again; removing sortVal orders by the raw +3.7 instead of the rendered -3.7; reverting the CSV cell writes 3.7, 0.5, 0.6 instead of -3.7, 0.5, -0.6.

Checklist

  • 分支命名符合约定 (fix/)
  • Commit message 使用 Conventional Commits 格式
  • 单一职责,改动最小化 — one column, one convention: every consumer of the 点数 cell (range filter, sort, CSV export) now agrees with what the cell renders.

…nders

The transactions page renders the 点数 cell as a signed number (income
positive / expense negative via `signedPts()`), but every other consumer of
that column still read the raw stored `pts`, which every writer stores as a
non-negative number. A filter/sort is a claim about the numbers the user is
looking at, so the two must agree.

Server (`/api/transactions`, `/api/transactions/trend`):
- the `pts_min`/`pts_max` range condition now compares the rendered signed
  value (`CASE WHEN type IN (income) THEN pts ELSE -pts END`), so
  `pts_max=0` returns the expenses the user sees as negative instead of
  returning nothing;
- the direction set gets a single source (`TX_INCOME_TYPES` /
  `TX_EXPENSE_TYPES` + `signed_pts_expr()`), which also replaces the four
  inline literals in the summary/trend/dashboard aggregates.

Client (`ui/js/app.js`):
- the pts column declares `filterVal`/`sortVal` (the value it renders) and
  `buildDataTable`'s comparator honours an optional `sortVal` (default
  unchanged for the other six tables);
- the CSV export writes the same signed value the cell shows.

Tests: the pts assertions in `transactions_column_filters` are rewritten to
the rendered-value spec with both a negative control (the raw positive no
longer matches) and a positive control (an income row still matches), plus a
new `pts_range_filter_matches_the_rendered_signed_value` covering the list and
trend endpoints.
@argszero
argszero merged commit f6eed10 into main Sep 13, 2026
1 check passed
@argszero
argszero deleted the fix/tx-pts-range-filter-sign branch September 13, 2026 02:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant