feat(billing): 2a NexaCloud→Odoo importer (read-only, idempotent, shadow-safe)
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.
This commit is contained in:
@@ -6,3 +6,4 @@ from . import usage
|
||||
from . import webhook
|
||||
from . import reconciliation
|
||||
from . import sale_order
|
||||
from . import res_partner
|
||||
|
||||
12
fusion_centralize_billing/models/res_partner.py
Normal file
12
fusion_centralize_billing/models/res_partner.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = "res.partner"
|
||||
|
||||
x_fc_stripe_customer_id = fields.Char(
|
||||
index=True, copy=False,
|
||||
help="Existing Stripe customer id imported from a source app, reused at flip.")
|
||||
@@ -7,6 +7,16 @@ from odoo import api, fields, models
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = "sale.order"
|
||||
|
||||
x_fc_nexacloud_subscription_id = fields.Char(
|
||||
index=True, copy=False,
|
||||
help="Source NexaCloud subscription id — the importer's idempotency key.")
|
||||
x_fc_nexacloud_deployment_id = fields.Char(index=True, copy=False)
|
||||
x_fc_billing_service_id = fields.Many2one(
|
||||
"fusion.billing.service", index=True, copy=False, ondelete="set null")
|
||||
x_fc_shadow = fields.Boolean(
|
||||
default=False, copy=False,
|
||||
help="Imported in shadow mode: Odoo computes but must not charge/post/email.")
|
||||
|
||||
def _fc_rate_usage(self, charge, period_start, period_end):
|
||||
"""Aggregate this subscription's usage for `charge`'s metric in the period,
|
||||
compute the overage amount, and upsert a matching overage order line.
|
||||
|
||||
Reference in New Issue
Block a user