fix: pagination count 500s, order_by, and billing state (MAPPS-135) #122

Merged
David merged 2 commits from fix/mapps-135-pagination-billing-correctness into main 2026-06-13 13:38:48 +02:00
Owner

Cluster of server-side correctness defects in pagination count-query construction and billing state handling. All present on main; the count-query and order_by bugs were touched by later commits but never actually fixed.

count-query param indices: the list queries bind $1=tenant_id, $2=limit, $3=offset so filter placeholders start at $4, but the count queries reuse the same WHERE clause while binding tenant_id at $1 and filters from $2. Any filtered list 500s with a Postgres parameter error. Build a separate count WHERE clause off a parallel counter starting at $2 and bind it in the same order. Fixed in list_time_entries, list_invoices, and list_payments.

order_by double-direction: order_by() did format!("{} {}", field, direction), so a default that already embeds a direction produced "... DESC DESC" (invalid SQL on the no-?sort= path). It now appends a direction only to an explicit allow-listed sort or to a bare single-column default; a compound or already-directional default is emitted verbatim. The single-column billing defaults are stripped to bare invoice_date / payment_date; the time-entry default stays the compound date DESC, start_time DESC (it cannot be a bare field) and is now emitted verbatim without double-appending.

create_payment frozen guard: recording a payment now fetches the invoice status first and rejects (Conflict) a payment against a void / written_off invoice, which previously un-voided it via the status overwrite. is_frozen() also covers the normal sent/paid/partially_paid payment targets, so the gate is the terminal cancelled subset.

unknown enum fallback: the row-to-response conversions silently mapped unknown DB enum strings to Draft / Stripe / Other / Service, which could re-enable edits on a frozen invoice. The three conversions become TryFrom returning a Database error on an unknown value, and the gateway-provider and create_payment status parses do the same.

payment DELETE feedback: the payments table swallowed a failed delete via .is_ok(). It now matches the result and surfaces an error line, mirroring the tax-rate delete.

lookup_tax_rate guard: added the missing RequireFinance extractor so tax-rate lookup is finance-only like the other tax-rate routes.

compute_minutes overnight: an entry whose end_time precedes start_time now wraps by a full day (crosses midnight) instead of being rejected as a bad request.

#MAPPS-135

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Cluster of server-side correctness defects in pagination count-query construction and billing state handling. All present on main; the count-query and order_by bugs were touched by later commits but never actually fixed. count-query param indices: the list queries bind $1=tenant_id, $2=limit, $3=offset so filter placeholders start at $4, but the count queries reuse the same WHERE clause while binding tenant_id at $1 and filters from $2. Any filtered list 500s with a Postgres parameter error. Build a separate count WHERE clause off a parallel counter starting at $2 and bind it in the same order. Fixed in `list_time_entries`, `list_invoices`, and `list_payments`. order_by double-direction: `order_by()` did `format!("{} {}", field, direction)`, so a default that already embeds a direction produced `"... DESC DESC"` (invalid SQL on the no-`?sort=` path). It now appends a direction only to an explicit allow-listed sort or to a bare single-column default; a compound or already-directional default is emitted verbatim. The single-column billing defaults are stripped to bare `invoice_date` / `payment_date`; the time-entry default stays the compound `date DESC, start_time DESC` (it cannot be a bare field) and is now emitted verbatim without double-appending. create_payment frozen guard: recording a payment now fetches the invoice status first and rejects (Conflict) a payment against a void / written_off invoice, which previously un-voided it via the status overwrite. `is_frozen()` also covers the normal sent/paid/partially_paid payment targets, so the gate is the terminal cancelled subset. unknown enum fallback: the row-to-response conversions silently mapped unknown DB enum strings to Draft / Stripe / Other / Service, which could re-enable edits on a frozen invoice. The three conversions become `TryFrom` returning a Database error on an unknown value, and the gateway-provider and create_payment status parses do the same. payment DELETE feedback: the payments table swallowed a failed delete via `.is_ok()`. It now matches the result and surfaces an error line, mirroring the tax-rate delete. lookup_tax_rate guard: added the missing `RequireFinance` extractor so tax-rate lookup is finance-only like the other tax-rate routes. compute_minutes overnight: an entry whose end_time precedes start_time now wraps by a full day (crosses midnight) instead of being rejected as a bad request. #MAPPS-135 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix: pagination count 500s, order_by, and billing state (MAPPS-135)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 50s
2a2fb6c12a
Cluster of server-side correctness defects in pagination count-query construction and billing state handling. All present on main; the count-query and order_by bugs were touched by later commits but never actually fixed.

count-query param indices: the list queries bind $1=tenant_id, $2=limit, $3=offset so filter placeholders start at $4, but the count queries reuse the same WHERE clause while binding tenant_id at $1 and filters from $2. Any filtered list 500s with a Postgres parameter error. Build a separate count WHERE clause off a parallel counter starting at $2 and bind it in the same order. Fixed in `list_time_entries`, `list_invoices`, and `list_payments`.

order_by double-direction: `order_by()` did `format!("{} {}", field, direction)`, so a default that already embeds a direction produced `"... DESC DESC"` (invalid SQL on the no-`?sort=` path). It now appends a direction only to an explicit allow-listed sort or to a bare single-column default; a compound or already-directional default is emitted verbatim. The single-column billing defaults are stripped to bare `invoice_date` / `payment_date`; the time-entry default stays the compound `date DESC, start_time DESC` (it cannot be a bare field) and is now emitted verbatim without double-appending.

create_payment frozen guard: recording a payment now fetches the invoice status first and rejects (Conflict) a payment against a void / written_off invoice, which previously un-voided it via the status overwrite. `is_frozen()` also covers the normal sent/paid/partially_paid payment targets, so the gate is the terminal cancelled subset.

unknown enum fallback: the row-to-response conversions silently mapped unknown DB enum strings to Draft / Stripe / Other / Service, which could re-enable edits on a frozen invoice. The three conversions become `TryFrom` returning a Database error on an unknown value, and the gateway-provider and create_payment status parses do the same.

payment DELETE feedback: the payments table swallowed a failed delete via `.is_ok()`. It now matches the result and surfaces an error line, mirroring the tax-rate delete.

lookup_tax_rate guard: added the missing `RequireFinance` extractor so tax-rate lookup is finance-only like the other tax-rate routes.

compute_minutes overnight: an entry whose end_time precedes start_time now wraps by a full day (crosses midnight) instead of being rejected as a bad request.

#MAPPS-135

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge branch 'main' into fix/mapps-135-pagination-billing-correctness
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 55s
99c581d375
David merged commit 31d5fe48e3 into main 2026-06-13 13:38:48 +02:00
David deleted branch fix/mapps-135-pagination-billing-correctness 2026-06-13 13:38:48 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
psa-systems/mokosh-apps!122
No description provided.