fusion.billing.import.wizard backfills NexaCloud into Odoo: read-only
psycopg2 reader (_read_nexacloud_rows, DSN from ir.config_parameter)
split from pure-Odoo writes (_import_rows/_do_import) so the logic is
unit-tested headless. Maps users→partners+links (reusing
_resolve_or_create_partner, stashing stripe_customer_id), plans→a
cpu_seconds charge catalog (included_quota=cpu_seconds_quota,
unit_batch=3600, $0.0075/core-hour, plan_id NULL), and deployments→one
DRAFT shadow sale.order per deployment with the flat price set
explicitly. Shadow-safe by construction: draft + no payment token +
charge plan_id NULL (rating cron is a no-op). Idempotent re-runs;
per-row savepoints isolate bad rows; dry-run rolls back. 11 tests,
50/50 green on odoo-trial.
Centralize billing for all NexaSystems services (NexaCloud, NexaDesk,
NexaMaps, custom apps, memberships) on the Odoo 19 Enterprise instance,
replacing Lago. The module adds only the metering + integration layer;
native sale_subscription / account_accountant / payment_stripe do all the
financial work (invoicing, HST, dunning, portal, credit notes, Stripe).
Includes:
- Design spec (docs/superpowers/specs/2026-05-27-nexa-billing-centralized-design.md):
6 locked decisions, architecture, data model, usage engine, Lago-shaped
API, webhook control loop, NexaCloud pilot, phased dual-run migration.
- Module scaffold: 7 fusion.billing.* models (service, account.link, metric,
charge, usage, webhook, reconciliation), bearer-auth API controller shell,
security ACLs, README. Compiles on Odoo 19.0; engine/API bodies are stubs
pending the implementation plan.
- CLAUDE.md rule #15: no sale.subscription model in Odoo 19 — a subscription
is a sale.order(is_subscription) + sale.subscription.plan (verified live).
Task 0 verified: a single Stripe account is shared across NexaCloud and all
Lago providers, so no Stripe account/card migration is required.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>