# 22 — Phase 6 Checklist & Status

Working tracker for [13 — Implementation Plan, Phase 6](13-implementation-plan.md) —
billing engine & collections, the plan's declared **highest-risk module**. Status date:
**2026-08-05**.

---

## Exit criteria (doc 13)

| Criterion | Status | Evidence |
|---|---|---|
| A month runs end-to-end on **all four cycle types** | ✅ Automated | `BillingTest::testAMonthRunsEndToEndOnAllFourCycleTypes`: 61 simulated daily cron runs across DAILY / EVERY_N_DAYS(7) / MONTHLY-EOM / MONTHLY-anchor-25 + a zero-invoice account — grids never drift, zero-activity periods close silently, gapless `INV-YYYY-######` numbering across all accounts, full-loop re-run adds nothing, the cap summary block sums to the invoice, a late August charge rolls forward flagged and **the closed August invoice never changes** |
| One account goes overdue → **auto-held** then **auto-released on payment, no manual intervention** | ✅ Automated | `testOverdueAutoHoldThenAutoReleaseOnPaymentNoManualIntervention`: due 10 Sep → overdue + reminder 1; grace 15 Sep passes → reminder 2 + `on_hold(overdue)` + collections log + alert, **audit actor = SYSTEM, source = system**; partial payment keeps the hold; the clearing payment allocates, pays the invoice, releases the hold and resolves the alert — every step machine-actored |
| **Two cycles match the spreadsheet exactly** | ✅ Engineering half · 🔲 **External** live half | `testTwoCyclesReconcileLineByLineAgainstTheStatedExpectation`: an independently-stated expectation table vs engine output — line count, per-line day/amount/invoice, per-invoice totals, every charge billed exactly once. Real-world half: run two live cycles, export with `php tools/billing-export.php <code|all> <from> <to> > cycle.csv`, and reconcile against the accountant's manual sheet **line by line before trusting the engine** (doc 11's explicit instruction — protect these two cycles from schedule compression, doc 13 watch-item 1) |

Suites: **168 PHPUnit tests / 693 assertions** (7 new billing tests, 101 assertions) and
**292 PWA assertions**.

## Delivered

- **Billing run** (`billing:run` cron, hourly; manual button for Accountant/Owner):
  period grids for all four modes via the shared `BillingPeriod` (D29 — caps and
  invoices agree by construction), idempotent on (account, period), catch-up over
  missed days, roll-forward of late arrivals flagged on line and header (D31),
  zero-activity handling with per-account `issue_zero_invoices`, terms changes parked
  and promoted at the next period close (D32), deterministic `issue_date = period_end`
  (D30), `due = issue + due_days`, `grace_until = due + grace_days`.
- **Invoices**: gapless central numbering via row-locked `number_sequences` taken
  inside the issuing transaction; immutable once issued; cancellation only while
  nothing is allocated (Owner, reasoned, audited) — the number stays burned and the
  charges return to unbilled; **cap summary block** (cap / covered / paid personally /
  unused / overage approvals — only "covered" sums to the invoice); branch breakdown;
  three statement groupings (full / by-day / by-person) rendered per account default
  and switchable per view.
- **Payments & allocations**: oldest-invoice-first (a targeted credit note leads with
  its invoice), overpayment sits as unapplied credit and **auto-applies to the next
  invoice**; central payment entry (`RB-` receipts, bank transfer/cheque included);
  payment reversal as an immutable negative document mirroring the original's
  allocations; credit notes (`CN-` sequence, Accountant); write-offs (Owner approval —
  Owner-direct is the approval, others name the approving Owner; self-approval
  rejected), closing the invoice as a recognised loss.
- **Aging & collections** (doc 05 §7): overdue from `due_date`, buckets
  current/1–30/31–60/61–90/90+; reminder queue (stage 1 on due, stage 2 past grace,
  manual send-marking until the Phase 7 transport); auto-hold past grace when the
  account opts in, auto-release when the overdue balance clears — both by the SYSTEM
  actor (D34), logged in `collections_log`, alerting the back office.
- **AR report suite** `/admin/reports`: aging summary + open AR + simplified DSO,
  unbilled-charges exposure, cap utilization per person mid-cycle + overage approvals,
  write-offs & credit notes; `/admin/invoices` list with status filter + pending
  reminders; invoice detail with groupings, cap block, allocations, and the money
  actions.
- **POS**: the pull `account` entity now carries `overdue_past_grace_iqd` /
  `overdue_oldest_days` (contract [1.5.0](15-sync-contract.md)) — charging blocks hard
  with the amount and age on screen (doc 03 §5); auto-held accounts flow down as
  `on_hold` via the existing snapshot machinery.
- **Charge lifecycle**: `order_charges.status` unbilled → billed/voided with
  `invoice_id`; a void arriving for an already-billed charge leaves it billed and
  drops a `void_after_billing` note in the collections log pointing at a credit note
  (doc 05 §2).

## Deferred, with reasons

| Item | Where it lands |
|---|---|
| Manual reallocation of payments across invoices | Doc 05 §5 rule 2 allows it; the oldest-first default is correct until an accountant actually asks |
| Reminder auto-send (WhatsApp/Telegram) | Phase 7 owner-digest transport — the queue, stages and logging are ready |
| Invoice print/PDF layout + stamped legal format | Blocked on business answer Q2 (doc 17 §3); the statement views print via the browser meanwhile |
| Signature images on statements | Digital capture deferred in Phase 5; the printed slip line stands in |
| `collected_in_person` / delivery timestamps workflow | Column exists; marking UI when delivery tracking is actually operated |
| Top-accounts-by-revenue/risk report | The aging table already ranks by exposure; revenue ranking joins Phase 7's comparison suite |

## Notes for Phase 7 (rollout & owner visibility)

The collections ladder's missing rung is transport: reminders and the auto-hold alert
should ride the owner daily digest (WhatsApp/Telegram) that Phase 7 ships first.
Cross-branch charging already works end to end (`allow_cross_branch`, provisional
labels, central ledger); rollout needs the per-branch onboarding drill from doc 13 and
five-branch load on `sync:process`. Branch comparison reads `rollup_daily_sales`
(incl. `on_account_iqd` / `account_payments_iqd`) — no new ledger work expected.
