Fix reopening round robin match locking subsequent matches

When reopening a match in round robin format to correct a misreported score,
subsequent matches that were already in progress would become locked. This
happened because resetMatchStatus was clearing their status and startedAt.

Now round robin matches are left as-is when a preceding match is reopened,
allowing them to continue where they left off.

Closes #2690
This commit is contained in:
Kalle 2026-01-04 22:01:45 +02:00
parent 306e12d8c4
commit 650bd0028e
3 changed files with 67 additions and 21 deletions

View File

@ -34,7 +34,6 @@ import {
import { findResultsByMatchId } from "../queries/findResultsByMatchId.server";
import { insertTournamentMatchGameResult } from "../queries/insertTournamentMatchGameResult.server";
import { insertTournamentMatchGameResultParticipant } from "../queries/insertTournamentMatchGameResultParticipant.server";
import { resetMatchStatus } from "../queries/resetMatchStatus.server";
import { updateMatchGameResultPoints } from "../queries/updateMatchGameResultPoints.server";
import {
matchPageParamsSchema,
@ -510,13 +509,10 @@ export const action: ActionFunction = async ({ params, request }) => {
tournament.matchIdToBracketIdx(match.id)!,
)!.type;
sql.transaction(() => {
for (const match of followingMatches) {
// for other formats the database triggers handle the startedAt clearing. Status reset for those is managed by the brackets-manager
if (bracketFormat === "round_robin") {
resetMatchStatus(match.id);
} else {
// edge case but for round robin we can just leave the match as is, lock it then unlock later to continue where they left off (should not really ever happen)
deleteMatchPickBanEvents(match.id);
// edge case but for round robin we can just leave the match as is, lock it then unlock later to continue where they left off (should not really ever happen)
if (bracketFormat !== "round_robin") {
for (const followingMatch of followingMatches) {
deleteMatchPickBanEvents(followingMatch.id);
}
}

View File

@ -1,13 +0,0 @@
import { sql } from "~/db/sql";
const stm = sql.prepare(/* sql */ `
update "TournamentMatch"
set
"status" = 0,
"startedAt" = null
where "id" = @matchId
`);
export function resetMatchStatus(matchId: number) {
stm.run({ matchId });
}

View File

@ -745,6 +745,69 @@ test.describe("Tournament bracket", () => {
});
});
test("reopening round robin match does not lock already-unlocked matches (issue #2690)", async ({
page,
}) => {
const tournamentId = 3;
await seed(page);
await impersonate(page);
await navigate({
page,
url: tournamentBracketsPage({ tournamentId }),
});
await page.getByTestId("finalize-bracket-button").click();
await submit(page, "confirm-finalize-bracket-button");
// Use Group B which has 4 teams and 2 matches per round
// Group B Round 1: Match 7 (Whatcha Say vs Come Together), Match 8 (We Are Champions vs Please Mr Postman)
// Group B Round 2: Match 9 (Please Mr Postman vs Come Together), Match 10 (Whatcha Say vs We Are Champions)
// Complete R1 matches in group B (matches 7 and 8) to unlock R2 matches
await navigateToMatch(page, 7);
await reportResult({
page,
amountOfMapsToReport: 2,
sidesWithMoreThanFourPlayers: ["last"],
points: [100, 0],
});
await backToBracket(page);
await navigateToMatch(page, 8);
await reportResult({
page,
amountOfMapsToReport: 2,
sidesWithMoreThanFourPlayers: ["first", "last"],
points: [100, 0],
});
await backToBracket(page);
// Match 9 is R2 in group B - should now be unlocked since R1 is complete
// Start it but don't complete it
await navigateToMatch(page, 9);
await reportResult({
page,
amountOfMapsToReport: 1,
sidesWithMoreThanFourPlayers: ["last"],
points: [100, 0],
});
await backToBracket(page);
// Reopen match 7 (R1 match) - simulating a score misreport correction
await navigateToMatch(page, 7);
await submit(page, "reopen-match-button");
await backToBracket(page);
// Verify the R2 match that was already in progress is still playable
// Before the fix, this would become locked and unplayable
await navigateToMatch(page, 9);
await expect(page.getByText("1-0")).toBeVisible();
await page.getByTestId("actions-tab").click();
await expect(page.getByTestId("winner-radio-1")).toBeVisible();
});
test("locks/unlocks matches & sets match as casted", async ({ page }) => {
const tournamentId = 2;