docs(billing): handoff update — sub-project #2 complete (2a/2d shipped, 2b/2c code-complete)

This commit is contained in:
gsinghpal
2026-05-27 14:52:28 -04:00
parent a82f09ea50
commit 1f5eaf0386

View File

@@ -78,3 +78,51 @@ start with?**
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.
---
## UPDATE — sub-project #2 complete (2026-05-27, later session)
All four chunks of #2 are now built. The brainstorm "which slice" question resolved to
2a-first; everything else followed.
**Done + on `main` in `Odoo-Modules` (fully tested on odoo-trial, suite `FCB_EXIT=0`):**
- **2a — importer** (`fusion.billing.import.wizard`): read-only `psycopg2` reader split
from pure-Odoo writes; users→partners+links, plans→`cpu_seconds` charge catalog
(`plan_id` NULL), deployments→one **draft shadow** `sale.order` each with the flat price.
Shadow-safe by construction (draft + no token + NULL `plan_id`). Idempotent, dry-run,
Test-Connection guard, README runbook.
- **2d — reconciliation** (`fusion.billing.reconciliation`): `_compute_reconciliation` +
`_reconcile_rows` (Odoo flat+overage vs NexaCloud actual, status match/delta), reader for
NexaCloud usage+invoice actuals, "Run Reconciliation" button. **Upsert key is
`(service, external_subscription_id, period)`** — per subscription, so a customer with
two deployments doesn't collide.
- **/usage enabler**: `_api_record_usage` resolves a subscription by the source app's own
id (`x_fc_nexacloud_subscription_id`) so NexaCloud can push against shadow subs.
- Core-engine bug fixed in passing: `charge.price_per_unit` is now `Float(16,6)` and
`_compute_billable` keeps 6-dp precision (was `Monetary`/cent-rounded → would under-bill
sub-cent rates and drift from NexaCloud's 4-dp amounts).
**Code-complete in `Nexa-Cloud` (feature-flagged, NOT deployed, NOT integration-tested):**
- **2b — usage push**: `services/odoo_billing_client.py` + a hook in `usage_metering.py`
posting cpu-seconds to Odoo `/usage`. **2c — control loop**:
`routers/odoo_billing.py` (`POST /api/v1/billing/webhooks/central`, HMAC-verified) +
`services/odoo_billing_integration.py` (suspend/restore/deprovision). All INERT unless
`ODOO_BILLING_ENABLED`. Implemented as NEW modules + edits to clean files only —
NexaCloud `main` had concurrent **Cursor uncommitted WIP** (`routers/billing.py`,
`scheduler.py`, `stripe_service.py`, `models/billing.py`, …) which was deliberately not
touched. Commits: `94542ec` + `956abb2` (only my files staged).
**Remaining before go-live (gated on infra / ops you do):**
1. Grant the read-only DSN (`fusion_billing.nexacloud_dsn`) — see the module README — then
Test Connection → dry-run import → review → real import.
2. Run a dual-run cycle (Run Reconciliation), confirm all rows `match`.
3. **2c needs the Odoo side to actually EMIT** `invoice.payment_failed` /
`payment_succeeded` / `subscription.terminated` webhooks with `deployment_id` in the
payload — that emission isn't wired yet (it belongs to the live billing flow). The
NexaCloud receiver is built to that contract; confirm the payload shape when wiring it.
4. Integration-test + deploy the NexaCloud changes (no test harness in that repo).
5. The flip: set `charge.plan_id`, attach Stripe tokens, confirm the shadow subs.
Specs/plans: `specs/2026-05-27-nexacloud-billing-importer-design.md`,
`specs/2026-05-27-nexacloud-reconciliation-design.md`, and the matching plans.