Files
member-console/internal/billing/queries/plan_ladders.sql
T
cgalo5758 dd3962990b Adopt entity keys and add invoice numbers
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.
2026-08-29 20:12:04 -05:00

140 lines
5.1 KiB
SQL

-- name: CreatePlanLadder :one
-- `key` is the optional declarative address (entity-keys §3), root-scoped.
-- The operator create form asks for a name only, so a UI-created ladder
-- leaves it NULL; a seed or a configuration loader supplies it.
INSERT INTO core.plan_ladders (name, description, is_active, key)
VALUES ($1, $2, $3, sqlc.narg(key))
RETURNING *;
-- name: GetPlanLadderByKey :one
-- Root key resolver (entity-keys §5). uq_plan_ladders_key guarantees at most
-- one row matches.
SELECT * FROM core.plan_ladders
WHERE key = $1;
-- name: ListPlanLadders :many
SELECT * FROM core.plan_ladders
ORDER BY sort_order ASC, name ASC;
-- name: GetPlanLadderByID :one
SELECT * FROM core.plan_ladders
WHERE plan_ladder_id = $1;
-- name: UpdatePlanLadder :one
UPDATE core.plan_ladders
SET name = $2, description = $3, is_active = $4
WHERE plan_ladder_id = $1
RETURNING *;
-- name: SetPlanLadderSortOrder :exec
-- Sets a single ladder's display order. Used by the topology reorder flow,
-- which renumbers ladders to a contiguous sequence; sort_order is deliberately
-- NOT writable via UpdatePlanLadder so editing a ladder never reorders it.
UPDATE core.plan_ladders
SET sort_order = $2
WHERE plan_ladder_id = $1;
-- name: DeletePlanLadder :exec
DELETE FROM core.plan_ladders
WHERE plan_ladder_id = $1;
-- name: CreatePlanLadderTier :one
-- Appends the tier at the end of the ladder: rank self-assigns to MAX+1 in the
-- INSERT itself (no read-then-write race). Reorder keeps ranks contiguous
-- 0..N-1, so MAX+1 == COUNT; operators position tiers by drag-and-drop.
INSERT INTO core.plan_ladder_tiers (plan_ladder_id, product_id, rank)
VALUES ($1, $2, (
SELECT COALESCE(MAX(rank) + 1, 0)
FROM core.plan_ladder_tiers
WHERE plan_ladder_id = $1
))
RETURNING *;
-- name: EnsurePlanLadderTier :exec
-- Idempotent tier membership for a seed (entity-keys §6 as amended by §9 A1:
-- a seed ensures a row exists and never overwrites one). A tier has no key of
-- its own: it is a relationship, and its identity is the pair (§2 class E),
-- which is also its primary key. Rank self-assigns to MAX+1 exactly as
-- CreatePlanLadderTier does, so a caller inserting tiers in ascending order
-- gets 0..N-1; a second run conflicts on the pair and does nothing, leaving
-- an operator's own reordering alone.
INSERT INTO core.plan_ladder_tiers (plan_ladder_id, product_id, rank)
VALUES ($1, $2, (
SELECT COALESCE(MAX(rank) + 1, 0)
FROM core.plan_ladder_tiers
WHERE plan_ladder_id = $1
))
ON CONFLICT (plan_ladder_id, product_id) DO NOTHING;
-- name: ListTiersByLadder :many
SELECT * FROM core.plan_ladder_tiers
WHERE plan_ladder_id = $1
ORDER BY rank ASC;
-- name: ListTiersByLadderWithProducts :many
SELECT t.plan_ladder_id, t.product_id, t.rank, t.created_at, t.updated_at,
p.name AS product_name, p.display_category, p.lifecycle_status
FROM core.plan_ladder_tiers t
JOIN core.products p ON p.product_id = t.product_id
WHERE t.plan_ladder_id = $1
ORDER BY t.rank ASC;
-- name: ListLaddersByProduct :many
SELECT t.*, l.name AS ladder_name
FROM core.plan_ladder_tiers t
JOIN core.plan_ladders l ON l.plan_ladder_id = t.plan_ladder_id
WHERE t.product_id = $1
ORDER BY l.name ASC;
-- name: GetTier :one
SELECT * FROM core.plan_ladder_tiers
WHERE plan_ladder_id = $1 AND product_id = $2;
-- name: GetTierByLadderRank :one
SELECT * FROM core.plan_ladder_tiers
WHERE plan_ladder_id = $1 AND rank = $2;
-- name: ListPlanLaddersWithRankZeroProduct :many
-- LEFT JOINs on purpose: a ladder whose rank-0 tier was deleted must still
-- appear (product columns NULL) so surfaces that reference it — notably an
-- org type's configured default — can show it as broken instead of silently
-- dropping it from a dropdown and NULLing the stored value on save.
SELECT l.plan_ladder_id, l.name AS ladder_name, l.is_active,
t.product_id, p.name AS rank_zero_product_name
FROM core.plan_ladders l
LEFT JOIN core.plan_ladder_tiers t
ON t.plan_ladder_id = l.plan_ladder_id AND t.rank = 0
LEFT JOIN core.products p ON p.product_id = t.product_id
ORDER BY l.name ASC;
-- name: UpdateTierRank :one
UPDATE core.plan_ladder_tiers
SET rank = $3
WHERE plan_ladder_id = $1 AND product_id = $2
RETURNING *;
-- name: DeleteTier :exec
DELETE FROM core.plan_ladder_tiers
WHERE plan_ladder_id = $1 AND product_id = $2;
-- name: ListSharedTierProducts :many
-- Products that are a tier in more than one ladder (the many-to-many made
-- legible for the operator topology overview). Returns one row per
-- (shared product, ladder) membership, ordered for stable grouping in Go.
SELECT t.product_id, p.name AS product_name,
t.plan_ladder_id, l.name AS ladder_name, t.rank
FROM core.plan_ladder_tiers t
JOIN core.products p ON p.product_id = t.product_id
JOIN core.plan_ladders l ON l.plan_ladder_id = t.plan_ladder_id
WHERE t.product_id IN (
SELECT product_id FROM core.plan_ladder_tiers
GROUP BY product_id
HAVING COUNT(*) > 1
)
ORDER BY p.name ASC, l.sort_order ASC, l.name ASC;
-- name: AnyRankZeroTier :one
-- Setup-checklist existence probe: a ladder delivers only through its rank-0
-- tier, so "ladder exists" alone is not the step.
SELECT EXISTS(SELECT 1 FROM core.plan_ladder_tiers WHERE rank = 0);