все остальные апдейты на Кубок России

This commit is contained in:
2026-07-02 17:00:15 +03:00
parent 03a68ee8ca
commit e7b215af5e
11 changed files with 1644 additions and 180 deletions

View File

@@ -0,0 +1,295 @@
from db import get_connection
VALID_SIDES = {"home", "away"}
VALID_RESULTS = {"scored", "missed"}
def ensure_match_penalty_tables() -> None:
"""Создает таблицы для серии пенальти."""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
CREATE TABLE IF NOT EXISTS match_penalty_settings (
match_id INTEGER PRIMARY KEY REFERENCES matches(id) ON DELETE CASCADE,
max_rounds INTEGER NOT NULL DEFAULT 5,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
"""
)
cur.execute(
"""
CREATE TABLE IF NOT EXISTS match_penalties (
id SERIAL PRIMARY KEY,
match_id INTEGER NOT NULL REFERENCES matches(id) ON DELETE CASCADE,
side VARCHAR(10) NOT NULL CHECK (side IN ('home', 'away')),
shot_number INTEGER NOT NULL CHECK (shot_number > 0),
result VARCHAR(20) NOT NULL CHECK (result IN ('scored', 'missed')),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE (match_id, side, shot_number)
);
"""
)
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def _normalize_side(side: str) -> str:
side = str(side or "").strip().lower()
if side not in VALID_SIDES:
raise ValueError("Некорректная сторона пенальти")
return side
def _normalize_result(result: str) -> str:
result = str(result or "").strip().lower()
if result not in VALID_RESULTS:
raise ValueError("Некорректный результат пенальти")
return result
def _ensure_penalty_rounds(cur, match_id: int, max_rounds: int) -> None:
safe_rounds = max(5, int(max_rounds or 5))
cur.execute(
"""
INSERT INTO match_penalty_settings (match_id, max_rounds, created_at, updated_at)
VALUES (%s, %s, NOW(), NOW())
ON CONFLICT (match_id)
DO UPDATE SET
max_rounds = GREATEST(match_penalty_settings.max_rounds, EXCLUDED.max_rounds),
updated_at = NOW();
""",
(match_id, safe_rounds),
)
def get_penalty_state(match_id: int, home_team_name: str = "", away_team_name: str = "") -> dict:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
SELECT COALESCE(max_rounds, 5)
FROM match_penalty_settings
WHERE match_id = %s;
""",
(match_id,),
)
settings_row = cur.fetchone()
cur.execute(
"""
SELECT side, shot_number, result
FROM match_penalties
WHERE match_id = %s
ORDER BY shot_number, side;
""",
(match_id,),
)
rows = cur.fetchall()
finally:
conn.close()
max_rounds = int(settings_row[0]) if settings_row else 5
for _, shot_number, _ in rows:
max_rounds = max(max_rounds, int(shot_number or 0), 5)
shots_map = {
(str(side), int(shot_number)): str(result)
for side, shot_number, result in rows
}
rounds = []
totals = {"home": 0, "away": 0}
completed = {"home": 0, "away": 0}
for number in range(1, max_rounds + 1):
row = {"number": number}
for side in ("home", "away"):
result = shots_map.get((side, number), "")
if result:
completed[side] += 1
if result == "scored":
totals[side] += 1
row[side] = result
rounds.append(row)
shots = [
{"side": str(side), "shot_number": int(shot_number), "result": str(result)}
for side, shot_number, result in rows
]
return {
"match_id": match_id,
"home_team": home_team_name or "Хозяева",
"away_team": away_team_name or "Гости",
"max_rounds": max_rounds,
"totals": totals,
"completed": completed,
"rounds": rounds,
"shots": shots,
}
def set_penalty_shot(match_id: int, side: str, shot_number: int, result: str) -> None:
side = _normalize_side(side)
result = _normalize_result(result)
safe_shot_number = max(1, int(shot_number or 1))
conn = get_connection()
try:
with conn.cursor() as cur:
_ensure_penalty_rounds(cur, match_id, safe_shot_number)
cur.execute(
"""
INSERT INTO match_penalties (match_id, side, shot_number, result, created_at, updated_at)
VALUES (%s, %s, %s, %s, NOW(), NOW())
ON CONFLICT (match_id, side, shot_number)
DO UPDATE SET
result = EXCLUDED.result,
updated_at = NOW();
""",
(match_id, side, safe_shot_number, result),
)
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def delete_penalty_shot(match_id: int, side: str, shot_number: int) -> None:
side = _normalize_side(side)
safe_shot_number = max(1, int(shot_number or 1))
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
DELETE FROM match_penalties
WHERE match_id = %s AND side = %s AND shot_number = %s;
""",
(match_id, side, safe_shot_number),
)
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def add_penalty_round(match_id: int) -> int:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO match_penalty_settings (match_id, max_rounds, created_at, updated_at)
VALUES (%s, 6, NOW(), NOW())
ON CONFLICT (match_id)
DO UPDATE SET
max_rounds = GREATEST(match_penalty_settings.max_rounds + 1, 6),
updated_at = NOW()
RETURNING max_rounds;
""",
(match_id,),
)
max_rounds = int(cur.fetchone()[0])
conn.commit()
return max_rounds
except Exception:
conn.rollback()
raise
finally:
conn.close()
def delete_last_penalty_round(match_id: int) -> int:
"""Удаляет последнюю добавленную строку серии пенальти и ее данные.
Первые 5 строк считаются базовыми и не удаляются. Если добавлена 6-я,
7-я и т.д. строка, удаляется самая последняя строка вместе с ударами
обеих команд в этой строке.
"""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
SELECT COALESCE(max_rounds, 5)
FROM match_penalty_settings
WHERE match_id = %s;
""",
(match_id,),
)
settings_row = cur.fetchone()
settings_max = int(settings_row[0]) if settings_row else 5
cur.execute(
"""
SELECT COALESCE(MAX(shot_number), 0)
FROM match_penalties
WHERE match_id = %s;
""",
(match_id,),
)
shots_max = int(cur.fetchone()[0] or 0)
current_max = max(5, settings_max, shots_max)
if current_max <= 5:
_ensure_penalty_rounds(cur, match_id, 5)
conn.commit()
return 5
new_max = max(5, current_max - 1)
cur.execute(
"""
DELETE FROM match_penalties
WHERE match_id = %s AND shot_number = %s;
""",
(match_id, current_max),
)
cur.execute(
"""
INSERT INTO match_penalty_settings (match_id, max_rounds, created_at, updated_at)
VALUES (%s, %s, NOW(), NOW())
ON CONFLICT (match_id)
DO UPDATE SET
max_rounds = EXCLUDED.max_rounds,
updated_at = NOW();
""",
(match_id, new_max),
)
conn.commit()
return new_max
except Exception:
conn.rollback()
raise
finally:
conn.close()
def clear_penalties(match_id: int) -> None:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute("DELETE FROM match_penalties WHERE match_id = %s;", (match_id,))
cur.execute("DELETE FROM match_penalty_settings WHERE match_id = %s;", (match_id,))
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()