-- ===================================================================== -- TeeCup — individuelle/flerrunde-turneringer: grunnstruktur (migrasjon -- 040, ADR-037) -- ===================================================================== -- Realiserer ADR-037 sine fire beslutninger. Rent additivt — ingen -- eksisterende tabell/rad endres i betydning, kun `tournament` som får -- to nye, nullable-med-trygg-default kolonner. -- -- Beslutning A: NY, parallell org-scopet tabellstruktur — IKKE en -- utvidelse av de frittstående rundetabellene (round/round_participant/ -- round_hole, ADR-033). Disse nye tabellene er RLS-beskyttet på -- organization_id, som alt annet turnering-relatert. -- Beslutning B: SAMME `tournament`-tabell, ny `format_type`-diskriminator -- ('team' | 'individual', default 'team' — alle eksisterende rader -- forblir 'team' uendret). `team`/`team_roster` blir ganske enkelt -- ubrukte for format_type='individual', håndhevet i app-laget (samme -- mønster som ADR-011s to-lags-grense), ikke en tabellomfattende CHECK. -- Beslutning C: Flerrunde via ny `tournament_round`-tabell (samme rolle -- som `session` har for lagturneringer), pekende til EKSISTERENDE -- org-scopet `course` (ingen snapshot — se ADR-037s egen begrunnelse: -- snapshot-behovet i ADR-033 kom av at frittstående runder leser en -- ekstern, ikke-org-kontrollert kilde, det gjelder ikke her). Ny -- `tournament_participant` (samme rolle som `team_roster`, fryser -- handicap_index_snapshot ved uttak — ADR-007-prinsippet). -- Beslutning D: Rå brutto slag lagres (`tournament_round_hole`, -- individuell ball — samme grunnform som hole_score for singles/ -- fourball), OG et ferdig utregnet resultat caches per runde per -- deltaker (`tournament_round_score` — samme mønster som -- match.status_text/points_side_a/b). Sammenlagt resultat over flere -- runder summeres VED LESING (samme prinsipp som -- tournament_standings-viewet for lagturneringer), ikke en egen -- tredje cache-tabell. -- -- BEVISST UTENFOR DENNE MIGRASJONEN (se ADR-037 "hva denne ADR-en IKKE -- avgjør ennå"): de fem konkrete formatenes egne poengregler -- (Københavner/High-low-high/Robbins/Try all/Flaggturnering) og Order of -- Merit (sesong-sammenlagt på tvers av FLERE turneringer). Dette -- skjemaet dekker kun rene, per-spiller-uavhengige scoringsmetoder -- (bruttoslagspill/nettoslagspill/Stableford) — formater som krever -- paring PÅ TVERS av spillere per hull (Københavner m.fl.) trenger sin -- egen, senere design-runde oppå denne grunnstrukturen. -- ===================================================================== \set ON_ERROR_STOP on -- --------------------------------------------------------------------- -- 1. tournament: type-diskriminator + scoringsmetode (Beslutning B) -- --------------------------------------------------------------------- ALTER TABLE tournament ADD COLUMN format_type text NOT NULL DEFAULT 'team' CHECK (format_type IN ('team', 'individual')); -- Hvilken motorfunksjon (handicap_engine.py) som regner cachet poeng fra -- de rå slagene i tournament_round_hole. Nullable -- en individuell -- turnering kan opprettes som 'draft' før scoringsmetoden er bestemt; -- app-laget krever den satt før en runde kan ta imot score (samme -- "krev X før Y virker, håndhevet i app-laget"-mønster som f.eks. hull- -- data for egendefinerte baner andre steder i skjemaet). ALTER TABLE tournament ADD COLUMN scoring_method text CHECK (scoring_method IS NULL OR scoring_method IN ('stroke_gross', 'stroke_net', 'stableford')); -- --------------------------------------------------------------------- -- 2. tournament_round: én runde/dag i en individuell turnering -- (samme rolle som `session` har for lagturneringer) -- --------------------------------------------------------------------- CREATE TABLE tournament_round ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), organization_id uuid NOT NULL, tournament_id uuid NOT NULL, sequence smallint NOT NULL, -- rekkefølge i turneringen (1,2,3...) name text, -- f.eks. "Runde 1 — fredag" hole_config hole_scope NOT NULL DEFAULT 'full_18', course_id uuid NOT NULL, -- org-ens EGEN banedata, ingen snapshot scheduled_at timestamptz, tee_interval_minutes smallint CHECK (tee_interval_minutes IS NULL OR tee_interval_minutes > 0), start_hole smallint NOT NULL DEFAULT 1 CHECK (start_hole BETWEEN 1 AND 18), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), FOREIGN KEY (organization_id, tournament_id) REFERENCES tournament(organization_id, id) ON DELETE CASCADE, FOREIGN KEY (organization_id, course_id) REFERENCES course(organization_id, id) ON DELETE RESTRICT, UNIQUE (organization_id, id), UNIQUE (tournament_id, sequence) ); -- --------------------------------------------------------------------- -- 3. tournament_participant: spiller-pool for EN individuell turnering -- (samme rolle som `team_roster`, men uten noe lag) -- --------------------------------------------------------------------- CREATE TABLE tournament_participant ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), organization_id uuid NOT NULL, tournament_id uuid NOT NULL, player_id uuid NOT NULL, handicap_index_snapshot numeric(4,1), -- fryst ved uttak, ADR-007-prinsippet created_at timestamptz NOT NULL DEFAULT now(), FOREIGN KEY (organization_id, tournament_id) REFERENCES tournament(organization_id, id) ON DELETE CASCADE, FOREIGN KEY (organization_id, player_id) REFERENCES player(organization_id, id) ON DELETE RESTRICT, UNIQUE (organization_id, id), UNIQUE (tournament_id, player_id) -- én spiller én gang i feltet ); -- --------------------------------------------------------------------- -- 4. tournament_round_participant: hvilken tee + hvilket beregnet -- handicap en deltaker har for ÉN GITT runde (banen/tee kan variere -- runde til runde selv om spilleren er den samme -- samme rolle som -- match_participant sin tee_id/course_handicap/playing_handicap) -- --------------------------------------------------------------------- CREATE TABLE tournament_round_participant ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), organization_id uuid NOT NULL, tournament_round_id uuid NOT NULL, tournament_participant_id uuid NOT NULL, tee_id uuid NOT NULL, -- Cachede handicap-felt (kilde: motoren, ut fra indeks-snapshot + tee-rating). course_handicap smallint, playing_handicap smallint, -- kun relevant for scoring_method='stroke_net'/'stableford' created_at timestamptz NOT NULL DEFAULT now(), FOREIGN KEY (organization_id, tournament_round_id) REFERENCES tournament_round(organization_id, id) ON DELETE CASCADE, FOREIGN KEY (organization_id, tournament_participant_id) REFERENCES tournament_participant(organization_id, id) ON DELETE RESTRICT, FOREIGN KEY (organization_id, tee_id) REFERENCES tee(organization_id, id) ON DELETE RESTRICT, UNIQUE (organization_id, id), UNIQUE (tournament_round_id, tournament_participant_id) ); -- --------------------------------------------------------------------- -- 5. tournament_round_hole: rå brutto slag per hull (kilde-sannhet, -- samme prinsipp som hole_score/round_hole -- netto/Stableford er -- ALLTID avledet, aldri lagret som kilde) -- --------------------------------------------------------------------- CREATE TABLE tournament_round_hole ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), organization_id uuid NOT NULL, tournament_round_participant_id uuid NOT NULL, hole_number smallint NOT NULL CHECK (hole_number BETWEEN 1 AND 18), gross_strokes smallint NOT NULL CHECK (gross_strokes BETWEEN 1 AND 20), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), FOREIGN KEY (organization_id, tournament_round_participant_id) REFERENCES tournament_round_participant(organization_id, id) ON DELETE CASCADE, UNIQUE (tournament_round_participant_id, hole_number) ); -- --------------------------------------------------------------------- -- 6. tournament_round_score: FERDIG UTREGNET resultat, cachet per runde -- per deltaker (samme mønster som match.status_text/points_side_a/b -- -- motoren regner på nytt ved hver hull-innsending, SQL summerer -- bare de cachede tallene ved lesing). Alle tre kolonner fylles -- uansett scoring_method (billig å ha alle tre klare -- lar -- leaderboardet bytte visning uten omregning). -- --------------------------------------------------------------------- CREATE TABLE tournament_round_score ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), organization_id uuid NOT NULL, tournament_round_id uuid NOT NULL, tournament_participant_id uuid NOT NULL, holes_played smallint NOT NULL DEFAULT 0, gross_total smallint, net_total smallint, stableford_points smallint, updated_at timestamptz NOT NULL DEFAULT now(), FOREIGN KEY (organization_id, tournament_round_id) REFERENCES tournament_round(organization_id, id) ON DELETE CASCADE, FOREIGN KEY (organization_id, tournament_participant_id) REFERENCES tournament_participant(organization_id, id) ON DELETE CASCADE, UNIQUE (tournament_round_id, tournament_participant_id) ); -- --------------------------------------------------------------------- -- 7. Indekser (RLS-predikat og FK-oppslag, samme konvensjon som 001) -- --------------------------------------------------------------------- CREATE INDEX ON tournament_round (organization_id, tournament_id); CREATE INDEX ON tournament_participant (organization_id, tournament_id); CREATE INDEX ON tournament_round_participant (organization_id, tournament_round_id); CREATE INDEX ON tournament_round_participant (organization_id, tournament_participant_id); CREATE INDEX ON tournament_round_hole (organization_id, tournament_round_participant_id); CREATE INDEX ON tournament_round_score (organization_id, tournament_round_id); CREATE INDEX ON tournament_round_score (organization_id, tournament_participant_id); -- --------------------------------------------------------------------- -- 8. Row-Level Security (samme org_isolation-policy-mønster som 001) -- --------------------------------------------------------------------- DO $$ DECLARE t text; BEGIN FOREACH t IN ARRAY ARRAY[ 'tournament_round', 'tournament_participant', 'tournament_round_participant', 'tournament_round_hole', 'tournament_round_score' ] 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 $$;