Tournament team-picked map lists and check-ins fixes

This commit is contained in:
Kalle
2026-09-20 11:27:25 +03:00
parent b98f700a21
commit a3a44aacf4
21 changed files with 113 additions and 17 deletions

View File

@@ -815,15 +815,7 @@ async function updateTournamentTables(
// the teams' picks were made against the old settings, so they pick again
if (changedMapPickingStyle || changedMapPool) {
await trx
.deleteFrom("MapPoolMap")
.where("tournamentTeamId", "in", (eb) =>
eb
.selectFrom("TournamentTeam")
.select("id")
.where("tournamentId", "=", tournamentId),
)
.execute();
await resetTeamMapPicks({ tournamentId, args }, trx);
}
if (
@@ -841,6 +833,47 @@ async function updateTournamentTables(
}
}
/**
* Deletes every team's map picks. Teams that had picked are also checked out when picks are still
* a check-in requirement, as otherwise they would enter the bracket without a pool.
*/
async function resetTeamMapPicks(
{
tournamentId,
args,
}: { tournamentId: number; args: Pick<UpdateArgs, "mapPickingStyle"> },
trx: Transaction<DB>,
) {
// before the picks go, as the teams to check out are the ones that have them
if (args.mapPickingStyle !== "TO") {
await trx
.deleteFrom("TournamentTeamCheckIn")
.where("bracketIdx", "is", null)
.where("tournamentTeamId", "in", (eb) =>
eb
.selectFrom("MapPoolMap")
.innerJoin(
"TournamentTeam",
"TournamentTeam.id",
"MapPoolMap.tournamentTeamId",
)
.select("TournamentTeam.id")
.where("TournamentTeam.tournamentId", "=", tournamentId),
)
.execute();
}
await trx
.deleteFrom("MapPoolMap")
.where("tournamentTeamId", "in", (eb) =>
eb
.selectFrom("TournamentTeam")
.select("id")
.where("tournamentId", "=", tournamentId),
)
.execute();
}
function teamPickSettings(
args: Pick<CreateArgs, "mapPickingStyle" | "teamPick" | "isFullTournament">,
) {

View File

@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, test } from "vitest";
import * as BadgeFactory from "~/db/seed/factories/BadgeFactory";
import * as TournamentFactory from "~/db/seed/factories/TournamentFactory";
import * as TournamentOrganizationFactory from "~/db/seed/factories/TournamentOrganizationFactory";
import * as TournamentTeamFactory from "~/db/seed/factories/TournamentTeamFactory";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import * as CalendarRepository from "~/features/calendar/CalendarRepository.server";
import { MapPool } from "~/features/map-list-generator/core/map-pool";
@@ -21,6 +22,8 @@ const editAction = wrappedAction<typeof calendarNewSchemaServer>({
const users = UserFactory.pool();
const badgeManagingAuthorId = () => users.id(1);
const teamPickAuthorId = () => users.id(5);
const teamPickTeamMemberIds = () => users.ids(4);
describe("calendar new action: editing an event with badge prizes", () => {
let orgAdminId: number;
@@ -168,6 +171,7 @@ describe("calendar new action: bracket URL", () => {
describe("calendar new action: team picked tournament", () => {
beforeEach(async () => {
await UserFactory.createRegular(null, { roles: ["TOURNAMENT_ORGANIZER"] });
await users.create(5);
});
test("saves the team pick settings and the custom pool", async () => {
@@ -211,4 +215,37 @@ describe("calendar new action: team picked tournament", () => {
customPool.serialized,
);
});
test("changing the pick settings resets the teams' picks and checks them out", async () => {
const tournament = await TournamentFactory.create({
authorId: teamPickAuthorId(),
mapPickingStyle: "AUTO",
teamPick: { modes: [{ mode: "SZ", count: 2 }], pool: "ALL" },
});
await TournamentTeamFactory.create(
{
tournamentId: tournament.id,
memberUserIds: teamPickTeamMemberIds(),
mapPool: new MapPool({ ...MapPool.EMPTY.parsed, SZ: [1, 2] }),
},
{ isCheckedIn: true },
);
const res = await editAction(
calendarNewFormValues({
eventToEditId: tournament.eventId,
bracketUrl: "https://sendou.ink",
mapPickingStyle: "AUTO",
teamPickModes: ["SZ"],
teamPickCounts: [{ mode: "SZ", count: 3 }],
teamPickPool: "ALL",
}),
{ user: teamPickAuthorId() },
);
expect(res.fieldErrors).toBeUndefined();
const edited = await tournamentFromDB(tournament.id);
expect(edited.ctx.teams[0].hasMapPool).toBe(0);
expect(edited.ctx.teams[0].checkIns).toEqual([]);
});
});

View File

@@ -601,13 +601,18 @@ function TeamPickCountInputs({
value={count}
onChange={(e) =>
onChange(
pickedModes.map((m) => ({
mode: m,
count:
// an emptied input drops the entry so the mode falls back to the default count
pickedModes.flatMap((m) => {
const newCount =
m === mode
? Number(e.target.value)
: (value.find((c) => c.mode === m)?.count ?? 1),
})),
? Number.parseInt(e.target.value, 10)
: value.find((c) => c.mode === m)?.count;
return typeof newCount === "number" &&
!Number.isNaN(newCount)
? [{ mode: m, count: newCount }]
: [];
}),
mode,
)
}

View File

@@ -118,7 +118,12 @@ export default function TournamentRegisterPage() {
return (
<div className={clsx("stack lg", containerClassName("normal"))}>
{isRegularMemberOfATeam ? (
{tournament.hasStarted && teamMemberOf ? (
<div className="stack md">
<Alert>{t("tournament:pre.startedNoEdit")}</Alert>
<RegistrationForms readOnly />
</div>
) : isRegularMemberOfATeam ? (
<div className="stack md">
<Alert>{t("tournament:pre.captainOnlyEdit")}</Alert>
<div className="stack md items-center">

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "Log in for at tilmelde dig",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "Logge dich zum Registrieren ein",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -362,7 +362,7 @@
"options.teamPickPool.SENDOUQ": "SendouQ legal maps",
"options.teamPickPool.ALL": "All maps",
"options.teamPickPool.CUSTOM": "Custom",
"bottomTexts.teamPickReset": "Changing the map settings resets the maps every team has already picked",
"bottomTexts.teamPickReset": "Changing the map settings resets the maps every team has already picked and checks those teams out",
"bottomTexts.regClosesAt": "All times relative to the reported tournament start time e.g. \"30 minutes\" means \"30 minutes before event start time\". After registration closes only TO's can make changes to team rosters.",
"bottomTexts.maxTeamSize": "Maximum number of players that can be registered per team. Doesn't apply to tournament organizers. Default is 6.",
"bottomTexts.avatarValidation": "Note that for non-patrons there is a validation process before avatar is shown.",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "To play tournaments on sendou.ink you'll need to register your friend code.",
"pre.logIn": "Log in to register",
"pre.captainOnlyEdit": "You are in a team for this event. Only the team captain can edit the registration.",
"pre.startedNoEdit": "The tournament has started. Registration can no longer be edited.",
"pre.leave.button": "Leave the team",
"pre.leave.confirm": "Leave \"{{teamName}}\"?",
"pre.leave.cant.organizerAdded": "You were added to the team by the organizer. Contact the TO to leave the team.",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "Inicia sesión para inscribirte",
"pre.captainOnlyEdit": "Estás en un equipo para este evento. Solo el capitán del equipo puede editar la inscripción.",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "Inicia sesión para inscribirte",
"pre.captainOnlyEdit": "Estás en un equipo para este evento. Solo el capitán del equipo puede editar la inscripción.",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "Connectez-vous pour vous inscrire",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "Connectez-vous pour vous inscrire",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "התחבר כדי להירשם",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "Accedi per iscriverti",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "ログインして登録する",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "Faça o login para registrar",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "Войдите, чтобы зарегистрироваться",
"pre.captainOnlyEdit": "",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",

View File

@@ -37,6 +37,7 @@
"pre.friendCode.needed": "",
"pre.logIn": "登录以报名",
"pre.captainOnlyEdit": "您已加入此赛事的某支队伍。只有队长可以修改报名信息。",
"pre.startedNoEdit": "",
"pre.leave.button": "",
"pre.leave.confirm": "",
"pre.leave.cant.organizerAdded": "",