-- ===================================================================== -- 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 $$;