# fusion_centralize_billing — Session Handoff (2026-05-27) Resume point for the centralized-billing initiative. Read this first, then continue from **"Decision pending"** below. ## Where we are - **Sub-project #1 (core billing engine): DONE and on `main`** (tip `d770c0c3`, pushed to GitHub + Gitea). - 11/11 plan tasks, TDD, Opus code-reviewed; all Critical/High bugs fixed (cross-billing cron → match by `plan_id`; `/usage` authz vs IDOR; input validation → 4xx not 500; correct billing-period window; idempotency scoped to `(sub, metric, key)`; webhook sign-exact-bytes + event-id + SSRF guard). - **39 tests green on Odoo 19 Enterprise.** - Note: the 14 billing commits were rebased off the old login-audit/helpdesk stack and landed cleanly on `main`. `fusion_login_audit` was deliberately **not** landed — it stays on `feat/fusion-login-audit`. A concurrent `feat/helpdesk-customer-followup` session still carries a pre-landing copy of the billing commits; when it merges, replay its helpdesk-only commits onto `main`. - **Reference docs (on `main`):** - Spec: `docs/superpowers/specs/2026-05-27-nexa-billing-centralized-design.md` - Core plan: `docs/superpowers/plans/2026-05-27-fusion-centralize-billing-core.md` ## Next: sub-project #2 — NexaCloud adapter + dual-run reconciliation Per spec §12, each sub-project is its own spec → plan → build cycle. #2 decomposes into four chunks (dependency order): | Chunk | What | Risk | |-------|------|------| | **2a — Mapping + importer** | Read `nexacloud` DB → create `res.partner` + `account.link`, `product.template` + subscription plans, one subscription `sale.order` per deployment | **Low** — read-only on NexaCloud, writes only into Odoo | | **2b — Usage metering wiring** | NexaCloud `usage_metering.py` pushes CPU-seconds → Odoo `/usage`; verify aggregation → draft invoice w/ quota + overage + HST | Edits NexaCloud code | | **2c — Control loop** | NexaCloud consumes Odoo's outbound webhooks (`invoice.payment_failed` → suspend via existing `network_isolation`/`throttle_checker`; `subscription.terminated` → deprovision) | Edits NexaCloud code | | **2d — Dual-run reconciliation** | `fusion.billing.reconciliation` diffs Odoo-computed vs NexaCloud-actual per customer/period for ≥ 1 cycle before any flip | Safety gate before flipping real billing | The core engine already built the *receiving* side (`/usage`, webhook engine, charge math). #2 is about **connecting NexaCloud to it and proving the numbers match before flipping.** ## Decision pending (resume here) We were in the `superpowers:brainstorming` flow for #2 and stopped at: **which slice to start with?** - **(recommended) 2a — Mapping + importer** — lowest risk, foundation for everything else. - 2d — Reconciliation first (front-load the trust mechanism). - Full #2 design as one spec, then one plan. - Just write the #2 plan, no code this session. ## Open questions to resolve before building #2 - **Spec §15 Q2 — NexaCloud billing granularity:** confirm **one subscription per deployment** (spec leans this way) vs one subscription per customer with deployment line items. - **Access / environments needed:** - Read access to the `nexacloud` DB schema (LXC 102 / its Postgres on LXC 201) to design the importer mapping. - A NexaCloud staging or safe path for 2b/2c (they edit live NexaCloud code). - Test target for the Odoo side stays the odoo-trial Enterprise sandbox. - **Resolved already:** Stripe is one account (`acct_1ShlA9IkwUB1dVox`) for everything — no account migration (spec §11 / §15 Q1). Branch strategy — land on `main`, branch new work off `main`. ## How to run / test - **Billing tests:** `bash scripts/fcb_test_on_trial.sh` from repo root → pass = `FCB_EXIT=0` (~1–2 min). Syncs the module to the odoo-trial Enterprise sandbox (Proxmox VM 316, db `trial`) and runs `--test-enable`. Local dev Odoo is Community and **cannot** install this module. ## Branch hygiene (lesson from this session) Cut each new feature branch from `main`, and land it before starting the next. For any cross-branch git surgery, use a **throwaway `git worktree`** — never switch the shared working dir's branch, because a concurrent session may be working on it.