# Phase 3 — PlusROI write forms (design spec)

Drives `docs/goals/plusroi-soft-launch.md` Phase 3. Every form writes to a
table that already exists (ADR #012/#013/#016); the only new schema is a
small additive migration (alembic 0008). All endpoints are **owner-gated**
(`can_transfer`/`can_see_income`), refused when `books_owner == "sheet"`
(the Phase-2 write-block), **audited**, and **tenant-scoped**. The executor
writes ADR #022 + updates `docs/features/plusroi-books.md` before code.

## New schema — alembic 0008 (additive)
- `project_billing`: **+`is_recurring` bool**, **+`cycle_months` int**,
  **+`billing_timing` text** (`month_end`|`mid_month`) — the clean
  recurrence model (replaces the sheet's conflated "type"). Keep
  `invoice_timing`/`billing_type` for back-compat/import mapping.
- **NEW `project_milestones`** (non-recurring schedules): `id`,
  `project_id` FK (CASCADE), `label`, `amount` Numeric(12,2), `currency`
  CHAR(3), `sort_order` int, `generated_invoice_id` FK invoices NULL
  (so a milestone generates at most one invoice). Index (project_id,
  sort_order).
- `invoices`: **+`collected_amount` Numeric(12,2)** + **`collected_currency`
  CHAR(3)** (nullable) — the realized CAD actually collected, recorded at
  Paid for USD invoices (manual_total stays the CAD book value;
  original_amount/currency stays the USD face).

## The forms

### 1. Client + Contacts  (`/clients`)
- **Create/edit a client** = an org party (name, official_name→
  `organizations.legal_name`, address/phone on the org) + a
  `client_profiles` row for THIS tenant (currency, terms, invoice_timing,
  credit_limit, `default_commission_pct` + `commission_party_id`, archived)
  + a `direct_client` engagement to the PlusROI operator party. Reuse
  `services/entities/parties.create_org_party`.
- **Contacts inline**: add N person records (name + email) each linked to
  the client org via a `party_affiliations` row `role='cc_recipient'`
  (email lives on the CONTACT person, per rian — not the org). A Contacts
  page (`/contacts`) lists them; contacts are reusable across clients.
- `POST/PATCH /api/clients`; `POST /api/clients/{id}/contacts`.

### 2. Vendors  (`/vendors`)
- Thin: create an org OR person party (payee). `POST /api/vendors`.
  Match-existing-by-name first (reuse the registry dedup pattern).

### 3. Projects  (`/projects`, PlusROI-shaped)
- Create attached to a client; **inherits** currency/terms/commission/
  timing from the client profile (editable). Then either:
  - **recurring**: `is_recurring=true`, `cycle_months` (1/3/12), `billing_timing`
    (month_end|mid_month), a default `amount`; OR
  - **non-recurring**: a **milestone schedule** — a list of {label, amount}
    (deposit / milestone / final) → `project_milestones` rows.
- `POST/PATCH /api/projects` (writes projects + project_billing +
  milestones).

### 4. Invoice generation + lifecycle  (`/invoices`)
- **Generate from a project**: `POST /api/projects/{id}/invoices` → a new
  invoice `status='pending_approval'`, to_party=client, settlement_month=
  target month, amount **defaulting from the project** but **editable
  per-invoice without changing the project default** (Rob's $1,500→$2,000).
  For a **recurring** project → one invoice for the chosen month. For a
  **milestone** project → **generate all milestones at once** (one invoice
  per un-generated milestone; stamp `generated_invoice_id`).
- **Batch month-end** (Danielle): `GET /api/projects?recurring=1&timing=month_end`
  then generate per project → each `pending_approval`.
- **Status lifecycle** (per-invoice + batch): `pending_approval → ready →
  sent (record `external_id` = QuickBooks #) → paid`. At **paid**, if the
  invoice is USD, record `collected_amount` (CAD). `cancelled`/`delayed`
  available. `PATCH /api/invoices/{id}` handles transitions (reuse the
  existing invoices router — now writable under books_owner=app).

### 5. Expenses  (`/expenses`)
- **Manual entry**: vendor (picker), project (picker, nullable), amount,
  currency, category, paid_by (PlusROI|Bowden Works|other party), date,
  settlement_month. `source='manual'`. `POST /api/expenses`.
- **Multi-line / per-project** (Yulia): one submit → N expense rows sharing
  vendor+date, split by project+amount. `POST /api/expenses/batch`.
- **CC extractor → PlusROI**: the existing CC tool, extended so a batch's
  lines can be **ported into PlusROI expenses** ("move to shared expenses"
  → creates `expenses` rows). **Per-user batch privacy**: CC batch list
  scopes to `created_by == actor.user_id` (Rob sees only his; rian only
  his) — a change from BW's owner-sees-all (moot there: one owner).

### 6. Settlement "apply to month"  (`/books/settlement`)
- An action to **tag** selected paid invoices + unapplied expenses with a
  `settlement_month` (Rian's month-end apply). `POST /api/settlement/apply
  {month, invoice_ids?, expense_ids?}`. The settlement engine already
  computes the split + copy-lines from `settlement_month` — this just sets
  the tag on app-owned rows.

## Sequencing
Migration 0008 first (single-writer, after Phase 2 releases the lock) →
Clients+Contacts → Vendors → Projects → Invoice gen+lifecycle → Expenses+CC
→ settlement-apply. Each ships with tests (round-trip, owner-gate, tenant +
per-user isolation) and a browser check before the persona gate (Phase 4).
