feat(timesheets): status filter + multi-week range + decision audit on list (PMS-506) #360
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/mokosh-server!360
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/PMS-506-timesheet-history"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The week-rolled
GET /timesheetsendpoint accepted onlyuser_id+weekfilters and dropped the decision audit (approved_by_id /approved_at / rejection_reason) on its way through the
TimesheetRow -> TimesheetSummaryResponseconversion. The SPA'sadmin approvals queue then filtered the response down to
approval_status == "pending", so approved + rejected weeks"disappeared" after the admin clicked Approve - matches the QA
report.
Server contract changes:
status: Option<String>(pending/approved/rejected/all),from: Option<NaiveDate>, andto: Option<NaiveDate>. status validates server-side (422 on anunknown value); from/to overrides the legacy single-
weekfield.from/toagainst the Monday anchor,caps the span at 26 weeks (422 if longer), widens the date WHERE
to scan the range, and HAVING-filters the rolled CASE expression
on the requested status. The aggregate carries decided_at
(
MAX(approved_at)), decided_by_id, and rejection_reason - allidentical across the rolled rows because approve_week / reject_week
run a single UPDATE per week - via
ARRAY_AGG ... FILTER.rejection_reason as
Options; they serialize as absent on pendingweeks.
Integration tests (tests/timesheet_history.rs):
approved, one rejected with a reason, one pending), then asserts
each status filter returns exactly the right subset with the right
audit fields populated.
enum guard.
Client side (status filter + range mode + history rendering on
TimesheetApprovalsPage) lands in the mokosh-apps PR under the sameticket id.
#PMS-506
CI report: `list_timesheets_status_filter_and_range` failed with `got 1 row; want 3`. The seed wrote three weekly entries (approved A, rejected B, pending C) over a range, but the service returned only the newest one. Root cause is the test fixture, not the production query: `PaginationParams::default()` derives Rust's `Default` which zeroes `per_page` (the serde `default_per_page = 25` fires only when deserialised from a query string). The service then clamps `per_page.clamp(1, MAX_PER_PAGE)` to 1, so the SQL ran with `LIMIT 1 OFFSET 0` and only the first row in the `ORDER BY week_start DESC` cursor came back - week C, the newest. Spell out `PaginationParams { page: 1, per_page: 25, ... }` in the local `page()` helper so the multi-row assertions actually exercise the multi-week range. Service code untouched - the bug never hit a real HTTP caller because the query-string defaults wire up correctly on the route layer; this was a tests-only blind spot. #PMS-506