torneos/app/Services/ScoreSheetService.php

262 lines
11 KiB
PHP

<?php
namespace App\Services;
use App\Core\Database;
use InvalidArgumentException;
use PDO;
final class ScoreSheetService
{
private PDO $db;
public function __construct()
{
$this->db = Database::connection();
}
public function state(int $matchId): array
{
$match = $this->match($matchId);
$sets = $this->sets($matchId);
$events = $this->events($matchId);
return [
'match' => $match,
'sets' => $sets,
'sets_won' => [
'home' => count(array_filter($sets, fn ($set) => ($set['winner_team_id'] ?? null) === $match['home_team_id'])),
'away' => count(array_filter($sets, fn ($set) => ($set['winner_team_id'] ?? null) === $match['away_team_id'])),
],
'events' => $events,
];
}
public function addEvent(int $matchId, array $data, array $user): array
{
$match = $this->match($matchId);
if (($match['status'] ?? '') === 'finished') {
throw new InvalidArgumentException('El partido ya finalizó');
}
$type = $data['event_type'] ?? 'point';
$teamId = (int) ($data['team_id'] ?? 0);
if ($teamId && !in_array($teamId, [(int) $match['home_team_id'], (int) $match['away_team_id']], true)) {
throw new InvalidArgumentException('El equipo no participa en este partido');
}
$this->db->beginTransaction();
try {
$set = $this->currentSet($matchId);
if ($type === 'point') {
$homePoint = $teamId === (int) $match['home_team_id'] ? 1 : 0;
$awayPoint = $teamId === (int) $match['away_team_id'] ? 1 : 0;
$stmt = $this->db->prepare(
'UPDATE match_sets
SET home_points = home_points + :home, away_points = away_points + :away
WHERE id = :id'
);
$stmt->execute(['home' => $homePoint, 'away' => $awayPoint, 'id' => $set['id']]);
$set = $this->setById((int) $set['id']);
}
$stmt = $this->db->prepare(
'INSERT INTO match_events
(match_id, set_number, team_id, player_id, event_type, points_home, points_away, notes, created_by)
VALUES (:match_id, :set_number, :team_id, :player_id, :event_type, :points_home, :points_away, :notes, :created_by)'
);
$stmt->execute([
'match_id' => $matchId,
'set_number' => $set['set_number'],
'team_id' => $teamId ?: null,
'player_id' => $data['player_id'] ?? null,
'event_type' => $type,
'points_home' => $set['home_points'],
'points_away' => $set['away_points'],
'notes' => $data['notes'] ?? null,
'created_by' => $user['sub'] ?? null,
]);
$this->evaluateSetAndMatch($matchId, $match, $set);
$this->db->commit();
} catch (\Throwable $e) {
$this->db->rollBack();
throw $e;
}
return $this->state($matchId);
}
public function standings(int $tournamentId): array
{
$stmt = $this->db->prepare(
'SELECT t.id, t.name,
COALESCE(s.played, 0) played,
COALESCE(s.won, 0) won,
COALESCE(s.lost, 0) lost,
COALESCE(s.sets_for, 0) sets_for,
COALESCE(s.sets_against, 0) sets_against,
COALESCE(s.points, 0) points
FROM teams t
LEFT JOIN team_standings s ON s.team_id = t.id
WHERE t.tournament_id = :tournament_id
ORDER BY points DESC, (sets_for - sets_against) DESC, sets_for DESC, t.name ASC'
);
$stmt->execute(['tournament_id' => $tournamentId]);
return $stmt->fetchAll();
}
public function stats(int $tournamentId): array
{
$stmt = $this->db->prepare(
'SELECT p.id, CONCAT(p.first_name, " ", p.last_name) AS player_name, tm.name AS team_name,
SUM(e.event_type = "point") AS points,
SUM(e.event_type = "ace") AS aces,
SUM(e.event_type = "block") AS blocks,
SUM(e.event_type = "attack") AS attacks,
SUM(e.event_type = "mvp") AS mvp
FROM players p
JOIN teams tm ON tm.id = p.team_id
LEFT JOIN match_events e ON e.player_id = p.id
WHERE tm.tournament_id = :tournament_id
GROUP BY p.id, player_name, team_name
ORDER BY points DESC, aces DESC, blocks DESC
LIMIT 50'
);
$stmt->execute(['tournament_id' => $tournamentId]);
return ['players' => $stmt->fetchAll(), 'teams' => $this->standings($tournamentId)];
}
private function evaluateSetAndMatch(int $matchId, array $match, array $set): void
{
$target = (int) $set['set_number'] === 5 ? 15 : 25;
$home = (int) $set['home_points'];
$away = (int) $set['away_points'];
if (max($home, $away) < $target || abs($home - $away) < 2) {
$this->markLive($matchId);
return;
}
$winner = $home > $away ? $match['home_team_id'] : $match['away_team_id'];
$stmt = $this->db->prepare('UPDATE match_sets SET winner_team_id = :winner WHERE id = :id');
$stmt->execute(['winner' => $winner, 'id' => $set['id']]);
$sets = $this->sets($matchId);
$homeSets = count(array_filter($sets, fn ($row) => ($row['winner_team_id'] ?? null) === $match['home_team_id']));
$awaySets = count(array_filter($sets, fn ($row) => ($row['winner_team_id'] ?? null) === $match['away_team_id']));
if ($homeSets === 3 || $awaySets === 3) {
$winnerTeam = $homeSets === 3 ? $match['home_team_id'] : $match['away_team_id'];
$this->finishMatch($matchId, (int) $winnerTeam, $homeSets, $awaySets);
return;
}
$nextNumber = (int) $set['set_number'] + 1;
$sideSwitch = $nextNumber % 2 === 0 ? 'switched' : 'normal';
$stmt = $this->db->prepare(
'INSERT IGNORE INTO match_sets (match_id, set_number, home_points, away_points, side_state)
VALUES (:match_id, :set_number, 0, 0, :side_state)'
);
$stmt->execute(['match_id' => $matchId, 'set_number' => $nextNumber, 'side_state' => $sideSwitch]);
}
private function finishMatch(int $matchId, int $winnerTeamId, int $homeSets, int $awaySets): void
{
$stmt = $this->db->prepare(
'UPDATE matches SET status = "finished", winner_team_id = :winner, home_sets = :home_sets, away_sets = :away_sets WHERE id = :id'
);
$stmt->execute(['winner' => $winnerTeamId, 'home_sets' => $homeSets, 'away_sets' => $awaySets, 'id' => $matchId]);
$this->rebuildStandings((int) $this->match($matchId)['tournament_id']);
}
public function rebuildStandings(int $tournamentId): void
{
$this->db->prepare('DELETE FROM team_standings WHERE tournament_id = :id')->execute(['id' => $tournamentId]);
$teams = $this->db->prepare('SELECT id FROM teams WHERE tournament_id = :id');
$teams->execute(['id' => $tournamentId]);
foreach ($teams->fetchAll() as $team) {
$stmt = $this->db->prepare(
'SELECT
SUM((home_team_id = :team OR away_team_id = :team) AND status = "finished") played,
SUM(winner_team_id = :team) won,
SUM(status = "finished" AND winner_team_id <> :team) lost,
SUM(CASE WHEN home_team_id = :team THEN home_sets WHEN away_team_id = :team THEN away_sets ELSE 0 END) sets_for,
SUM(CASE WHEN home_team_id = :team THEN away_sets WHEN away_team_id = :team THEN home_sets ELSE 0 END) sets_against
FROM matches WHERE tournament_id = :tournament_id'
);
$stmt->execute(['team' => $team['id'], 'tournament_id' => $tournamentId]);
$row = $stmt->fetch();
$won = (int) ($row['won'] ?? 0);
$lost = (int) ($row['lost'] ?? 0);
$this->db->prepare(
'INSERT INTO team_standings (tournament_id, team_id, played, won, lost, sets_for, sets_against, points)
VALUES (:tournament_id, :team_id, :played, :won, :lost, :sets_for, :sets_against, :points)'
)->execute([
'tournament_id' => $tournamentId,
'team_id' => $team['id'],
'played' => (int) ($row['played'] ?? 0),
'won' => $won,
'lost' => $lost,
'sets_for' => (int) ($row['sets_for'] ?? 0),
'sets_against' => (int) ($row['sets_against'] ?? 0),
'points' => ($won * 3) + $lost,
]);
}
}
private function currentSet(int $matchId): array
{
$stmt = $this->db->prepare('SELECT * FROM match_sets WHERE match_id = :id AND winner_team_id IS NULL ORDER BY set_number LIMIT 1');
$stmt->execute(['id' => $matchId]);
$set = $stmt->fetch();
if ($set) {
return $set;
}
$this->db->prepare('INSERT INTO match_sets (match_id, set_number) VALUES (:id, 1)')->execute(['id' => $matchId]);
return $this->setById((int) $this->db->lastInsertId());
}
private function markLive(int $matchId): void
{
$this->db->prepare('UPDATE matches SET status = "live" WHERE id = :id AND status = "scheduled"')->execute(['id' => $matchId]);
}
private function match(int $matchId): array
{
$stmt = $this->db->prepare('SELECT * FROM matches WHERE id = :id');
$stmt->execute(['id' => $matchId]);
$match = $stmt->fetch();
if (!$match) {
throw new InvalidArgumentException('Partido no encontrado');
}
return $match;
}
private function setById(int $id): array
{
$stmt = $this->db->prepare('SELECT * FROM match_sets WHERE id = :id');
$stmt->execute(['id' => $id]);
return $stmt->fetch();
}
private function sets(int $matchId): array
{
$stmt = $this->db->prepare('SELECT * FROM match_sets WHERE match_id = :id ORDER BY set_number');
$stmt->execute(['id' => $matchId]);
return $stmt->fetchAll();
}
private function events(int $matchId): array
{
$stmt = $this->db->prepare(
'SELECT e.*, CONCAT(p.first_name, " ", p.last_name) AS player_name, t.name AS team_name
FROM match_events e
LEFT JOIN players p ON p.id = e.player_id
LEFT JOIN teams t ON t.id = e.team_id
WHERE e.match_id = :id
ORDER BY e.id DESC LIMIT 100'
);
$stmt->execute(['id' => $matchId]);
return $stmt->fetchAll();
}
}