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.
140 lines
5.1 KiB
SQL
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);
|