fix(wallet): filter, sort and export the 点数 column by the value it renders - #196
Merged
Merged
Conversation
…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.
This was referenced Sep 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 storedpts, and every production writer storesptsas 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.7row and oneearn pts=0.5row, inserted exactly asbilling.rsstores them):-3.7/-0.63–4-3.7row-4–-3(the-3.7cell)Related Issue
None (no issue for this).
Changes
src/routes/wallet.rs— thepts_min/pts_maxrange 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 bysigned_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 declaresfilterVal/sortVal(the value it renders), andbuildDataTable's comparator honours an optionalsortVal(default staysrow[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.jscache-bust.Tests
cargo test— 186 passed, 0 failedcargo fmt --checkandcargo clippy --all-targets -- -D warnings— cleanNew/updated unit tests
pts_range_filter_matches_the_rendered_signed_value(new): seeds oneconsume pts=3.7and oneearn pts=0.5row exactly asbilling.rsstores 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+20no 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 realexportTxCsvand the real sort block sliced out ofapp.js): removingfilterValmakes all four ranges disagree with the rendered values again; removingsortValorders by the raw+3.7instead of the rendered-3.7; reverting the CSV cell writes3.7, 0.5, 0.6instead of-3.7, 0.5, -0.6.Checklist