from db import get_connection from repositories.match_view_repository import get_match_lineups_grouped from repositories.match_coach_repository import get_match_coaches_grouped def replace_match_lineups(match_id: int, rows: list[dict]) -> None: delete_query = "DELETE FROM match_lineups WHERE match_id = %s;" insert_query = """ INSERT INTO match_lineups ( match_id, team_id, player_id, player_name, number, position, is_captain, lineup_type, source, created_at, updated_at ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW()); """ conn = get_connection() try: with conn.cursor() as cur: cur.execute(delete_query, (match_id,)) for row in rows: cur.execute( insert_query, ( row["match_id"], row["team_id"], row.get("player_id"), row["player_name"], row.get("number"), row.get("position"), row.get("is_captain"), row["lineup_type"], row.get("source", "parser"), ), ) conn.commit() except Exception: conn.rollback() raise finally: conn.close() def _player_to_editor_dict(p: dict) -> dict: return { "player_id": p.get("player_id"), "player_name": p.get("player_name") or f"{p.get('last_name', '')} {p.get('first_name', '')}".strip(), "last_name": p.get("last_name", "") or p.get("player_name", "") or "", "first_name": p.get("first_name", "") or "", "number": str(p.get("number", "") or ""), "position": p.get("position", "") or "", "is_captain": bool(p.get("is_captain")), } def _coach_to_editor_dict(c: dict) -> dict: return { "coach_id": c.get("coach_id"), "coach_name": c.get("coach_name", "") or "", "role": c.get("role", "") or "", } def get_match_lineup_for_editor( match_id: int, home_team_id: int, away_team_id: int, ) -> dict: conn = get_connection() try: with conn.cursor() as cur: cur.execute( """ SELECT mlp.side, mlp.role, mlp.sort_order, p.id AS player_id, COALESCE(p.full_name, TRIM(COALESCE(p.last_name, '') || ' ' || COALESCE(p.first_name, ''))) AS player_name, COALESCE(p.last_name, '') AS last_name, COALESCE(p.first_name, '') AS first_name, COALESCE(mlp.number::text, '') AS number, COALESCE(p.position, '') AS position, COALESCE(mlp.is_captain, FALSE) AS is_captain FROM match_lineup_players mlp JOIN players p ON p.id = mlp.player_id WHERE mlp.match_id = %s ORDER BY mlp.side, mlp.role, mlp.sort_order, mlp.id """, (match_id,), ) player_rows = cur.fetchall() cur.execute( """ SELECT mc.side, mc.sort_order, c.id AS coach_id, COALESCE(c.player, '') AS coach_name, COALESCE(mc.role, c.amplua, '') AS role FROM match_coaches mc JOIN coaches c ON c.id = mc.coach_id WHERE mc.match_id = %s ORDER BY mc.side, mc.sort_order, mc.id """, (match_id,), ) coach_rows = cur.fetchall() finally: conn.close() result = { "home_starting": [], "home_bench": [], "away_starting": [], "away_bench": [], "home_coaches": [], "away_coaches": [], } # Если редактор уже сохранял состав — берём игроков из новой таблицы if player_rows: for row in player_rows: player = { "player_id": row[3], "player_name": row[4] or "", "last_name": row[5] or "", "first_name": row[6] or "", "number": row[7] or "", "position": row[8] or "", "is_captain": bool(row[9]), } side = row[0] role = row[1] if side == "home" and role == "starting": result["home_starting"].append(player) elif side == "home" and role == "bench": result["home_bench"].append(player) elif side == "away" and role == "starting": result["away_starting"].append(player) elif side == "away" and role == "bench": result["away_bench"].append(player) else: # Иначе берём загруженный с сайта состав из старой таблицы lineups = get_match_lineups_grouped( match_id=match_id, home_team_id=home_team_id, away_team_id=away_team_id, ) result["home_starting"] = [ _player_to_editor_dict(p) for p in lineups.get("home_starting", []) ] result["home_bench"] = [ _player_to_editor_dict(p) for p in lineups.get("home_bench", []) ] result["away_starting"] = [ _player_to_editor_dict(p) for p in lineups.get("away_starting", []) ] result["away_bench"] = [ _player_to_editor_dict(p) for p in lineups.get("away_bench", []) ] # Тренеров берём из match_coaches, если они уже есть if coach_rows: for row in coach_rows: coach = { "coach_id": row[2], "coach_name": row[3] or "", "role": row[4] or "", } side = row[0] if side == "home": result["home_coaches"].append(coach) elif side == "away": result["away_coaches"].append(coach) else: coaches = get_match_coaches_grouped( match_id=match_id, home_team_id=home_team_id, away_team_id=away_team_id, ) result["home_coaches"] = [ _coach_to_editor_dict(c) for c in coaches.get("home", []) ] result["away_coaches"] = [ _coach_to_editor_dict(c) for c in coaches.get("away", []) ] return result def get_match_lineup_for_vmix( match_id: int, home_team_id: int, away_team_id: int, ) -> dict: conn = get_connection() try: with conn.cursor() as cur: # Сначала пробуем взять ручную версию из редактора cur.execute( """ SELECT mlp.side, mlp.role, mlp.sort_order, p.id AS player_id, COALESCE( p.full_name, TRIM(COALESCE(p.last_name, '') || ' ' || COALESCE(p.first_name, '')) ) AS player_name, COALESCE(p.last_name, '') AS last_name, COALESCE(p.first_name, '') AS first_name, COALESCE(mlp.number::text, '') AS number, COALESCE(p.position, '') AS position, COALESCE(mlp.is_captain, FALSE) AS is_captain, COALESCE(p.position, '') AS pos FROM match_lineup_players mlp JOIN players p ON p.id = mlp.player_id WHERE mlp.match_id = %s ORDER BY CASE WHEN LOWER(COALESCE(p.position, '')) IN ('вр', 'вр.', 'gk', 'goalkeeper', 'вратарь') THEN 0 ELSE 1 END, CASE WHEN COALESCE(p.number::text, '') ~ '^[0-9]+$' THEN p.number::integer ELSE 999 END, COALESCE(p.last_name, ''), COALESCE(p.first_name, ''), p.id """, (match_id,), ) player_rows = cur.fetchall() cur.execute( """ SELECT mc.side, mc.sort_order, c.id AS coach_id, COALESCE(c.player, '') AS coach_name, COALESCE(mc.role, c.amplua, '') AS role FROM match_coaches mc JOIN coaches c ON c.id = mc.coach_id WHERE mc.match_id = %s ORDER BY mc.side, mc.sort_order, mc.id """, (match_id,), ) coach_rows = cur.fetchall() finally: conn.close() result = { "home_starting": [], "home_bench": [], "away_starting": [], "away_bench": [], "home_coaches": [], "away_coaches": [], } # 1. Если есть ручной состав — берём его if player_rows: for row in player_rows: player = { "player_id": row[3], "player_name": row[4] or "", "last_name": row[5] or "", "first_name": row[6] or "", "number": row[7] or "", "position": row[8] or "", "is_captain": bool(row[9]), "pos": row[10] or "", } side = row[0] role = row[1] if side == "home" and role == "starting": result["home_starting"].append(player) elif side == "home" and role == "bench": result["home_bench"].append(player) elif side == "away" and role == "starting": result["away_starting"].append(player) elif side == "away" and role == "bench": result["away_bench"].append(player) else: # 2. Иначе — fallback на сайт lineups = get_match_lineups_grouped( match_id=match_id, home_team_id=home_team_id, away_team_id=away_team_id, ) result["home_starting"] = lineups.get("home_starting", []) result["home_bench"] = lineups.get("home_bench", []) result["away_starting"] = lineups.get("away_starting", []) result["away_bench"] = lineups.get("away_bench", []) if coach_rows: for row in coach_rows: coach = { "coach_id": row[2], "coach_name": row[3] or "", "role": row[4] or "", } if row[0] == "home": result["home_coaches"].append(coach) elif row[0] == "away": result["away_coaches"].append(coach) else: coaches = get_match_coaches_grouped( match_id=match_id, home_team_id=home_team_id, away_team_id=away_team_id, ) result["home_coaches"] = coaches.get("home", []) result["away_coaches"] = coaches.get("away", []) return result def save_match_lineup_for_editor( match_id: int, home_team_id: int, away_team_id: int, home_starting: list[dict], home_bench: list[dict], away_starting: list[dict], away_bench: list[dict], home_coaches: list[dict], away_coaches: list[dict], ) -> None: def is_goalkeeper(position: str) -> bool: if not position: return False return str(position).strip().lower() in { "вр", "вр.", "вратарь", "gk", "goalkeeper" } def safe_number(value) -> int: try: return int(str(value).strip()) except Exception: return 9999 def lineup_sort_key(player: dict): return ( 0 if is_goalkeeper(player.get("position", "")) else 1, safe_number(player.get("number")), (player.get("last_name") or player.get("player_name") or "").lower(), (player.get("first_name") or "").lower(), player.get("player_id") or 0, ) # Пересортировка перед сохранением home_starting = sorted(home_starting, key=lineup_sort_key) home_bench = sorted(home_bench, key=lineup_sort_key) away_starting = sorted(away_starting, key=lineup_sort_key) away_bench = sorted(away_bench, key=lineup_sort_key) conn = get_connection() try: with conn.cursor() as cur: cur.execute( "DELETE FROM match_lineup_players WHERE match_id = %s", (match_id,), ) cur.execute( "DELETE FROM match_coaches WHERE match_id = %s", (match_id,), ) def insert_players(side: str, role: str, players: list[dict]) -> None: for sort_order, p in enumerate(players, start=1): cur.execute( """ INSERT INTO match_lineup_players ( match_id, side, role, sort_order, player_id, number, position, is_captain ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) """, ( match_id, side, role, sort_order, p.get("player_id"), p.get("number", "") or None, p.get("position", "") or None, bool(p.get("is_captain")), ), ) def insert_coaches(side: str, coaches: list[dict]) -> None: for sort_order, c in enumerate(coaches, start=1): cur.execute( """ INSERT INTO match_coaches ( match_id, side, sort_order, coach_id, role ) VALUES (%s, %s, %s, %s, %s) """, ( match_id, side, sort_order, c.get("coach_id"), c.get("role", "") or None, ), ) insert_players("home", "starting", home_starting) insert_players("home", "bench", home_bench) insert_players("away", "starting", away_starting) insert_players("away", "bench", away_bench) insert_coaches("home", home_coaches) insert_coaches("away", away_coaches) conn.commit() except Exception: conn.rollback() raise finally: conn.close()