Month-end engine — turns a period of metered usage into tenant invoices (revenue side). Distinct from cloud_invoices, which are DO's bills to Sky AI (COGS). tenant_invoices — one per (tenant, period). subtotal/tax/total cents, status draft/issued/paid/void. unique (tenant_id, period_start). tenant_invoice_lines — kind plan_base/addon/overage/tax, tagged with deployment_id (NULL for tenant-level lines like GST) + resource_kind, so the cost-vs-revenue dashboard can group by deployment and by kind. ArcadiaCloud.Invoicing.roll_up_period/3: - groups active subscriptions by tenant - one tenant_invoice per tenant; per subscription, runs the quote engine with the deployment's ACTUAL metered usage (Metering.usage_for_period) and persists the recurring + overage lines tagged with the deployment - appends a tenant-level GST line (AU 10%, per project_skyai_australia) - idempotent on (tenant_id, period_start); re-run skips unless force:true Because the same quote engine serves provisioning-time projection and month-end invoicing, a tenant's quoted price and invoiced price are computed identically. InvoiceRollupWorker — Oban cron, 1st of month 03:00 UTC, invoices the month just ended. API (platform_admin sees all; tenants scoped to own): - GET /api/v1/invoices — tenant invoice list - GET /api/v1/invoices/:id — invoice with lines Also: SubscriptionAddon now preloads its :addon so quote/invoice lines read "Addon: storage_50gb" rather than the addon UUID. Smoke verified: pilot deployment on Studio + storage_50gb, 3 droplets metered across all 30 days of April (2160 droplet_hours vs 1488 included) — rollup produced an invoice with plan_base $50 + addon $7.50 + droplet_hours overage $6.72 (672h x 1c) = $64.22 subtotal, GST $6.42, total $70.64. Re-run without force correctly skipped. NOT in this chunk: pushing tenant invoices to skyai-finance as AR — that needs an income-side endpoint on skyai-finance (the phase-1 push endpoint creates vendor expense invoices, wrong direction). Deferred to its own chunk. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
44 lines
1.2 KiB
Elixir
44 lines
1.2 KiB
Elixir
defmodule ArcadiaCloudWeb.Router do
|
|
use ArcadiaCloudWeb, :router
|
|
|
|
pipeline :api do
|
|
plug :accepts, ["json"]
|
|
end
|
|
|
|
pipeline :authed do
|
|
plug ArcadiaCloudWeb.Plugs.RequireAuth
|
|
end
|
|
|
|
scope "/api", ArcadiaCloudWeb do
|
|
pipe_through :api
|
|
|
|
get "/health", HealthController, :show
|
|
end
|
|
|
|
scope "/api/v1", ArcadiaCloudWeb do
|
|
pipe_through [:api, :authed]
|
|
|
|
get "/inventory", InventoryController, :index
|
|
|
|
get "/billing/balance", BillingController, :balance
|
|
get "/billing/cost-lines", BillingController, :cost_lines
|
|
|
|
get "/drift", DriftController, :index
|
|
post "/drift/:id/accept", DriftController, :accept
|
|
|
|
get "/catalog/plans", CatalogController, :plans
|
|
get "/catalog/addons", CatalogController, :addons
|
|
|
|
post "/quote", QuoteController, :create
|
|
|
|
get "/deployments", DeploymentController, :index
|
|
post "/deployments", DeploymentController, :create
|
|
get "/deployments/:id", DeploymentController, :show
|
|
post "/deployments/:id/transition", DeploymentController, :transition
|
|
post "/deployments/:id/subscribe", DeploymentController, :subscribe
|
|
|
|
get "/invoices", InvoiceController, :index
|
|
get "/invoices/:id", InvoiceController, :show
|
|
end
|
|
end
|