Files
member-console/internal/organization/queries/organizations.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

121 lines
5.5 KiB
SQL

-- name: CreateOrganization :one
-- Identity is org_id, presentation is name, and organization names are
-- deliberately not unique. `key` is the optional declarative address
-- (entity-keys §1 to §4): a caller outside the database -- a seed, a
-- configuration loader, an API client -- supplies it to name this row
-- literally. UI-created organizations leave it NULL, and a personal
-- organization never gets one: a key is never derived from a person's login
-- or display name (§4).
INSERT INTO core.organizations (name, org_type, owner_person_id, key)
VALUES ($1, $2, $3, sqlc.narg(key))
RETURNING *;
-- name: GetOrganizationByKey :one
-- Root-scoped key resolver (entity-keys §5): the query that makes `key` a
-- whole feature rather than a seed-only column. uq_organizations_key
-- guarantees at most one row matches; a NULL key never matches because the
-- parameter is compared with `=`.
SELECT * FROM core.organizations
WHERE key = $1;
-- name: GetOrganizationByID :one
SELECT * FROM core.organizations
WHERE org_id = $1;
-- name: GetOrganizationsByOwner :many
SELECT * FROM core.organizations
WHERE owner_person_id = $1
ORDER BY name;
-- name: ListOrganizations :many
SELECT * FROM core.organizations
ORDER BY name;
-- name: ListOrganizationsPage :many
-- Organizations list operator surface (operator-list-scale UX-4): the
-- paged, searched, org-type-filtered successor to ListOrganizationsWithOwner
-- -- each row still carries its owner's display name so no organization can
-- read as unowned (ux-honest-surfaces UX-16). owner_person_id is NOT NULL
-- (every organization has an owner person, the System tenant included --
-- 00001_init.sql), so the LEFT JOIN is defensive rather than expected to
-- ever drop the owner's name.
--
-- sqlc.narg(q): NULL matches every row; set, a case-insensitive substring
-- match against the organization's name, or its owner's display
-- name or primary email (operator-list-scale: "carries a scoped
-- server-side search" -- "organizations: name and owner name or
-- email").
-- sqlc.narg(org_type): NULL matches every org type; set, an exact facet
-- match (operator-list-scale: "Status filters exist ... organizations: org
-- type").
-- count(*) OVER() carries the true total for the filtered set alongside
-- the page's rows (design D2), so the stated total never reflects only the
-- page.
SELECT o.org_id, o.name, o.org_type, o.owner_person_id, o.status, o.created_at, o.updated_at,
p.display_name AS owner_display_name,
count(*) OVER() AS total_count
FROM core.organizations o
LEFT JOIN core.persons p ON p.person_id = o.owner_person_id
WHERE (sqlc.narg(q)::text IS NULL
OR o.name ILIKE '%' || sqlc.narg(q)::text || '%'
OR p.display_name ILIKE '%' || sqlc.narg(q)::text || '%'
OR p.primary_email ILIKE '%' || sqlc.narg(q)::text || '%')
AND (sqlc.narg(org_type)::text IS NULL OR o.org_type = sqlc.narg(org_type)::text)
ORDER BY o.name
LIMIT sqlc.arg(page_limit)::int OFFSET sqlc.arg(page_offset)::int;
-- name: SearchOrganizationsByName :many
-- Landing lookup's organization branch (entity-keys). Runs only
-- when no person matches the lookup term; a dedicated query rather than
-- ListOrganizationsPage because that query's owner-email term would blur
-- the person/organization split the lookup depends on. Active
-- organizations only, name substring match, bounded by LIMIT so a short
-- term cannot return the whole table.
SELECT o.org_id, o.name, o.org_type,
p.display_name AS owner_display_name
FROM core.organizations o
LEFT JOIN core.persons p ON p.person_id = o.owner_person_id
WHERE o.status = 'active'
AND o.name ILIKE '%' || sqlc.arg(name)::text || '%'
ORDER BY o.name
LIMIT sqlc.arg(row_limit)::int;
-- name: ListOrganizationsByType :many
SELECT * FROM core.organizations
WHERE org_type = $1
ORDER BY name;
-- name: CountTeamOrganizations :one
-- Operator overview headline. Personal orgs exist one-per-member by
-- structural convention (design/organization/architecture.md §2), so an
-- organizations count that includes them tracks the people count and reads
-- as an error. Negative-space predicate instead of org_type = 'team':
-- everything active that is neither the auto-created personal type nor a
-- reserved type counts, so team, enterprise, and deployment-defined types
-- need no code change here and the reserved system org never appears.
SELECT COUNT(*) FROM core.organizations o
JOIN core.org_types t ON t.org_type = o.org_type
WHERE o.status = 'active' AND o.org_type <> 'personal' AND NOT t.is_reserved;
-- name: EnsureSystemOrganization :exec
-- Idempotently insert the singleton System organization. The partial unique
-- index uq_organizations_one_system enforces "exactly one System tenant", and
-- the conflict target repeats the index predicate so Postgres can infer it.
-- Read back with GetSystemOrganization.
--
-- `system` is one of the two keys the system writes for itself (entity-keys
-- §4): a fixed constant for a row the console creates by design, not a value
-- derived from anybody's data. It is written here rather than by the Go
-- caller so the only path that creates the System tenant is also the only
-- path that names it.
INSERT INTO core.organizations (name, org_type, owner_person_id, key)
VALUES ($1, $2, $3, 'system')
ON CONFLICT (org_type) WHERE org_type = 'system' DO NOTHING;
-- name: GetSystemOrganization :one
-- The singleton System organization, resolved by its natural key. Paired
-- with EnsureSystemOrganization; uq_organizations_one_system guarantees at
-- most one row matches.
SELECT * FROM core.organizations
WHERE org_type = 'system';