262 lines
11 KiB
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();
|
|
}
|
|
}
|