# Time Billing Runbook (Release 1)

## Enable for one company

**UI (preferred):** Project Config → Time billing → feature flag switch (confirm dialog). Requires `project.time-billing-manage`.

**CLI:**

```bash
php artisan time-billing:flag {company_id} --enable
```

API: `PUT /api/v1/project/time-billing/flag` with `{ "enabled": true }`.  
Confirm with `GET /api/v1/project/time-billing/status` (`enabled: true`).  
Rollback = UI switch off, `--disable`, or `{ "enabled": false }`. Ledger/invoice rows remain; routes return 404 while the flag is off.

## Configure

1. Company policy: `GET/PUT /api/v1/project/time-billing/settings` (or Project → Time billing settings UI).
2. Contract: enable time billing, set `hourly_rate`, `time_entry_source` (`ptm` or `manual`, default `ptm`), schedule (`invoice_frequency`, `invoice_cutoff_day`, `next_invoice_period_start`).
3. Env for sync (PTM source only): `TIME_BILLING_PTM_BASE_URL`, `TIME_BILLING_PTM_CLIENT_ID`, `TIME_BILLING_PTM_CLIENT_SECRET`.

## Hours source

| `time_entry_source` | Hours come from | Sync |
|---------------------|-----------------|------|
| `ptm` (default) | PTM export → ledger | Yes |
| `manual` | Manager enters rows in ERPFlow (`POST /time-billing/entries`) | Skipped for that contract |

Both paths share the same ledger statuses, approval, time invoice, and reconciliation.

## Daily / period flow

1. **Hours in:** PTM contracts → Sync now / `POST …/sync` / `php artisan time-billing:sync`. Manual contracts → Add entry on the Time billing tab.
2. **Review:** Contract → Time billing tab. Approve or exclude pending rows. Clear exceptions before invoicing a period.
3. **Invoice:** Create time draft (period + preview). Soft ceiling may require override permission + reason. Issue via invoice detail. Pay with existing AR.
4. **Cancel / rebill:** Cancel unpaid draft/issued time invoice (releases ledger to approved). Create a new draft for the same entries.

## Invoice-due suggestions

Read-only `GET /api/v1/project/time-billing/due`.

| Frequency | Period |
|-----------|--------|
| weekly | `[next_invoice_period_start, +6 days]` |
| bi_weekly | `[start, +13 days]` |
| monthly | `[start, cutoff day in that month]` (or month end if cutoff null) |
| manual / custom | never due |

Due when `period_end < today` and the contract has approved or unreviewed entries in that window.  
`next_invoice_period_start` is **not** auto-advanced on issue in Release 1.

## Reconciliation

`GET /api/v1/project/time-billing/contracts/{id}/reconciliation` (also on the Time billing tab).  
Pilot gate: `ok: true` and empty `mismatches` before issuing.

## Exception codes

| Code | Meaning | Action |
|------|---------|--------|
| `unknown_user` | User not in company | Fix membership; re-sync / re-review |
| `no_time_billing_contract` | No TB contract on project | Enable exactly one TB contract |
| `ambiguous_contract` | Multiple TB contracts | Disable extras |
| `changed_while_reserved` | Source changed on reserved row | Cancel draft or resolve manually |
| `late_hours_rejected` | Hours up after invoice; policy reject | Manual finance decision |
| `needs_manual_resolution` | Hours down / billing change after invoice | Manual resolution |
| `source_deleted_after_billing` | Tombstone after reserve/invoice | Manual resolution |

Tombstone on pending/approved → excluded (`source_deleted`).

## Rollback

1. `php artisan time-billing:flag {company_id} --disable`
2. Do not delete ledger/invoice history unless a separate data cleanup is approved.
