Fix Team Race duplicate-membership bug and two related team-roster gaps

Reported by the user: creating a team, adding the wrong driver, then
moving that driver to another team by ticking them on the new team's
edit form left the driver on both teams at once. Root cause: there
was never a "move" action - the edit form only ever added a driver to
the team being saved, never removed them from wherever they already
were. Live laps from a driver on two teams were then silently
credited to only the first-matching team, with no clear error.

reassignTeamMembers (event_race_logic.js) now strips a driver/car
being saved onto one team out of every other team in the same event,
and the team checkbox list shows which team a driver is already on
before you move them.

Also fixed two related gaps found while investigating, at the user's
request to fix everything: getTeamDriverPool had regressed to
ignoring the event's class/participant scoping entirely (showing
every driver in the club), and deleting a driver or car left them
behind in team rosters forever. Added a duplicate_team_members Race
Day preflight check as defense in depth for any already-corrupted
saved state.

Verified with new unit tests for reassignTeamMembers, getTeamDriverPool
and the new preflight check, plus a new permanent browser test
reproducing the user's exact reported scenario end to end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
larssand
2026-09-10 17:07:45 +02:00
parent 71b3451b13
commit 15b574a37d
16 changed files with 261 additions and 15 deletions

View File

@@ -560,10 +560,36 @@ export function getEventTeams(event, { normalizeRaceTeam }) {
export function getTeamDriverPool(event, { getEventDrivers, state }) {
return {
drivers: Array.isArray(state?.drivers) ? [...state.drivers] : [],
fallback: false,
};
const scopedDrivers = getEventDrivers(event);
if (scopedDrivers.length) {
return { drivers: scopedDrivers, fallback: false };
}
// No driver matched the event's class or configured participant list (e.g. a team is
// being set up before drivers/participants exist yet) - fall back to every driver in
// the club rather than leaving the admin with zero checkboxes and no way to proceed.
const allDrivers = Array.isArray(state?.drivers) ? [...state.drivers] : [];
return { drivers: allDrivers, fallback: allDrivers.length > 0 };
}
// A driver or car can only be a member of one team at a time. There is no separate "move
// to another team" action in the UI - ticking a driver's checkbox on a different team's
// form IS the move - so saving a team's roster here must pull that driver/car out of
// whichever other team currently has them, or the same driver ends up listed on two teams
// at once (their live passings then get silently credited to only the first match).
export function reassignTeamMembers(teams, { excludeTeamId = "", driverIds = [], carIds = [] } = {}) {
const driverSet = new Set(driverIds);
const carSet = new Set(carIds);
return (Array.isArray(teams) ? teams : []).map((team) => {
if (team.id === excludeTeamId) return team;
const nextDriverIds = (team.driverIds || []).filter((id) => !driverSet.has(id));
const nextCarIds = (team.carIds || []).filter((id) => !carSet.has(id));
if (nextDriverIds.length === (team.driverIds || []).length && nextCarIds.length === (team.carIds || []).length) {
return team;
}
return { ...team, driverIds: nextDriverIds, carIds: nextCarIds };
});
}