teecup/055_order_of_merit.sql

118 lines
6.2 KiB
MySQL
Raw Permalink Normal View History

-- =====================================================================
-- TeeCup — Order of Merit: sesong-sammenlagt rangering på tvers av
-- flere individuelle turneringer (migrasjon 055, ADR-043 [OOM])
-- =====================================================================
-- Bygges i seks stadier (se plan-loggen 2026-08-04) -- denne migrasjonen
-- dekker skjemaet for ALLE stadier på én gang (billig å legge til
-- kolonner nå, dyrere å migrere skjema seks ganger), men selve
-- BACKEND-logikken bygges stadium for stadium, scratch-verifisert
-- mellom hvert.
--
-- order_of_merit: selve OOM-en. `kind` ('player'|'team') låses etter
-- opprettelse i app-laget, som GolfBox sin egen tilsvarende låsing.
-- `result_type` (poeng/stableford/brutto/netto/pengeliste) og
-- `aggregation_mode` (sum/snitt/eclectic) er de to uavhengige aksene
-- GolfBox sitt gode mønster besto av -- HVA telles vs. HVORDAN det
-- summeres. `count_best_n`/`min_results_required`/`birth_year_from/to`
-- er alle valgfrie grenser (NULL = ingen grense), lagt til fra
-- stadium 3, men kolonnene finnes fra start.
-- order_of_merit_link: hvilke individuelle turneringer (ADR-037) som
-- teller mot en gitt OOM, pluss per-lenke-konfig for resultattyper
-- som trenger det (poeng-tabell, pengepremie-tabell) -- disse to
-- JSONB-feltene er NULL med mindre `result_type` faktisk krever dem
-- (håndhevet i app-laget, ikke en CHECK, siden reglene er
-- resultattype-avhengige).
-- order_of_merit_team/_team_member: KUN for kind='team' -- et
-- sesong-lag satt opp DIREKTE i OOM-en (ikke hentet fra noen
-- turnering), som summerer/velger-beste-N-av medlemmenes ALLEREDE
-- beregnede individuelle OOM-resultater. Bevisst IKKE koblet til
-- `team`/`team_roster` (ADR-011s lagturneringer) -- disse er
-- strukturelt urelaterte konsepter (ett er en enkelt-turnering-
-- kamp-side, det andre er en sesong-lang gruppering av spillere).
--
-- Bevisst forenklet vs. GolfBox (ikke glemt, kan utvides senere uten
-- brudd): ingen gjenbrukbart "grense-mal"-system på tvers av flere OOM
-- (hver OOM har sine egne grenser direkte på raden); ingen "verst N"
-- eller kombinert beste+verst, kun "behold de N beste".
-- =====================================================================
\set ON_ERROR_STOP on
CREATE TABLE order_of_merit (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id uuid NOT NULL REFERENCES organization (id) ON DELETE CASCADE,
name text NOT NULL,
kind text NOT NULL CHECK (kind IN ('player', 'team')),
result_type text NOT NULL CHECK (result_type IN ('points', 'stableford', 'gross', 'net', 'money')),
aggregation_mode text NOT NULL DEFAULT 'sum' CHECK (aggregation_mode IN ('sum', 'average', 'eclectic')),
count_best_n smallint CHECK (count_best_n IS NULL OR count_best_n >= 1),
min_results_required smallint CHECK (min_results_required IS NULL OR min_results_required >= 1),
birth_year_from smallint,
birth_year_to smallint,
public_visible boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (organization_id, id),
-- Eclectic krever per-hull-granularitet -- poeng/pengeliste har ingen
-- "beste hull" å hente (samme begrensning GolfBox selv håndhever).
CHECK (aggregation_mode <> 'eclectic' OR result_type IN ('stableford', 'gross', 'net'))
);
CREATE INDEX ON order_of_merit (organization_id);
CREATE TABLE order_of_merit_link (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id uuid NOT NULL,
order_of_merit_id uuid NOT NULL REFERENCES order_of_merit (id) ON DELETE CASCADE,
tournament_id uuid NOT NULL,
-- Kun result_type='points': liste av heltall, indeks 0 = poeng for
-- 1. plass, indeks 1 = 2. plass, osv.
points_table jsonb,
-- Kun result_type='money': premiepott + utbetaling per plassering.
money_pool_total numeric(10, 2),
money_payout_table jsonb,
created_at timestamptz NOT NULL DEFAULT now(),
FOREIGN KEY (organization_id, tournament_id) REFERENCES tournament (organization_id, id) ON DELETE CASCADE,
UNIQUE (order_of_merit_id, tournament_id)
);
CREATE INDEX ON order_of_merit_link (organization_id, order_of_merit_id);
CREATE TABLE order_of_merit_team (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id uuid NOT NULL,
order_of_merit_id uuid NOT NULL REFERENCES order_of_merit (id) ON DELETE CASCADE,
name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (order_of_merit_id, name)
);
CREATE INDEX ON order_of_merit_team (organization_id, order_of_merit_id);
CREATE TABLE order_of_merit_team_member (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id uuid NOT NULL,
order_of_merit_team_id uuid NOT NULL REFERENCES order_of_merit_team (id) ON DELETE CASCADE,
player_id uuid NOT NULL REFERENCES player (id) ON DELETE CASCADE,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (order_of_merit_team_id, player_id)
);
CREATE INDEX ON order_of_merit_team_member (organization_id, order_of_merit_team_id);
-- ---------------------------------------------------------------------
-- Row-Level Security (samme org_isolation-policy-mønster som 040/053)
-- ---------------------------------------------------------------------
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY[
'order_of_merit', 'order_of_merit_link',
'order_of_merit_team', 'order_of_merit_team_member'
]
LOOP
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY;', t);
EXECUTE format('ALTER TABLE %I FORCE ROW LEVEL SECURITY;', t);
EXECUTE format($p$
CREATE POLICY org_isolation ON %I
USING (organization_id = current_setting('app.current_org', true)::uuid)
WITH CHECK (organization_id = current_setting('app.current_org', true)::uuid);
$p$, t);
END LOOP;
END $$;