docs(billing): handoff update — sub-project #2 complete (2a/2d shipped, 2b/2c code-complete)
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user