feat(matches): add manual match creation functionality and related fields
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 59s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 59s
This commit is contained in:
@@ -17,6 +17,72 @@ import HttpError from '../exceptions/HttpError.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class MatchService {
|
||||
|
||||
async enrichMatch(match) {
|
||||
const enriched = {
|
||||
id: match.id, date: match.date, time: match.time,
|
||||
homeTeamId: match.homeTeamId, guestTeamId: match.guestTeamId,
|
||||
locationId: match.locationId, leagueId: match.leagueId,
|
||||
code: match.code, homePin: match.homePin, guestPin: match.guestPin,
|
||||
homeMatchPoints: match.homeMatchPoints || 0, guestMatchPoints: match.guestMatchPoints || 0,
|
||||
isCompleted: match.isCompleted || false, pdfUrl: match.pdfUrl,
|
||||
playersReady: match.playersReady || [], playersPlanned: match.playersPlanned || [], playersPlayed: match.playersPlayed || [],
|
||||
fixtureType: match.fixtureType || 'league', notes: match.notes || null,
|
||||
homeTeam: { name: 'Unbekannt' }, guestTeam: { name: 'Unbekannt' },
|
||||
location: { name: 'Unbekannt', address: '', city: '', zip: '' }, leagueDetails: { name: 'Unbekannt' }
|
||||
};
|
||||
const [homeTeam, guestTeam, location, league] = await Promise.all([
|
||||
match.homeTeamId ? Team.findByPk(match.homeTeamId, { attributes: ['name'] }) : null,
|
||||
match.guestTeamId ? Team.findByPk(match.guestTeamId, { attributes: ['name'] }) : null,
|
||||
match.locationId ? Location.findByPk(match.locationId, { attributes: ['name', 'address', 'city', 'zip'] }) : null,
|
||||
match.leagueId ? League.findByPk(match.leagueId, { attributes: ['name'] }) : null
|
||||
]);
|
||||
if (homeTeam) enriched.homeTeam = homeTeam;
|
||||
if (guestTeam) enriched.guestTeam = guestTeam;
|
||||
if (location) enriched.location = location;
|
||||
if (league) enriched.leagueDetails = league;
|
||||
return enriched;
|
||||
}
|
||||
|
||||
async createManualMatch(userToken, clubId, payload = {}) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const parsedClubId = Number(clubId);
|
||||
const clubTeamId = Number(payload.clubTeamId);
|
||||
const date = String(payload.date || '').trim();
|
||||
const time = String(payload.time || '').trim();
|
||||
const opponentName = String(payload.opponentName || '').trim();
|
||||
const homeAway = payload.homeAway === 'away' ? 'away' : 'home';
|
||||
if (!Number.isInteger(clubTeamId) || !/^\d{4}-\d{2}-\d{2}$/.test(date) || !/^\d{2}:\d{2}$/.test(time) || !opponentName) {
|
||||
throw new HttpError('Mannschaft, Gegner, Datum und Uhrzeit sind erforderlich', 400);
|
||||
}
|
||||
const clubTeam = await ClubTeam.findOne({ where: { id: clubTeamId, clubId: parsedClubId } });
|
||||
if (!clubTeam?.leagueId || !clubTeam?.seasonId) throw new HttpError('Die ausgewählte Mannschaft ist keiner Liga der Saison zugeordnet', 400);
|
||||
const league = await League.findOne({ where: { id: clubTeam.leagueId, clubId: parsedClubId, seasonId: clubTeam.seasonId } });
|
||||
if (!league) throw new HttpError('Ungültige Liga für die ausgewählte Mannschaft', 400);
|
||||
const [ownTeam] = await Team.findOrCreate({
|
||||
where: { name: clubTeam.name, clubId: parsedClubId, leagueId: league.id, seasonId: clubTeam.seasonId },
|
||||
defaults: { name: clubTeam.name, clubId: parsedClubId, leagueId: league.id, seasonId: clubTeam.seasonId }
|
||||
});
|
||||
const [opponentTeam] = await Team.findOrCreate({
|
||||
where: { name: opponentName, clubId: parsedClubId, leagueId: league.id, seasonId: clubTeam.seasonId },
|
||||
defaults: { name: opponentName, clubId: parsedClubId, leagueId: league.id, seasonId: clubTeam.seasonId }
|
||||
});
|
||||
if (ownTeam.id === opponentTeam.id) throw new HttpError('Gegner darf nicht mit der eigenen Mannschaft identisch sein', 400);
|
||||
let locationId = null;
|
||||
const locationName = String(payload.locationName || '').trim();
|
||||
if (locationName) {
|
||||
const locationData = { name: locationName, address: String(payload.locationAddress || '').trim() || null, city: String(payload.locationCity || '').trim() || '', zip: String(payload.locationZip || '').trim() || '' };
|
||||
const [location] = await Location.findOrCreate({ where: locationData, defaults: locationData });
|
||||
locationId = location.id;
|
||||
}
|
||||
const match = await Match.create({
|
||||
date: new Date(`${date}T${time}:00`), time, clubId: parsedClubId, leagueId: league.id, locationId,
|
||||
homeTeamId: homeAway === 'home' ? ownTeam.id : opponentTeam.id,
|
||||
guestTeamId: homeAway === 'home' ? opponentTeam.id : ownTeam.id,
|
||||
fixtureType: 'cup', notes: String(payload.notes || '').trim() || null
|
||||
});
|
||||
return { id: match.id, clubId: match.clubId, match: await this.enrichMatch(match) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Format team name with age class suffix
|
||||
* @param {string} teamName - Base team name (e.g. "Harheimer TC")
|
||||
@@ -362,6 +428,20 @@ class MatchService {
|
||||
);
|
||||
ownTeamIdSet = new Set(ownTeams.map((t) => t.id));
|
||||
}
|
||||
const configuredClubTeams = await ClubTeam.findAll({
|
||||
where: { clubId, seasonId: season.id },
|
||||
attributes: ['name', 'leagueId']
|
||||
});
|
||||
const configuredTeamNames = new Set(configuredClubTeams.map((team) => this.normalizeTeamNameForMatch(team.name)));
|
||||
if (configuredTeamNames.size && leagueIdList.length) {
|
||||
const configuredTeams = await Team.findAll({
|
||||
where: { leagueId: { [Op.in]: leagueIdList } },
|
||||
attributes: ['id', 'name']
|
||||
});
|
||||
configuredTeams
|
||||
.filter((team) => configuredTeamNames.has(this.normalizeTeamNameForMatch(team.name)))
|
||||
.forEach((team) => ownTeamIdSet.add(team.id));
|
||||
}
|
||||
|
||||
const matches = await Match.findAll({
|
||||
where: {
|
||||
@@ -403,6 +483,8 @@ class MatchService {
|
||||
playersReady: match.playersReady || [],
|
||||
playersPlanned: match.playersPlanned || [],
|
||||
playersPlayed: match.playersPlayed || [],
|
||||
fixtureType: match.fixtureType || 'league',
|
||||
notes: match.notes || null,
|
||||
homeTeam: { name: 'Unbekannt' },
|
||||
guestTeam: { name: 'Unbekannt' },
|
||||
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
|
||||
@@ -464,7 +546,14 @@ class MatchService {
|
||||
this.isTeamNameForClub(team.name, club.name)
|
||||
);
|
||||
|
||||
const ownTeamIds = ownTeams.map(t => t.id);
|
||||
const configuredClubTeams = await ClubTeam.findAll({
|
||||
where: { clubId, leagueId },
|
||||
attributes: ['name']
|
||||
});
|
||||
const configuredTeamNames = new Set(configuredClubTeams.map((team) => this.normalizeTeamNameForMatch(team.name)));
|
||||
ownTeams.push(...teamsInLeague.filter((team) => configuredTeamNames.has(this.normalizeTeamNameForMatch(team.name))));
|
||||
|
||||
const ownTeamIds = [...new Set(ownTeams.map(t => t.id))];
|
||||
|
||||
if (ownTeamIds.length > 0) {
|
||||
matches = await Match.findAll({
|
||||
@@ -502,6 +591,8 @@ class MatchService {
|
||||
playersReady: match.playersReady || [],
|
||||
playersPlanned: match.playersPlanned || [],
|
||||
playersPlayed: match.playersPlayed || [],
|
||||
fixtureType: match.fixtureType || 'league',
|
||||
notes: match.notes || null,
|
||||
homeTeam: { name: 'Unbekannt' },
|
||||
guestTeam: { name: 'Unbekannt' },
|
||||
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
|
||||
@@ -641,6 +732,8 @@ class MatchService {
|
||||
playersReady: updated.playersReady || [],
|
||||
playersPlanned: updated.playersPlanned || [],
|
||||
playersPlayed: updated.playersPlayed || [],
|
||||
fixtureType: updated.fixtureType || 'league',
|
||||
notes: updated.notes || null,
|
||||
homeTeam: { name: 'Unbekannt' },
|
||||
guestTeam: { name: 'Unbekannt' },
|
||||
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
|
||||
|
||||
Reference in New Issue
Block a user