feat(projects): expose per-task logged_hours (non-rejected) alongside approved actual_hours #249

Merged
nrupard merged 1 commit from feat/PMS-329-task-logged-hours into main 2026-06-15 21:08:07 +02:00
Owner

Summary

A task's actual_hours rollup counts only approval_status = 'approved' time entries, so time logged against a task is invisible (0h) until it is submitted and approved. MAPPS-167 wants the task to show both numbers: total logged (everything not rejected) and approved. A client cannot compute the logged total itself because /time-entries is self-scoped and would miss other users' time on a shared task, so the rollup must come from the server.

Changes

  • Add logged_hours: Option<Decimal> to TaskResponse and logged_hours to the internal TaskRow + From impl.
  • In both task read paths (list_project_tasks and get_task), add a sibling subquery that sums non-rejected (approval_status <> 'rejected', i.e. draft + pending + approved) time-entry durations by task_id, divided to hours.
  • actual_hours is unchanged (approved-only). logged_hours >= actual_hours always.
  • Project-level rollups stay approved-only (explicitly out of scope for MAPPS-167; a matching project logged_hours could follow later if wanted).

Tests

task_logged_hours_counts_non_rejected (in tests/projects.rs), green against a throwaway Postgres. It drives the approval transitions directly in SQL so it pins the rollup SELECT rather than the timesheet state machine:

  • draft entry: logged_hours = 2.0, actual_hours = 0.0
  • approved: both = 2.0
  • rejected: both = 0.0
  • the list read path (GET /projects/{id}/tasks) also carries logged_hours

The existing approved_time_rolls_into_actuals regression still passes (actual_hours behavior unchanged). Full projects suite: 12/12.

#PMS-329

## Summary A task's `actual_hours` rollup counts only `approval_status = 'approved'` time entries, so time logged against a task is invisible (0h) until it is submitted and approved. MAPPS-167 wants the task to show both numbers: total logged (everything not rejected) and approved. A client cannot compute the logged total itself because `/time-entries` is self-scoped and would miss other users' time on a shared task, so the rollup must come from the server. ## Changes - Add `logged_hours: Option<Decimal>` to `TaskResponse` and `logged_hours` to the internal `TaskRow` + `From` impl. - In both task read paths (`list_project_tasks` and `get_task`), add a sibling subquery that sums non-rejected (`approval_status <> 'rejected'`, i.e. draft + pending + approved) time-entry durations by `task_id`, divided to hours. - `actual_hours` is unchanged (approved-only). `logged_hours >= actual_hours` always. - Project-level rollups stay approved-only (explicitly out of scope for MAPPS-167; a matching project `logged_hours` could follow later if wanted). ## Tests `task_logged_hours_counts_non_rejected` (in `tests/projects.rs`), green against a throwaway Postgres. It drives the approval transitions directly in SQL so it pins the rollup SELECT rather than the timesheet state machine: - draft entry: `logged_hours = 2.0`, `actual_hours = 0.0` - approved: both `= 2.0` - rejected: both `= 0.0` - the list read path (`GET /projects/{id}/tasks`) also carries `logged_hours` The existing `approved_time_rolls_into_actuals` regression still passes (actual_hours behavior unchanged). Full `projects` suite: 12/12. #PMS-329
feat(projects): expose per-task logged_hours alongside approved actual_hours
All checks were successful
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m2s
E2E / Playwright against staging (pull_request) Successful in 1m26s
Integration / integration tests (pull_request) Successful in 2m57s
Create release / Create release from merged PR (pull_request) Has been skipped
ecd62b4df2
A task's actual_hours rollup counts only approved time entries, so time logged against a task stays invisible (0h) until it is submitted and approved. MAPPS-167 wants the task to show both numbers: total logged (everything not rejected) and approved. A client cannot compute the logged total itself because /time-entries is self-scoped and would miss other users' time on a shared task, so the full rollup must come from the server.

Add logged_hours to TaskResponse and TaskRow, populated by a sibling subquery in both task read paths (list_project_tasks and get_task) that sums non-rejected time-entry durations (draft + pending + approved) by task_id. actual_hours is unchanged (approved-only); logged_hours >= actual_hours always. The project-level rollups stay approved-only (out of scope for MAPPS-167).

Test drives the lifecycle by setting approval_status directly so it pins the rollup SELECT rather than the timesheet state machine: a draft entry shows in logged_hours but not actual_hours; approving makes both reflect it; rejecting drops it from both. The list read path is asserted to carry logged_hours too.

#PMS-329

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/PMS-329-task-logged-hours 2026-06-15 21:08:08 +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-server!249
No description provided.