feat(billing): design + scaffold fusion_centralize_billing
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>
This commit is contained in:
@@ -31,6 +31,8 @@
|
||||
13. **`ir.cron` dropped `numbercall`** in Odoo 19. Old recipes set `<field name="numbercall">-1</field>` for "run forever"; that now raises `ValueError: Invalid field 'numbercall' in 'ir.cron'` at install time. Just omit the field — recurring crons keep running as long as `active=True`. Source: `odoo/addons/base/models/ir_cron.py` field list.
|
||||
14. **`cr.commit()` / `cr.rollback()` raise AssertionError inside `TransactionCase`** — they are NOT silent no-ops in Odoo 19. The test cursor explicitly refuses both ("Cannot commit or rollback a cursor from inside a test, this will lead to a broken cursor when trying to rollback the test. Please rollback to a specific savepoint instead..."). For cron/worker code that needs per-row isolation so one bad row doesn't roll back the whole batch, use `with self.env.cr.savepoint(): ...` inside the loop instead of `cr.commit()`. Savepoints work in both prod (under the outer cron transaction) and tests (under the outer test transaction). The cron transaction commits the whole batch when the method returns; in tests everything rolls back cleanly. Source: `odoo/sql_db.py::TestCursor.commit` and `Cursor.savepoint()`.
|
||||
|
||||
15. **There is NO `sale.subscription` model in Odoo 19** (Enterprise `sale_subscription`). A subscription is a **`sale.order`** with `is_subscription=True`, `plan_id` → **`sale.subscription.plan`** (the recurrence), plus `subscription_state` / `next_invoice_date` / `recurring_monthly`. Any Many2one or relation that targets "a subscription" must point at `sale.order` (filter `domain=[('is_subscription','=',True)]`) — **not** `sale.subscription`, which does not exist and fails at install. The surviving `sale.subscription.*` records are only the plan + wizards/reports (`sale.subscription.plan`, `sale.subscription.report`, `sale.subscription.change.customer.wizard`, `sale.subscription.close.reason.wizard`). Verified on live `nexamain` (odoo-nexa, 19.0): `SELECT model FROM ir_model WHERE model LIKE 'sale.subscription%'`.
|
||||
|
||||
## Card Styling — Copy Odoo's Kanban Pattern
|
||||
Don't rely on `var(--bs-border-color)` or `var(--bs-body-bg)` for card surfaces — they drift between themes/addons and often render **invisible**. Odoo's own kanban (`.o_kanban_record`) uses **explicit hex** values:
|
||||
```css
|
||||
|
||||
Reference in New Issue
Block a user