feat(time-tracking): rounding, rate derivation, timesheet approval, tenant scoping #58

Merged
longjacksonle merged 1 commit from feat/time-tracking-resolve-rounding into main 2026-06-04 05:56:20 +02:00

What

Composes the existing tickets and time_tracking surfaces into a demoable Milestone 1 Service Desk slice: a technician opens a ticket, times work, and submits a timesheet a manager approves. Every Service Desk response is now priced and tenant-scoped, no 501s on the slice.

The starting point was further along than the dev-docs snapshot claimed: F3 (ticket DTO joins) and the F8 time_tracking module already existed. This PR fills the real gaps.

Changes

  • Rounding applied. apply_rounding (floor to minimum_minutes, then round to increment_minutes; exact midpoint rounds up) runs on create_time_entry and stop_timer using the tenant default rule. Billing-critical order documented on the fn.
  • Rate derived from work type. resolve_billing precedence: explicit request rate > work_type.default_rate > none. stop_timer now prices the entry instead of inserting NULL rate/total with a hardcoded is_billable = TRUE.
  • Timesheet approval state machine. POST /timesheets/:user/:week/{approve,reject} (manager+), week-level approval_status rollup on TimesheetSummaryResponse, empty/already-approved week returns a zeroed summary instead of 404. No real submitted state yet (documented as debt).
  • Write-side tenant scoping. create_time_entry/start_timer validate work-type/ticket/company ownership; stop_timer company inference is tenant-scoped; single-active-timer race maps to Conflict.
  • Shareable module. time_tracking/mod.rs moved to the gated convention so the client carries a byte-identical copy (companion PR in mokosh-apps).

Testing

  • tests/time_tracking.rs: two-actor happy path (technician times + submits, manager approves) + technician-cannot-approve guard, against real Postgres.
  • Pure-fn unit tests for apply_rounding and resolve_billing.
  • Full suite green: 93 lib + 5 integration tests. fmt + clippy (--all-targets -D warnings) clean.
  • scripts/demo-service-desk.nu added as the 2026-06-05 rehearsal walkthrough.
  • Stale F3/F8/time_tracking dev-docs entries refreshed.

🤖 Generated with Claude Code

## What Composes the existing tickets and time_tracking surfaces into a demoable Milestone 1 Service Desk slice: a technician opens a ticket, times work, and submits a timesheet a manager approves. Every Service Desk response is now priced and tenant-scoped, no 501s on the slice. The starting point was further along than the dev-docs snapshot claimed: F3 (ticket DTO joins) and the F8 time_tracking module already existed. This PR fills the real gaps. ## Changes - **Rounding applied.** `apply_rounding` (floor to `minimum_minutes`, then round to `increment_minutes`; exact midpoint rounds up) runs on `create_time_entry` and `stop_timer` using the tenant default rule. Billing-critical order documented on the fn. - **Rate derived from work type.** `resolve_billing` precedence: explicit request rate > `work_type.default_rate` > none. `stop_timer` now prices the entry instead of inserting NULL rate/total with a hardcoded `is_billable = TRUE`. - **Timesheet approval state machine.** `POST /timesheets/:user/:week/{approve,reject}` (manager+), week-level `approval_status` rollup on `TimesheetSummaryResponse`, empty/already-approved week returns a zeroed summary instead of 404. No real `submitted` state yet (documented as debt). - **Write-side tenant scoping.** `create_time_entry`/`start_timer` validate work-type/ticket/company ownership; `stop_timer` company inference is tenant-scoped; single-active-timer race maps to `Conflict`. - **Shareable module.** `time_tracking/mod.rs` moved to the gated convention so the client carries a byte-identical copy (companion PR in mokosh-apps). ## Testing - `tests/time_tracking.rs`: two-actor happy path (technician times + submits, manager approves) + technician-cannot-approve guard, against real Postgres. - Pure-fn unit tests for `apply_rounding` and `resolve_billing`. - Full suite green: 93 lib + 5 integration tests. fmt + clippy (`--all-targets -D warnings`) clean. - `scripts/demo-service-desk.nu` added as the 2026-06-05 rehearsal walkthrough. - Stale F3/F8/time_tracking dev-docs entries refreshed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(time-tracking): apply rounding, derive rates, add timesheet approval + tenant scoping
All checks were successful
Check / fmt + clippy + compile + tests (pull_request) Successful in 54s
Create release / Create release from merged PR (pull_request) Has been skipped
Build OCI container / Build and push mokosh-api image (push) Successful in 6m49s
d0acbb5cc5
Composes the existing tickets and time_tracking surfaces into a demoable Milestone 1 Service Desk slice: a technician opens a ticket, times work, and submits a timesheet a manager approves, with every response priced and tenant-scoped.

Rounding is now applied. A new `apply_rounding` pure fn floors a raw duration to the rule's `minimum_minutes` then rounds to `increment_minutes` (exact midpoint rounds up); it runs on both `create_time_entry` and `stop_timer` using the tenant default rule. The ordering is billing-critical and documented on the function so changing it after entries are billed is an explicit decision.

Billable rate is derived from the work type. `resolve_billing` applies precedence explicit-request-rate > `work_type.default_rate` > none, and `stop_timer` now prices the entry from the work type instead of inserting a NULL rate/total with a hardcoded `is_billable = TRUE`.

Adds a timesheet approval state machine: `POST /timesheets/:user/:week/{approve,reject}` (manager+ via RequireManager) transition `approval_status`, and `TimesheetSummaryResponse` gains a week-level `approval_status` rollup so the client can render a submitted week as "awaiting approval". There is no real `submitted` state (the schema enum is pending|approved|rejected); that is documented as debt on the DTO. Submit on an empty or already-approved week now returns a zeroed summary instead of 404, consistent with approve/reject.

Closes write-side tenant leaks: FKs check existence not ownership, so `create_time_entry` and `start_timer` now verify the work type, ticket, and company belong to the caller's tenant; `stop_timer`'s company-inference query is tenant-scoped; and the single-active-timer TOCTOU race maps to a clean Conflict instead of a raw 500.

Converts `time_tracking/mod.rs` to the gated shared-module convention so the WASM client can carry a byte-identical copy and compile only the model types (the matching client port lands in mokosh-apps).

Tests: `tests/time_tracking.rs` drives the two-actor happy path (technician times and submits, manager approves) plus a technician-cannot-approve guard against a real Postgres; `apply_rounding` and `resolve_billing` get pure-fn unit tests. Adds `scripts/demo-service-desk.nu` as the rehearsal walkthrough and refreshes the stale F3/F8/time_tracking entries in the dev-docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/time-tracking-resolve-rounding 2026-06-04 05:56:20 +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!58
No description provided.