-- name: CreateSubscription :one INSERT INTO core.subscriptions (billing_account_id, status, current_period_start, current_period_end) VALUES ($1, $2, $3, $4) RETURNING *; -- name: GetSubscriptionByID :one SELECT * FROM core.subscriptions WHERE subscription_id = $1; -- name: GetSubscriptionsByBillingAccountID :many SELECT * FROM core.subscriptions WHERE billing_account_id = $1 ORDER BY created_at DESC; -- name: UpdateSubscriptionStatus :one UPDATE core.subscriptions SET status = $2, current_period_start = $3, current_period_end = $4, cancel_at_period_end = $5, canceled_at = $6, ended_at = $7 WHERE subscription_id = $1 RETURNING *; -- name: ListSubscriptionsPage :many -- Operator subscriptions view, paged/searched/filtered (operator-list-scale -- UX-4; operator-billing-views D8). Carries ba.org_id so the handler can -- resolve and link the organization without a cross-module join (see -- billing_accounts.sql's ListBillingAccountsPage comment for why). -- -- sqlc.narg(q): NULL matches every row; set, a case-insensitive substring -- match against the billing account's name (the view's leading identifier -- pre-reorder) or its organization via sqlc.narg(org_ids), pre-resolved in -- Go (operator_billing.go: matchingOrgIDs). -- sqlc.narg(status): NULL matches every status; set, an exact match -- against the subscription's CHECK-constrained status vocabulary -- (00010_schema_hardening.sql: chk_subscriptions_status_valid -- -- incomplete, incomplete_expired, trialing, active, past_due, canceled, -- unpaid, paused). -- count(*) OVER() carries the true total for the filtered set (design D2). SELECT s.subscription_id, s.billing_account_id, ba.org_id, s.status, s.current_period_start, s.current_period_end, s.cancel_at_period_end, s.canceled_at, s.ended_at, s.created_at, s.updated_at, ba.name as billing_account_name, count(*) OVER() AS total_count FROM core.subscriptions s JOIN core.accounts ba ON s.billing_account_id = ba.billing_account_id WHERE (sqlc.narg(q)::text IS NULL OR ba.name ILIKE '%' || sqlc.narg(q)::text || '%' OR ba.org_id = ANY(sqlc.narg(org_ids)::uuid[])) AND (sqlc.narg(status)::text IS NULL OR s.status = sqlc.narg(status)::text) ORDER BY s.created_at DESC LIMIT sqlc.arg(page_limit)::int OFFSET sqlc.arg(page_offset)::int; -- name: CountLiveSubscriptionsByStatus :one -- Both halves of the operator overview's Monthly recurring caption in one -- read: 'active' and 'trialing' are the two statuses that mean the -- subscription is presently owed service (the pair operator_billing.go -- treats as live), but only active ones contribute money, so the caption -- needs them separately. SELECT COUNT(*) FILTER (WHERE status = 'active') AS active_count, COUNT(*) FILTER (WHERE status = 'trialing') AS trialing_count FROM core.subscriptions; -- name: SumMonthlyRecurringByCurrency :many -- The Monthly recurring headline, per currency — the caller displays the -- largest bucket and acknowledges the rest (cross-currency conversion is -- explicitly out of scope: issues.md). Monthly-normalized: month prices at -- face value, year at one-twelfth, each times item quantity. Active only — -- trialing subscriptions pay nothing today and counting their money would -- overstate. SELECT p.currency, SUM( CASE p.recurring_interval WHEN 'month' THEN p.unit_amount::bigint * si.quantity WHEN 'year' THEN (p.unit_amount::bigint * si.quantity) / 12 ELSE 0 END )::bigint AS monthly_cents FROM core.subscriptions s JOIN core.subscription_items si ON si.subscription_id = s.subscription_id JOIN core.prices p ON p.price_id = si.price_id WHERE s.status = 'active' AND p.recurring_interval IS NOT NULL GROUP BY p.currency ORDER BY monthly_cents DESC;