Replace the entity slugs on organizations, workspaces, resource pools, and plan ladders with nullable `key` columns and add keys to products, prices, and entitlement sets. Rename `providers.slug` to `provider` and add partial unique indexes for system and org role names. Assign invoice numbers per billing account from a gapless transactional counter; Stripe's number moves to the invoice mapping as an external reference. Seeds, fixtures, and the operator lookup address rows by key, and the returning-login resync no longer blanks a display name when the IdP sends no `name` claim.
86 lines
3.6 KiB
SQL
86 lines
3.6 KiB
SQL
-- name: CreateBillingAccount :one
|
|
INSERT INTO core.accounts (org_id, name, status, metadata)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING *;
|
|
|
|
-- name: GetBillingAccountByID :one
|
|
SELECT * FROM core.accounts
|
|
WHERE billing_account_id = $1;
|
|
|
|
-- name: ListBillingAccountsByOrgID :many
|
|
SELECT * FROM core.accounts
|
|
WHERE org_id = $1
|
|
ORDER BY created_at ASC;
|
|
|
|
-- name: UpdateBillingAccountStatus :one
|
|
UPDATE core.accounts
|
|
SET status = $2
|
|
WHERE billing_account_id = $1
|
|
RETURNING *;
|
|
|
|
-- name: ListBillingAccountsPage :many
|
|
-- Operator billing-accounts view, paged and searched (operator-list-scale
|
|
-- UX-4; operator-billing-views D8). No status facet: core.accounts.status
|
|
-- carries no CHECK-enforced vocabulary and operator-list-scale's "Status
|
|
-- filters exist where a status vocabulary exists" requirement does not
|
|
-- name accounts, so this query offers search + pagination only.
|
|
--
|
|
-- sqlc.narg(q): NULL matches every row; set, a case-insensitive substring
|
|
-- match against the account's own name, or its organization via
|
|
-- sqlc.narg(org_ids) (design D3: "billing: organization name plus the
|
|
-- view's leading identifier"). Organization-name search resolves through
|
|
-- a pre-resolved ID array rather than a join to core.organizations: no
|
|
-- existing internal/billing query crosses that module boundary
|
|
-- (grep 'core\.' internal/billing/queries/ turns up none), so
|
|
-- operator_billing.go's matchingOrgIDs resolves matching org IDs in Go and
|
|
-- passes them here, mirroring the grants lane's product-name pattern.
|
|
-- count(*) OVER() carries the true total for the filtered set (design D2).
|
|
SELECT a.billing_account_id, a.org_id, a.name, a.status, a.metadata, a.created_at, a.updated_at,
|
|
count(*) OVER() AS total_count
|
|
FROM core.accounts a
|
|
WHERE (sqlc.narg(q)::text IS NULL
|
|
OR a.name ILIKE '%' || sqlc.narg(q)::text || '%'
|
|
OR a.org_id = ANY(sqlc.narg(org_ids)::uuid[]))
|
|
ORDER BY a.created_at DESC
|
|
LIMIT sqlc.arg(page_limit)::int OFFSET sqlc.arg(page_offset)::int;
|
|
|
|
-- name: GetLatestSubscriptionByBillingAccountID :one
|
|
-- Most recent subscription for a billing account (any status). Used by the
|
|
-- per-org composite's billing-summary section.
|
|
SELECT * FROM core.subscriptions
|
|
WHERE billing_account_id = $1
|
|
ORDER BY created_at DESC
|
|
LIMIT 1;
|
|
|
|
-- name: GetLatestInvoiceByBillingAccountID :one
|
|
-- Most recent invoice for a billing account (any status). Used by the
|
|
-- per-org composite's billing-summary section.
|
|
SELECT * FROM core.invoices
|
|
WHERE billing_account_id = $1
|
|
ORDER BY created_at DESC
|
|
LIMIT 1;
|
|
|
|
-- name: GetOutstandingBalanceByBillingAccountID :one
|
|
-- Sum of (amount_due - amount_paid) across invoices with status='open' — the
|
|
-- only actionable non-terminal invoice status per design/billing/model.md.
|
|
-- Returns 0 if no open invoices exist.
|
|
SELECT COALESCE(SUM(amount_due - amount_paid), 0)::BIGINT AS outstanding_balance
|
|
FROM core.invoices
|
|
WHERE billing_account_id = $1
|
|
AND status = 'open';
|
|
|
|
-- name: AssignNextInvoiceNumber :one
|
|
-- The transactional, gapless counter invoice-numbers D1 specifies: the
|
|
-- account's next_invoice_number is incremented and the prior value
|
|
-- returned in the same statement, so the caller runs this inside the same
|
|
-- transaction that inserts the invoice row (invoice.finalized projection).
|
|
-- Never a Postgres sequence — a sequence burns values on rollback, and this
|
|
-- counter must not: a projection that fails after this call rolls back
|
|
-- with the transaction, leaving the counter exactly where it was. Row
|
|
-- locking is implicit in the UPDATE and serializes only within one
|
|
-- account.
|
|
UPDATE core.accounts
|
|
SET next_invoice_number = next_invoice_number + 1, updated_at = NOW()
|
|
WHERE billing_account_id = $1
|
|
RETURNING next_invoice_number - 1 AS assigned;
|