fix(time-tracking): consume contract hours on timesheet approval (PMS-405) #307

Merged
vas2000-work merged 2 commits from fix/PMS-405-wire-time-to-contract-balance into main 2026-06-18 04:23:59 +02:00
Owner

Wires the time-entry approval transition to ContractsService::consume_hours, fixing the PMS-405 wiring bug where contract_hour_balances never decremented in production because consume_hours had no production callers. On approve_timesheet, every entry that flips from pending to approved and is billable against a contract now draws duration_minutes / 60 hours from the contract's block-hours balance for the entry's period; the pending guard keeps re-approval idempotent so hours are never double-counted, and hours past included_hours fall out as a non-zero overage that is persisted via the debited balance row and logged for the recurring-invoice follow-up. No new contract-level hours column is introduced (allotment remains contract_items.included_hours). Adds integration tests asserting the within-allotment decrement, the overage split, and the re-approval no-op.

#PMS-405

Wires the time-entry approval transition to `ContractsService::consume_hours`, fixing the PMS-405 wiring bug where `contract_hour_balances` never decremented in production because `consume_hours` had no production callers. On `approve_timesheet`, every entry that flips from pending to approved and is billable against a contract now draws `duration_minutes / 60` hours from the contract's block-hours balance for the entry's period; the `pending` guard keeps re-approval idempotent so hours are never double-counted, and hours past `included_hours` fall out as a non-zero overage that is persisted via the debited balance row and logged for the recurring-invoice follow-up. No new contract-level hours column is introduced (allotment remains `contract_items.included_hours`). Adds integration tests asserting the within-allotment decrement, the overage split, and the re-approval no-op. #PMS-405
fix(time): consume contract hours on timesheet approval
Some checks failed
Check / fmt + clippy + compile + unit/doc tests (pull_request) Failing after 6s
E2E / Playwright against staging (pull_request) Successful in 35s
Integration / integration tests (pull_request) Successful in 3m56s
9926b2ef13
Wire the time-entry approval transition to ContractsService::consume_hours so a contract's block-hours balance actually decrements as billable time is approved (PMS-405). consume_hours already resolved the block-hours item, found or created the period balance row, split applied-vs-overage, and debited the row, but it had zero production callers, so contract_hour_balances stayed at hours_used = 0 in production.

approve_timesheet now RETURNs the entries that transitioned from pending to approved and, for each billable entry carrying a contract_id, draws duration_minutes / 60 hours against the contract for the entry-date period. The pending guard keeps re-approval idempotent (a second call returns no rows), so hours are never double-counted. Non-billable entries and entries without a contract_id are skipped.

Hours past the period's included_hours fall out as a non-zero overage on ConsumeOutcome and are persisted via the debited contract_hour_balances row; the overage split is also logged so the recurring-invoice path or a thin follow-up can bill it rather than silently dropping it. No new contract-level hours column is introduced: the allotment remains contract_items.included_hours on the primary block_hours item.

Adds integration tests under tests/contracts.rs that log and approve billable time against a block-hours contract and assert both the within-allotment decrement and the overage split, plus a re-approval no-op assertion.

#PMS-405

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
style: match CI rustfmt in contracts tests (PMS-405)
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 37s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m20s
Integration / integration tests (pull_request) Successful in 6m59s
Create release / Create release from merged PR (pull_request) Has been skipped
ea42a1879e
CI runs a rustfmt that collapses the short seed_user binding to one line and expands the seed_time_entry argument lists; match it so cargo fmt --check passes.

#PMS-405
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!307
No description provided.