feat(billing): usage-rating + webhook-dispatch crons

- SaleOrder._fc_rate_usage: aggregates usage, computes overage via
  charge._compute_billable, upserts sale.order.line for the overage product
- FusionBillingUsage._cron_rate_open_periods: hourly cron iterates active
  charges × in-progress subscriptions, calls _fc_rate_usage
- data/ir_cron.xml: two crons — rate usage (hourly), dispatch webhooks (2 min)
- __manifest__.py: registers data/ir_cron.xml in data list
- test_usage.py: test_rate_open_period_creates_overage_line (TDD, FCB_EXIT=0)

Reference: _create_recurring_invoice / _get_invoiceable_lines confirmed in
Enterprise sale_subscription/models/sale_order.py — overage line goes onto
sale.order so native invoicing picks it up via _get_invoiceable_lines.
This commit is contained in:
gsinghpal
2026-05-27 03:08:45 -04:00
parent c44fd89ed1
commit a5db0fe71e
6 changed files with 84 additions and 0 deletions

View File

@@ -59,6 +59,25 @@ class FusionBillingUsage(models.Model):
return existing
return self.create(vals)
@api.model
def _cron_rate_open_periods(self):
"""Hourly cron: for every active charge, aggregate usage and upsert overage lines
on all in-progress subscriptions whose next invoice date is set."""
Charge = self.env['fusion.billing.charge'].search([('active', '=', True)])
SaleOrder = self.env['sale.order']
for charge in Charge:
subs = SaleOrder.search([
('is_subscription', '=', True),
('subscription_state', '=', '3_progress'),
('plan_id.name', '!=', False),
])
for sub in subs:
if not sub.next_invoice_date:
continue
period_end = fields.Datetime.to_datetime(sub.next_invoice_date)
period_start = period_end.replace(day=1)
sub._fc_rate_usage(charge, period_start, period_end)
@api.model
def _aggregate(self, subscription, metric, period_start, period_end):
"""Aggregate stored usage for a subscription+metric within [period_start, period_end)