-- Core business logic functions CREATE OR REPLACE FUNCTION get_default_rules() RETURNS JSONB AS $$ SELECT jsonb_build_object( 'points_win', 3, 'points_draw', 1, 'round_robin_format', 'double', 'transfer_policy', 'anytime', 'auto_qualify_count', 1, 'strict_availability', false, 'result_approval_required', true, 'cup_format', jsonb_build_object( 'legs', 'single', 'third_place_match', false, 'seeding', 'random' ) ); $$ LANGUAGE sql IMMUTABLE; -- Snapshot rules when activating competition CREATE OR REPLACE FUNCTION activate_competition(p_competition_id UUID) RETURNS VOID AS $$ DECLARE v_league_id UUID; v_latest_rules JSONB; v_version INT; BEGIN SELECT league_id INTO v_league_id FROM competitions WHERE id = p_competition_id; SELECT rules, version INTO v_latest_rules, v_version FROM league_rules WHERE league_id = v_league_id ORDER BY version DESC LIMIT 1; IF v_latest_rules IS NULL THEN v_latest_rules := get_default_rules(); v_version := 1; END IF; UPDATE competitions SET status = 'active', rules_snapshot = v_latest_rules, rule_set_version = v_version WHERE id = p_competition_id AND status = 'draft'; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Generate double round-robin fixtures CREATE OR REPLACE FUNCTION generate_league_fixtures(p_competition_id UUID) RETURNS INT AS $$ DECLARE team_ids UUID[]; n INT; rounds INT; r INT; i INT; j INT; fixed UUID; rotating UUID[]; match_count INT := 0; leg INT; BEGIN SELECT ARRAY_AGG(id ORDER BY name) INTO team_ids FROM teams WHERE competition_id = p_competition_id; n := COALESCE(array_length(team_ids, 1), 0); IF n < 2 THEN RAISE EXCEPTION 'Need at least 2 teams'; END IF; DELETE FROM matches WHERE competition_id = p_competition_id AND status = 'scheduled'; IF n % 2 = 1 THEN team_ids := team_ids || NULL::UUID; n := n + 1; END IF; rounds := n - 1; rotating := team_ids[2:n]; FOR leg IN 1..2 LOOP FOR r IN 1..rounds LOOP IF team_ids[1] IS NOT NULL AND rotating[1] IS NOT NULL THEN IF leg = 1 THEN INSERT INTO matches (competition_id, home_team_id, away_team_id, round, matchday, leg, status) VALUES (p_competition_id, team_ids[1], rotating[1], r, r + (leg-1)*rounds, leg, 'scheduled'); ELSE INSERT INTO matches (competition_id, home_team_id, away_team_id, round, matchday, leg, status) VALUES (p_competition_id, rotating[1], team_ids[1], r, r + (leg-1)*rounds, leg, 'scheduled'); END IF; match_count := match_count + 1; END IF; FOR i IN 1..(array_length(rotating, 1) / 2) LOOP j := array_length(rotating, 1) - i + 1; IF rotating[i] IS NOT NULL AND rotating[j] IS NOT NULL AND i <> j THEN IF leg = 1 THEN INSERT INTO matches (competition_id, home_team_id, away_team_id, round, matchday, leg, status) VALUES (p_competition_id, rotating[i], rotating[j], r, r + (leg-1)*rounds, leg, 'scheduled'); ELSE INSERT INTO matches (competition_id, home_team_id, away_team_id, round, matchday, leg, status) VALUES (p_competition_id, rotating[j], rotating[i], r, r + (leg-1)*rounds, leg, 'scheduled'); END IF; match_count := match_count + 1; END IF; END LOOP; fixed := rotating[array_length(rotating, 1)]; rotating := ARRAY[fixed] || rotating[1:array_length(rotating,1)-1]; END LOOP; END LOOP; RETURN match_count; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Generate cup bracket (single elimination, first round) CREATE OR REPLACE FUNCTION generate_cup_bracket(p_competition_id UUID) RETURNS INT AS $$ DECLARE team_ids UUID[]; n INT; i INT; match_count INT := 0; BEGIN SELECT ARRAY_AGG(id ORDER BY COALESCE(cup_seed, 999), name) INTO team_ids FROM teams WHERE competition_id = p_competition_id; n := COALESCE(array_length(team_ids, 1), 0); IF n < 2 THEN RAISE EXCEPTION 'Need at least 2 teams'; END IF; DELETE FROM matches WHERE competition_id = p_competition_id AND status = 'scheduled'; i := 1; WHILE i < n LOOP IF team_ids[i] IS NOT NULL AND team_ids[i+1] IS NOT NULL THEN INSERT INTO matches (competition_id, home_team_id, away_team_id, bracket_round, bracket_slot, status) VALUES (p_competition_id, team_ids[i], team_ids[i+1], 1, (i+1)/2, 'scheduled'); match_count := match_count + 1; END IF; i := i + 2; END LOOP; RETURN match_count; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Submit match result from team CREATE OR REPLACE FUNCTION submit_match_result( p_match_id UUID, p_team_id UUID, p_home_score INT, p_away_score INT ) RETURNS VOID AS $$ DECLARE v_competition_id UUID; v_sub_count INT; v_home_sub INT; v_away_sub INT; v_sub1_home INT; v_sub1_away INT; v_sub2_home INT; v_sub2_away INT; BEGIN SELECT competition_id INTO v_competition_id FROM matches WHERE id = p_match_id; INSERT INTO match_result_submissions (match_id, team_id, home_score, away_score, submitted_by) VALUES (p_match_id, p_team_id, p_home_score, p_away_score, auth.uid()) ON CONFLICT (match_id, team_id) DO UPDATE SET home_score = EXCLUDED.home_score, away_score = EXCLUDED.away_score, submitted_at = now(), submitted_by = auth.uid(); SELECT COUNT(*) INTO v_sub_count FROM match_result_submissions WHERE match_id = p_match_id; IF v_sub_count < 2 THEN UPDATE matches SET result_status = 'result_submitted', status = 'awaiting_results' WHERE id = p_match_id; RETURN; END IF; SELECT home_score, away_score INTO v_sub1_home, v_sub1_away FROM match_result_submissions WHERE match_id = p_match_id ORDER BY submitted_at LIMIT 1; SELECT home_score, away_score INTO v_sub2_home, v_sub2_away FROM match_result_submissions WHERE match_id = p_match_id ORDER BY submitted_at DESC LIMIT 1; IF v_sub1_home = v_sub2_home AND v_sub1_away = v_sub2_away THEN UPDATE matches SET result_status = 'pending_approval', status = 'pending_approval' WHERE id = p_match_id; ELSE UPDATE matches SET result_status = 'disputed', status = 'disputed' WHERE id = p_match_id; END IF; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- League manager approves result CREATE OR REPLACE FUNCTION approve_match_result(p_match_id UUID) RETURNS VOID AS $$ DECLARE v_sub RECORD; BEGIN IF NOT EXISTS ( SELECT 1 FROM matches m WHERE m.id = p_match_id AND is_competition_league_manager(m.competition_id) ) AND NOT EXISTS ( SELECT 1 FROM matches m JOIN competitions c ON c.id = m.competition_id JOIN leagues l ON l.id = c.league_id WHERE m.id = p_match_id AND l.created_by = auth.uid() ) THEN RAISE EXCEPTION 'Not authorized'; END IF; SELECT home_score, away_score INTO v_sub FROM match_result_submissions WHERE match_id = p_match_id LIMIT 1; UPDATE matches SET home_score = v_sub.home_score, away_score = v_sub.away_score, status = 'completed', result_status = 'completed', approved_by = auth.uid(), approved_at = now() WHERE id = p_match_id; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- League manager sets result directly CREATE OR REPLACE FUNCTION set_match_result_by_manager( p_match_id UUID, p_home_score INT, p_away_score INT, p_note TEXT DEFAULT NULL ) RETURNS VOID AS $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM matches m WHERE m.id = p_match_id AND is_competition_league_manager(m.competition_id) ) AND NOT EXISTS ( SELECT 1 FROM matches m JOIN competitions c ON c.id = m.competition_id JOIN leagues l ON l.id = c.league_id WHERE m.id = p_match_id AND l.created_by = auth.uid() ) THEN RAISE EXCEPTION 'Not authorized'; END IF; UPDATE matches SET home_score = p_home_score, away_score = p_away_score, status = 'completed', result_status = 'completed', approved_by = auth.uid(), approved_at = now(), resolution_note = p_note WHERE id = p_match_id; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Sign schedule CREATE OR REPLACE FUNCTION sign_match_schedule(p_match_id UUID, p_team_id UUID) RETURNS VOID AS $$ DECLARE v_sig_count INT; v_proposed TIMESTAMPTZ; BEGIN SELECT proposed_scheduled_at INTO v_proposed FROM matches WHERE id = p_match_id; INSERT INTO match_signatures (match_id, team_id, user_id, signature_type) VALUES (p_match_id, p_team_id, auth.uid(), 'schedule') ON CONFLICT (match_id, team_id, signature_type) DO NOTHING; SELECT COUNT(*) INTO v_sig_count FROM match_signatures WHERE match_id = p_match_id AND signature_type = 'schedule'; IF v_sig_count >= 2 AND v_proposed IS NOT NULL THEN UPDATE matches SET status = 'schedule_confirmed', scheduled_at = v_proposed, venue = (SELECT home_stadium_name FROM teams t JOIN matches m ON m.home_team_id = t.id WHERE m.id = p_match_id) WHERE id = p_match_id; END IF; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Propose schedule time CREATE OR REPLACE FUNCTION propose_match_schedule(p_match_id UUID, p_scheduled_at TIMESTAMPTZ) RETURNS VOID AS $$ BEGIN UPDATE matches SET proposed_scheduled_at = p_scheduled_at, status = 'schedule_pending' WHERE id = p_match_id; END; $$ LANGUAGE plpgsql SECURITY DEFINER;