все остальные апдейты на Кубок России
This commit is contained in:
295
repositories/match_penalty_repository.py
Normal file
295
repositories/match_penalty_repository.py
Normal 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()
|
||||
Reference in New Issue
Block a user