fix(billing): gate invoicing on timesheet approval (PMS-144) #114

Merged
longjacksonle merged 1 commit from fix/pms-144-billing-approval-gate into main 2026-06-05 20:23:05 +02:00

What

Closes PMS-42 AC4 ("Approved billable entries surface as ready_to_bill for PMS-33"). Re-verifying PMS-42 turned up that this criterion was not implemented:

  1. Nothing in src/ ever set billing_status='ready_to_bill' - approve_timesheet only touched approval_status, so the status was dead.
  2. create_invoice_from_time_entries selected billing_status IN ('ready_to_bill','not_billed') with no approval predicate, so any billable entry - including pending or rejected - was invoiceable.

Fix

  • time_tracking::approve_timesheet: in the same statement that sets approval_status='approved', flip billable not_billed rows to ready_to_bill (non-billable / already-invoiced rows untouched).
  • billing::create_invoice_from_time_entries: consume only billing_status='ready_to_bill'. Approval is now the gate; unapproved time cannot be invoiced.

Tests (pass against the dev DB)

  • tests/time_tracking.rs: asserts the billable entry is ready_to_bill after the manager approves.
  • tests/billing.rs: adds a billable-but-unapproved (not_billed) negative control and asserts invoice generation skips it; the existing lines.len() == 2 check also now guards it.
test service_desk_time_slice_happy_path ... ok
test generate_invoice_from_time_entries ... ok
test payment_against_generated_invoice_transitions_status ... ok

cargo fmt --check clean; cargo clippy --test billing --test time_tracking -- -D warnings clean.

🤖 Generated with Claude Code

## What Closes **PMS-42 AC4** ("Approved billable entries surface as `ready_to_bill` for PMS-33"). Re-verifying PMS-42 turned up that this criterion was not implemented: 1. Nothing in `src/` ever set `billing_status='ready_to_bill'` - `approve_timesheet` only touched `approval_status`, so the status was dead. 2. `create_invoice_from_time_entries` selected `billing_status IN ('ready_to_bill','not_billed')` with **no approval predicate**, so any billable entry - including `pending` or `rejected` - was invoiceable. ## Fix - `time_tracking::approve_timesheet`: in the same statement that sets `approval_status='approved'`, flip billable `not_billed` rows to `ready_to_bill` (non-billable / already-invoiced rows untouched). - `billing::create_invoice_from_time_entries`: consume only `billing_status='ready_to_bill'`. Approval is now the gate; unapproved time cannot be invoiced. ## Tests (pass against the dev DB) - `tests/time_tracking.rs`: asserts the billable entry is `ready_to_bill` after the manager approves. - `tests/billing.rs`: adds a billable-but-unapproved (`not_billed`) negative control and asserts invoice generation skips it; the existing `lines.len() == 2` check also now guards it. ``` test service_desk_time_slice_happy_path ... ok test generate_invoice_from_time_entries ... ok test payment_against_generated_invoice_transitions_status ... ok ``` `cargo fmt --check` clean; `cargo clippy --test billing --test time_tracking -- -D warnings` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(billing): gate invoicing on timesheet approval (ready_to_bill)
All checks were successful
E2E (staging) / Playwright against staging (pull_request) Successful in 22s
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m27s
Build OCI container / Build and push mokosh-api image (push) Successful in 3m18s
Create release / Create release from merged PR (pull_request) Has been skipped
4bd93f4264
Closes PMS-42 AC4. Approved billable time entries are meant to "surface as ready_to_bill for PMS-33", but nothing produced that status and billing ignored approval entirely: create_invoice_from_time_entries consumed billing_status IN ('ready_to_bill','not_billed') with no approval predicate, so pending or even rejected billable time was invoiceable, and ready_to_bill was dead status.

Make approval the billing gate. approve_timesheet now flips billable, not-yet-billed entries to billing_status='ready_to_bill' in the same UPDATE that sets approval_status='approved' (non-billable and already-invoiced rows untouched). create_invoice_from_time_entries consumes only billing_status='ready_to_bill', so unapproved time can no longer be invoiced.

Tests: tests/time_tracking.rs asserts the billable entry becomes ready_to_bill after the manager approves; tests/billing.rs adds a billable-but-unapproved (not_billed) negative control and asserts it is skipped by invoice generation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch fix/pms-144-billing-approval-gate 2026-06-05 20:23:05 +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!114
No description provided.