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:
38
fusion_centralize_billing/models/reconciliation.py
Normal file
38
fusion_centralize_billing/models/reconciliation.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class FusionBillingReconciliation(models.Model):
|
||||
"""Dual-run shadow-mode comparison: Odoo-computed vs the app's actual billing.
|
||||
|
||||
During phased cutover (NexaCloud first), Odoo computes invoices while the app
|
||||
keeps charging. This row records the per-customer, per-period delta so we only
|
||||
flip once deltas are within tolerance. See spec §10.
|
||||
"""
|
||||
|
||||
_name = "fusion.billing.reconciliation"
|
||||
_description = "Fusion Billing — Dual-Run Reconciliation"
|
||||
_order = "period desc, service_id"
|
||||
|
||||
service_id = fields.Many2one(
|
||||
"fusion.billing.service", required=True, ondelete="cascade", index=True,
|
||||
)
|
||||
partner_id = fields.Many2one("res.partner", required=True, ondelete="cascade", index=True)
|
||||
period = fields.Char(required=True, help="Billing period label, e.g. 2026-05.")
|
||||
odoo_amount = fields.Monetary()
|
||||
external_amount = fields.Monetary(string="App-actual Amount")
|
||||
delta = fields.Monetary(help="odoo_amount - external_amount.")
|
||||
currency_id = fields.Many2one(
|
||||
"res.currency", default=lambda self: self.env.company.currency_id,
|
||||
)
|
||||
status = fields.Selection(
|
||||
[
|
||||
("match", "Within tolerance"),
|
||||
("delta", "Delta — investigate"),
|
||||
("resolved", "Resolved"),
|
||||
],
|
||||
default="delta", required=True, index=True,
|
||||
)
|
||||
note = fields.Text()
|
||||
Reference in New Issue
Block a user