diff --git a/.vscode/settings.json b/.vscode/settings.json index 46211fab0a..66f8a853c2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,9 @@ "github-actions.workflows.pinned.workflows": [ ".github/workflows/main.yml" ], - "exportall.config.folderListener": [] + "exportall.config.folderListener": [], + "sonarlint.connectedMode.project": { + "connectionId": "badman", + "projectKey": "Badminton-Apps_badman" + } } diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 8a3104d9cc..a9591e67cd 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -22,6 +22,7 @@ import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; import versionPackage from '../version.json'; import { CleanEnvironmentModule } from './clean-environment.module'; +import { CalendarController } from './controllers/ical.controller'; const productionModules = []; if (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') { @@ -70,7 +71,7 @@ console.log('envFilePath', envFilePath, process.env.NODE_ENV); SocketModule, TransferLoanModule, ], - controllers: [AppController, ImageController], + controllers: [AppController, ImageController, CalendarController], providers: [Logger], }) export class AppModule { diff --git a/apps/api/src/app/controllers/ical.controller.ts b/apps/api/src/app/controllers/ical.controller.ts new file mode 100644 index 0000000000..ea00d2d3c4 --- /dev/null +++ b/apps/api/src/app/controllers/ical.controller.ts @@ -0,0 +1,83 @@ +import { Team, Location, EncounterCompetition } from '@badman/backend-database'; +import { Controller, Get, Query, Res } from '@nestjs/common'; +import { Response } from 'express'; +import { ICalCalendar } from 'ical-generator'; +import moment from 'moment'; +import { Includeable, Op } from 'sequelize'; + +@Controller('calendar') +export class CalendarController { + @Get('team') + async generateCalendarLink( + @Query() query: { teamId: string; linkId: string }, + @Res() res: Response, + ) { + if (!query.teamId && !query.linkId) { + return res.status(400).send('Invalid team or link id'); + } + + const team = query.teamId + ? await Team.findByPk(query.teamId) + : await Team.findOne({ where: { link: query.linkId } }); + + if (!team) { + return res.status(404).send('Team not found'); + } + + const enconuters = await EncounterCompetition.findAll({ + where: { + [Op.or]: [{ homeTeamId: team.id }, { awayTeamId: team.id }], + }, + include: [ + { + model: Location, + as: 'location', + }, + { + model: Team, + as: 'home', + }, + { + model: Team, + as: 'away', + }, + ], + }); + + console.log(`found ${enconuters.length} away encounters`); + + const events = enconuters.map((enc) => ({ + title: `${enc.home.name} vs ${enc.away.name}`, + startTime: enc.date, + endTime: moment(enc.date).add(3, 'hours').toDate(), + description: `${enc.home.name} vs ${enc.away.name}`, + location: enc.location, + })); + + const calendar = new ICalCalendar({ + name: team.name, + }); + + events.forEach((event) => { + calendar.createEvent({ + start: event.startTime, + end: event.endTime, + summary: event.title, + description: event.description, + location: { + title: event.location.name, + address: event.location.address, + // geo: { + // lat: event.location.coordinates. + // } + }, + }); + }); + + res.header('Content-Type', 'text/calendar'); + res.header('Content-Disposition', `attachment; filename="calendar-${team.name}.ics"`); + + // Send the calendar data as an .ics file + res.send(calendar.toString()); + } +} diff --git a/apps/api/src/version.json b/apps/api/src/version.json index 18abab1efe..81b8b6803b 100644 --- a/apps/api/src/version.json +++ b/apps/api/src/version.json @@ -1,3 +1,3 @@ { - "version": "6.166.3" + "version": "6.168.3" } \ No newline at end of file diff --git a/apps/api/webpack.config.js b/apps/api/webpack.config.js index fe0473b256..12d9c79ef3 100644 --- a/apps/api/webpack.config.js +++ b/apps/api/webpack.config.js @@ -11,6 +11,7 @@ module.exports = { compiler: 'tsc', main: './src/main.ts', tsConfig: './tsconfig.app.json', + assets: [ './src/assets', { diff --git a/apps/badman/src/assets/CHANGELOG.md b/apps/badman/src/assets/CHANGELOG.md index cd5f98dd42..2c63706c54 100644 --- a/apps/badman/src/assets/CHANGELOG.md +++ b/apps/badman/src/assets/CHANGELOG.md @@ -1,3 +1,74 @@ +## 6.168.3 (2024-08-15) + + +### 🩹 Fixes + +- switching ([3ea25fc68](https://github.com/Badminton-Apps/badman/commit/3ea25fc68)) + +### ❤️ Thank You + +- cskiwi @cskiwi + +## 6.168.2 (2024-08-15) + + +### 🩹 Fixes + +- now directly copy to clipboard ([e5de0f9c2](https://github.com/Badminton-Apps/badman/commit/e5de0f9c2)) +- show tooltip that it was copied ([6396f6b24](https://github.com/Badminton-Apps/badman/commit/6396f6b24)) + +### ❤️ Thank You + +- cskiwi @cskiwi + +## 6.168.1 (2024-08-15) + + +### 🩹 Fixes + +- calendar wasn't showing th right events ([7b788bcda](https://github.com/Badminton-Apps/badman/commit/7b788bcda)) + +### ❤️ Thank You + +- cskiwi @cskiwi + +## 6.168.0 (2024-08-15) + + +### 🚀 Features + +- add you calendars to your agenda ([c0f60fb83](https://github.com/Badminton-Apps/badman/commit/c0f60fb83)) + +### 🩹 Fixes + +- location check now also loads the changed location. ([a8a26720e](https://github.com/Badminton-Apps/badman/commit/a8a26720e)) + +### ❤️ Thank You + +- cskiwi @cskiwi + +## 6.167.0 (2024-08-15) + + +### 🚀 Features + +- export ranking breakdown ([29ade2ce0](https://github.com/Badminton-Apps/badman/commit/29ade2ce0)) +- new ranking breakdown + export to excel ([e0dbfa26b](https://github.com/Badminton-Apps/badman/commit/e0dbfa26b)) +- option to trigger a re-calculate of the points ([b8c3f64d6](https://github.com/Badminton-Apps/badman/commit/b8c3f64d6)) + +### 🩹 Fixes + +- more sonar cloud implementations and stricter models ([1210cff44](https://github.com/Badminton-Apps/badman/commit/1210cff44)) +- allow faster mailing ([8c9273e1d](https://github.com/Badminton-Apps/badman/commit/8c9273e1d)) +- support for adding new games ([f0428a0bd](https://github.com/Badminton-Apps/badman/commit/f0428a0bd)) +- top level translations weren't loaded ([64cd3b36b](https://github.com/Badminton-Apps/badman/commit/64cd3b36b)) +- they should use the in-app translations ([4cfe9f4a7](https://github.com/Badminton-Apps/badman/commit/4cfe9f4a7)) + +### ❤️ Thank You + +- cskiwi @cskiwi +- Glenn Latomme + ## 6.166.3 (2024-08-12) diff --git a/apps/badman/src/index.html b/apps/badman/src/index.html index 578abe3319..447bb907d5 100644 --- a/apps/badman/src/index.html +++ b/apps/badman/src/index.html @@ -1,9 +1,10 @@ - + Badman + diff --git a/apps/badman/src/version.json b/apps/badman/src/version.json index 18abab1efe..81b8b6803b 100644 --- a/apps/badman/src/version.json +++ b/apps/badman/src/version.json @@ -1,3 +1,3 @@ { - "version": "6.166.3" + "version": "6.168.3" } \ No newline at end of file diff --git a/apps/scripts/src/app/app.module.ts b/apps/scripts/src/app/app.module.ts index 2c34da5e26..d5791e2aca 100644 --- a/apps/scripts/src/app/app.module.ts +++ b/apps/scripts/src/app/app.module.ts @@ -2,10 +2,10 @@ import { DatabaseModule } from '@badman/backend-database'; import { configSchema, load } from '@badman/utils'; import { Logger, Module, OnModuleInit } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { AssignClubToPlayers } from './scripts/assign-clubs-to-players-group-role/service'; +import { PlayersWrongRankingRunner } from './scripts/players-with-wrong-ranking/players-with-wrong-ranking'; @Module({ - providers: [AssignClubToPlayers], + providers: [PlayersWrongRankingRunner], imports: [ ConfigModule.forRoot({ cache: true, @@ -18,7 +18,7 @@ import { AssignClubToPlayers } from './scripts/assign-clubs-to-players-group-rol export class ScriptModule implements OnModuleInit { private readonly logger = new Logger(ScriptModule.name); - constructor(private fixer: AssignClubToPlayers) {} + constructor(private fixer: PlayersWrongRankingRunner) {} async onModuleInit() { this.logger.log('Running script'); diff --git a/apps/scripts/src/app/scripts/players-with-wrong-ranking/players-with-wrong-ranking.ts b/apps/scripts/src/app/scripts/players-with-wrong-ranking/players-with-wrong-ranking.ts new file mode 100644 index 0000000000..79592b613e --- /dev/null +++ b/apps/scripts/src/app/scripts/players-with-wrong-ranking/players-with-wrong-ranking.ts @@ -0,0 +1,160 @@ +import { + DrawTournament, + EventEntry, + EventTournament, + Player, + RankingLastPlace, + RankingSystem, + SubEventTournament, +} from '@badman/backend-database'; +import { GameType, getSeason, getSeasonPeriod } from '@badman/utils'; +import { Injectable, Logger } from '@nestjs/common'; +import { Op } from 'sequelize'; +import xlsx from 'xlsx'; + +/** +if a player with a ranking 12 has played in any offical tournament where the subEvent has a min ranking of 9 or lower, +This means the player has a wrong ranking and was probably not calculated correctly by the ranking service. + +The goal of this script is to find all these players and write them to a file +*/ +@Injectable() +export class PlayersWrongRankingRunner { + private readonly logger = new Logger(PlayersWrongRankingRunner.name); + + async process() { + const getPrimaryRanking = await RankingSystem.findOne({ + where: { + primary: true, + }, + }); + + const playerInclude = [ + { + model: RankingLastPlace, + attributes: ['id', 'single', 'double', 'mix'], + where: { + systemId: getPrimaryRanking.id, + single: 12, + double: 12, + mix: 12, + }, + }, + ]; + + const seasonPeriod = getSeasonPeriod(getSeason() - 1); + + // start by fetching all offical events and their subevents with a min ranking of 9 or lower of last season + const events = await EventTournament.findAll({ + attributes: ['id', 'name', 'firstDay'], + where: { + firstDay: { + [Op.between]: [seasonPeriod[0], seasonPeriod[1]], + }, + official: true, + }, + include: [ + { + model: SubEventTournament, + attributes: ['id', 'name', 'level', 'gameType'], + where: { + level: { + [Op.lt]: 9, + }, + }, + include: [ + { + model: DrawTournament, + attributes: ['id'], + include: [ + { + model: EventEntry, + attributes: ['id', 'player1Id', 'player2Id'], + }, + ], + }, + ], + }, + ], + }); + + // get all the players that played in these events + + this.logger.verbose(`Found ${events.length} events`); + + const players: Map = new Map(); + + for (const event of events) { + for (const subEvent of event.subEventTournaments) { + for (const draw of subEvent.drawTournaments) { + for (const entry of draw.eventEntries) { + const entryP = await entry.getPlayers({ + attributes: ['id', 'memberId', 'firstName', 'lastName'], + include: playerInclude, + }); + + for (const player of entryP) { + if (!player?.memberId) { + continue; + } + + // skip if the player is already added + if (players.has(player.memberId)) { + continue; + } + + let usedLevel = 12; + + switch (subEvent.gameType) { + case GameType.S: + usedLevel = player?.rankingLastPlaces?.[0]?.single; + break; + case GameType.D: + usedLevel = player?.rankingLastPlaces?.[0]?.double; + break; + case GameType.MX: + usedLevel = player?.rankingLastPlaces?.[0]?.mix; + break; + default: + break; + } + + if ( + // check if the player has a wrong ranking + usedLevel - 2 > + subEvent.level + ) { + this.logger.verbose( + `Player ${player.memberId} has wrong ranking ${usedLevel} in event ${subEvent.level} (${subEvent.name})`, + ); + players.set(player.memberId, player); + } + } + } + } + } + } + + if (players.size === 0) { + this.logger.verbose(`No players with wrong ranking found`); + return; + } + + this.logger.verbose(`Found ${players.size} players with wrong ranking`); + + // write the players to a file + const wb = xlsx.utils.book_new(); + const ws = xlsx.utils.json_to_sheet( + Array.from(players.values()).map((player) => ({ + memberId: player.memberId, + firstName: player.firstName, + lastName: player.lastName, + })), + ); + + xlsx.utils.book_append_sheet(wb, ws, 'players'); + xlsx.writeFile(wb, 'players-with-wrong-ranking.xlsx'); + + this.logger.verbose(`Done`); + } +} diff --git a/apps/worker/belgium/flanders/places/src/version.json b/apps/worker/belgium/flanders/places/src/version.json index 18abab1efe..81b8b6803b 100644 --- a/apps/worker/belgium/flanders/places/src/version.json +++ b/apps/worker/belgium/flanders/places/src/version.json @@ -1,3 +1,3 @@ { - "version": "6.166.3" + "version": "6.168.3" } \ No newline at end of file diff --git a/apps/worker/belgium/flanders/points/src/version.json b/apps/worker/belgium/flanders/points/src/version.json index 18abab1efe..81b8b6803b 100644 --- a/apps/worker/belgium/flanders/points/src/version.json +++ b/apps/worker/belgium/flanders/points/src/version.json @@ -1,3 +1,3 @@ { - "version": "6.166.3" + "version": "6.168.3" } \ No newline at end of file diff --git a/apps/worker/ranking/src/version.json b/apps/worker/ranking/src/version.json index 18abab1efe..81b8b6803b 100644 --- a/apps/worker/ranking/src/version.json +++ b/apps/worker/ranking/src/version.json @@ -1,3 +1,3 @@ { - "version": "6.166.3" + "version": "6.168.3" } \ No newline at end of file diff --git a/apps/worker/sync/src/app/processors/sync-events/competition-sync/processors/entry.ts b/apps/worker/sync/src/app/processors/sync-events/competition-sync/processors/entry.ts index b4c3d1ecbe..7497f7b346 100644 --- a/apps/worker/sync/src/app/processors/sync-events/competition-sync/processors/entry.ts +++ b/apps/worker/sync/src/app/processors/sync-events/competition-sync/processors/entry.ts @@ -50,7 +50,7 @@ export class CompetitionSyncEntryProcessor extends StepProcessor { transaction: this.transaction, }); - const drawEntries = await draw.getEntries({ + const drawEntries = await draw.getEventEntries({ transaction: this.transaction, }); @@ -110,7 +110,7 @@ export class CompetitionSyncEntryProcessor extends StepProcessor { this._entries.push({ entry, xmlTeamName: item }); } - const entries = await draw.getEntries({ + const entries = await draw.getEventEntries({ transaction: this.transaction, include: [{ model: Team }], }); diff --git a/apps/worker/sync/src/app/processors/sync-ranking/ranking-sync.ts b/apps/worker/sync/src/app/processors/sync-ranking/ranking-sync.ts index 04bc3a3331..3d726c4f2d 100644 --- a/apps/worker/sync/src/app/processors/sync-ranking/ranking-sync.ts +++ b/apps/worker/sync/src/app/processors/sync-ranking/ranking-sync.ts @@ -1,7 +1,7 @@ import { Player, RankingLastPlace, RankingPlace, RankingSystem } from '@badman/backend-database'; import { Sync } from '@badman/backend-queue'; import { VisualService } from '@badman/backend-visual'; -import { RankingSystems } from '@badman/utils'; +import { RankingSystems, Ranking, Gender } from '@badman/utils'; import { Logger } from '@nestjs/common'; import { Queue } from 'bull'; import { XMLParser } from 'fast-xml-parser'; @@ -164,7 +164,7 @@ export class RankingSyncer { date: momentDate, } as VisualPublication; }); - pubs = pubs?.sort((a, b) => a.date.diff(b.date)); + pubs = pubs?.slice()?.sort((a, b) => a.date.diff(b.date)); // get latest publication const last = pubs?.slice(-1)?.[0]; @@ -186,7 +186,7 @@ export class RankingSyncer { return { visiblePublications: pubs, hiddenPublications: publications - ?.filter((publication) => publication.Visible == false) + ?.filter((publication) => !publication.Visible) ?.map((publication) => { const momentDate = moment(publication.PublicationDate, 'YYYY-MM-DD'); return momentDate; @@ -213,8 +213,8 @@ export class RankingSyncer { category: string | null, places: Map, newPlayers: Map, - type: 'single' | 'double' | 'mix', - gender: 'M' | 'F', + type: Ranking, + gender: Gender, ) => { if (!category) { this.logger.error(`No category defined?`); @@ -304,7 +304,7 @@ export class RankingSyncer { // Check if other publication has create the ranking place if (places.has(foundPlayer.id)) { - const place = places.get(foundPlayer.id) as RankingPlace; + const place = places.get(foundPlayer.id); place[type] = points.Level; place[`${type}Points`] = points.Totalpoints; @@ -329,7 +329,7 @@ export class RankingSyncer { const place = foundPlayer.rankingLastPlaces.find( (r) => r.systemId === ranking.system.id, ); - if (place != null && place[type] != null && place[type] !== points.Level) { + if (place?.[type] != null && place[type] !== points.Level) { place[type] = points.Level; await place.save({ transaction: args.transaction }); } @@ -346,7 +346,7 @@ export class RankingSyncer { (!ranking.stopDate || publication.date.isBefore(ranking.stopDate)) ) { if (publication.usedForUpdate) { - this.logger.log(`Updating ranking on ${publication.date}`); + this.logger.log(`Updating ranking on ${publication.date.format('LLL')}`); } this.logger.debug(`Getting single levels for ${publication.date.format('LLL')}`); @@ -505,7 +505,7 @@ export class RankingSyncer { // For now we only check if it's the last update - const lastPublication = visiblePublications?.sort( + const lastPublication = visiblePublications.slice()?.sort( (a, b) => b.date.valueOf() - a.date.valueOf(), )?.[0]; diff --git a/apps/worker/sync/src/app/utils/correctWrongPlayers.ts b/apps/worker/sync/src/app/utils/correctWrongPlayers.ts index 70aeba9fe5..cd57858f80 100644 --- a/apps/worker/sync/src/app/utils/correctWrongPlayers.ts +++ b/apps/worker/sync/src/app/utils/correctWrongPlayers.ts @@ -1,10 +1,12 @@ +import { Gender } from '@badman/utils'; + export const correctWrongPlayers = (player: { id?: string; memberId?: string; firstName?: string; lastName?: string; birthDate?: Date; - gender?: 'M' | 'F'; + gender?: Gender; club?: number; }): { id?: string; @@ -12,7 +14,7 @@ export const correctWrongPlayers = (player: { firstName?: string; lastName?: string; birthDate?: Date; - gender?: 'M' | 'F'; + gender?: Gender; club?: number; } => { // Yaro Van Delsen @@ -5260,14 +5262,6 @@ export const correctWrongPlayers = (player: { }; } - // // Fixme - // if (player.memberId === '') { - // return { - // ...player, - // memberId: 'Fixme' - // }; - // } - // return player; }; diff --git a/apps/worker/sync/src/version.json b/apps/worker/sync/src/version.json index 18abab1efe..81b8b6803b 100644 --- a/apps/worker/sync/src/version.json +++ b/apps/worker/sync/src/version.json @@ -1,3 +1,3 @@ { - "version": "6.166.3" + "version": "6.168.3" } \ No newline at end of file diff --git a/badman.babel b/badman.babel index 2c132dee1a..00bacf86af 100644 --- a/badman.babel +++ b/badman.babel @@ -440,6 +440,25 @@ + + re-sync + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + save @@ -10255,6 +10274,106 @@ + + ical + + + click + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + copied + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + description + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + linked + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + team-season + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + menu @@ -10374,6 +10493,87 @@ + + re-calculate + + + calculate + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + from + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + title + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + to + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + regular @@ -10778,64 +10978,45 @@ - date - - - - - en-US - true - - - fr-BE - true - - - nl-BE - true - - - - - disclaimer + countsForDowngrade en-US - true + false fr-BE - true + false nl-BE - true + false - downgrade + countsForUpgrade en-US - true + false fr-BE - true + false nl-BE - true + false - drops-next-period + date @@ -10854,7 +11035,7 @@ - evolution + disclaimer @@ -10873,7 +11054,7 @@ - hint + downgrade-average @@ -10892,7 +11073,7 @@ - ignored + drops-next-period @@ -10911,7 +11092,7 @@ - includeOutOfScope + evolution @@ -10930,68 +11111,491 @@ - lostGames + export - none + avgDowngrade en-US - true + false fr-BE - true + false nl-BE - true + false - one + avgUpgrade en-US - true + false fr-BE - true + false nl-BE - true + false - other + countsFor en-US - true + false fr-BE - true + false nl-BE - true + false - - - + + date + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + LOST_DOWNGRADE + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + LOST_IGNORED + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + LOST_UPGRADE + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + opponent1 + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + opponent2 + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + OUT_SCOPE + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + player1 + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + player2 + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + points + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + title + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + usedForDowngrade + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + usedForUpgrade + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + WON + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + + + games-downgrade + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + games-upgrade + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + hint + + + + + en-US + true + + + fr-BE + true + + + nl-BE + true + + + + + ignored + + + + + en-US + true + + + fr-BE + true + + + nl-BE + true + + + + + includeOutOfScope + + + + + en-US + true + + + fr-BE + true + + + nl-BE + true + + + + + lostGames + + + none + + + + + en-US + true + + + fr-BE + true + + + nl-BE + true + + + + + one + + + + + en-US + true + + + fr-BE + true + + + nl-BE + true + + + + + other + + + + + en-US + true + + + fr-BE + true + + + nl-BE + true + + + + + + no-tean @@ -11129,6 +11733,63 @@ + + outOfScopeDowngrade + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + outOfScopeUpgrade + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + + + outOfScopeWonGames + + + + + en-US + false + + + fr-BE + false + + + nl-BE + false + + + period @@ -11406,7 +12067,7 @@ - upgrade + upgrade-average @@ -11425,7 +12086,7 @@ - usedForDowngrade + used-for-downgrade @@ -11444,7 +12105,7 @@ - usedForUpgrade + used-for-upgrade diff --git a/bun.lockb b/bun.lockb index 5a7ffef3ff..72e721d115 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/database/migrations/20240811122406-option to resync points.js b/database/migrations/20240811122406-option to resync points.js new file mode 100644 index 0000000000..e348dd7d20 --- /dev/null +++ b/database/migrations/20240811122406-option to resync points.js @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + up: async (queryInterface) => { + return queryInterface.sequelize.transaction(async (t) => { + try { + await queryInterface.bulkInsert( + { + tableName: 'Claims', + schema: 'security', + }, + [ + { + name: 're-sync:points', + description: 'Allow re-syncing points', + category: 'points', + type: 'global', + createdAt: new Date(), + updatedAt: new Date(), + }, + ], + { transaction: t }, + ); + } catch (err) { + console.error('We errored with', err?.message ?? err); + t.rollback(); + } + }); + }, + + down: async (queryInterface) => { + return queryInterface.sequelize.transaction(async (t) => { + try { + // remove enlist-any-event:team claim + await queryInterface.sequelize.query( + `DELETE FROM "security"."Claims" WHERE name = 're-sync:points'`, + { transaction: t }, + ); + } catch (err) { + console.error('We errored with', err); + t.rollback(); + } + }); + }, +}; diff --git a/libs/backend/authorization/package.json b/libs/backend/authorization/package.json index f4612dd185..fc27d3b8e8 100644 --- a/libs/backend/authorization/package.json +++ b/libs/backend/authorization/package.json @@ -1,8 +1,8 @@ { "name": "@badman/backend-authorization", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { - "@badman/backend-database": "6.166.3", + "@badman/backend-database": "6.168.3", "@nestjs/common": "^10.3.8", "@nestjs/config": "^3.2.2", "@nestjs/core": "^10.3.8", diff --git a/libs/backend/belgium/flanders/games/package.json b/libs/backend/belgium/flanders/games/package.json index 9f0933d3d4..0a7e49ab3c 100644 --- a/libs/backend/belgium/flanders/games/package.json +++ b/libs/backend/belgium/flanders/games/package.json @@ -1,6 +1,6 @@ { "name": "@badman/belgium-flanders-games", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "tslib": "^2.3.0", "@nestjs/common": "^10.3.1" diff --git a/libs/backend/belgium/flanders/places/package.json b/libs/backend/belgium/flanders/places/package.json index 38716d68c0..b0db4d2b84 100644 --- a/libs/backend/belgium/flanders/places/package.json +++ b/libs/backend/belgium/flanders/places/package.json @@ -1,10 +1,10 @@ { "name": "@badman/belgium-flanders-places", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "tslib": "^2.3.0", - "@badman/backend-database": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-database": "6.168.3", + "@badman/utils": "6.168.3", "@nestjs/common": "^10.3.10", "moment": "^2.30.1", "sequelize": "^6.37.3" diff --git a/libs/backend/belgium/flanders/points/package.json b/libs/backend/belgium/flanders/points/package.json index eed41e5c6d..8900650ff1 100644 --- a/libs/backend/belgium/flanders/points/package.json +++ b/libs/backend/belgium/flanders/points/package.json @@ -1,10 +1,10 @@ { "name": "@badman/belgium-flanders-points", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "tslib": "^2.3.0", - "@badman/backend-database": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-database": "6.168.3", + "@badman/utils": "6.168.3", "@nestjs/common": "^10.3.10", "sequelize": "^6.37.3" }, diff --git a/libs/backend/belgium/flanders/points/src/services/belgium-flanders-points.service.ts b/libs/backend/belgium/flanders/points/src/services/belgium-flanders-points.service.ts index eb63f22558..23dd01c71a 100644 --- a/libs/backend/belgium/flanders/points/src/services/belgium-flanders-points.service.ts +++ b/libs/backend/belgium/flanders/points/src/services/belgium-flanders-points.service.ts @@ -6,7 +6,7 @@ import { RankingPoint, RankingSystem, } from '@badman/backend-database'; -import { GameType, getRankingProtected } from '@badman/utils'; +import { GameStatus, GameType, getRankingProtected, Ranking } from '@badman/utils'; import { Injectable } from '@nestjs/common'; import { Op, Transaction } from 'sequelize'; @@ -28,7 +28,10 @@ export class BelgiumFlandersPointsService { } // ignore WO's - if ((game.set1Team1 ?? null) === null && (game.set1Team2 ?? null) === null) { + if ( + game.status == GameStatus.WALKOVER || + ((game.set1Team1 ?? null) === null && (game.set1Team2 ?? null) === null) + ) { return []; } @@ -207,7 +210,7 @@ export class BelgiumFlandersPointsService { system, ); - let pointsFrom: 'single' | 'mix' | 'double' | undefined = undefined; + let pointsFrom: Ranking | undefined = undefined; switch (game.gameType) { case GameType.S: @@ -253,32 +256,28 @@ export class BelgiumFlandersPointsService { // Store the difference in levels points.differenceInLevel = levelP1T2 - levelP1T1; } - } else { - if (game.winner === 1) { - const wonPoints = Math.round( - (this._getWinningPoints(system, levelP1T2) + this._getWinningPoints(system, levelP2T2)) / - 2, - ); - points.player1Team1Points = wonPoints; - points.player2Team1Points = wonPoints; - points.player1Team2Points = 0; - points.player2Team2Points = 0; + } else if (game.winner === 1) { + const wonPoints = Math.round( + (this._getWinningPoints(system, levelP1T2) + this._getWinningPoints(system, levelP2T2)) / 2, + ); + points.player1Team1Points = wonPoints; + points.player2Team1Points = wonPoints; + points.player1Team2Points = 0; + points.player2Team2Points = 0; - // Store the difference in levels - points.differenceInLevel = (levelP1T1 + levelP2T1 - (levelP1T2 + levelP2T2)) / 2; - } else { - const wonPoints = Math.round( - (this._getWinningPoints(system, levelP1T1) + this._getWinningPoints(system, levelP2T1)) / - 2, - ); - points.player1Team2Points = wonPoints; - points.player2Team2Points = wonPoints; - points.player1Team1Points = 0; - points.player2Team1Points = 0; + // Store the difference in levels + points.differenceInLevel = (levelP1T1 + levelP2T1 - (levelP1T2 + levelP2T2)) / 2; + } else { + const wonPoints = Math.round( + (this._getWinningPoints(system, levelP1T1) + this._getWinningPoints(system, levelP2T1)) / 2, + ); + points.player1Team2Points = wonPoints; + points.player2Team2Points = wonPoints; + points.player1Team1Points = 0; + points.player2Team1Points = 0; - // Store the difference in levels - points.differenceInLevel = (levelP1T2 + levelP2T2 - (levelP1T1 + levelP2T1)) / 2; - } + // Store the difference in levels + points.differenceInLevel = (levelP1T2 + levelP2T2 - (levelP1T1 + levelP2T1)) / 2; } return points; diff --git a/libs/backend/cache/package.json b/libs/backend/cache/package.json index 89a131eeb1..74a2100353 100644 --- a/libs/backend/cache/package.json +++ b/libs/backend/cache/package.json @@ -1,9 +1,9 @@ { "name": "@badman/backend-cache", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@swc/helpers": "~0.5.2", - "@badman/utils": "6.166.3", + "@badman/utils": "6.168.3", "@nestjs/common": "^10.3.10", "@nestjs/cache-manager": "^2.2.2", "@nestjs/config": "^3.2.3", diff --git a/libs/backend/cluster/package.json b/libs/backend/cluster/package.json index a4b34fc5d8..8006e43722 100644 --- a/libs/backend/cluster/package.json +++ b/libs/backend/cluster/package.json @@ -1,6 +1,6 @@ { "name": "@badman/backend-cluster", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@nestjs/common": "^10.3.8", "@swc/helpers": "^0.5.11" diff --git a/libs/backend/competition/assembly/package.json b/libs/backend/competition/assembly/package.json index 1eff92cdba..05ef849123 100644 --- a/libs/backend/competition/assembly/package.json +++ b/libs/backend/competition/assembly/package.json @@ -1,11 +1,11 @@ { "name": "@badman/backend-assembly", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { - "@badman/backend-compile": "6.166.3", - "@badman/backend-database": "6.166.3", - "@badman/utils": "6.166.3", - "@badman/backend-authorization": "6.166.3", + "@badman/backend-compile": "6.168.3", + "@badman/backend-database": "6.168.3", + "@badman/utils": "6.168.3", + "@badman/backend-authorization": "6.168.3", "@nestjs/common": "^10.3.8", "@nestjs/config": "^3.2.2", "fastify": "^4.27.0", @@ -17,7 +17,7 @@ "moment": "^2.30.1", "sequelize": "^6.37.3", "tslib": "^2.6.2", - "@badman/backend-validation": "6.166.3" + "@badman/backend-validation": "6.168.3" }, "type": "commonjs", "main": "./src/index.js", diff --git a/libs/backend/competition/assembly/src/controllers/pdf.controller.ts b/libs/backend/competition/assembly/src/controllers/pdf.controller.ts index 12b639fda3..d4de03d09f 100644 --- a/libs/backend/competition/assembly/src/controllers/pdf.controller.ts +++ b/libs/backend/competition/assembly/src/controllers/pdf.controller.ts @@ -1,6 +1,7 @@ +import { User } from '@badman/backend-authorization'; import { CompileService } from '@badman/backend-compile'; import { Logging, Player, RankingLastPlace, Team } from '@badman/backend-database'; -import { I18nTranslations, LoggingAction, gameLabel } from '@badman/utils'; +import { I18nTranslations, LoggingAction, SubEventTypeEnum, gameLabel } from '@badman/utils'; import { Controller, Logger, Post, Req, Res, StreamableFile } from '@nestjs/common'; import { FastifyReply, FastifyRequest } from 'fastify'; import { readFile } from 'fs/promises'; @@ -9,7 +10,6 @@ import { I18nService } from 'nestjs-i18n'; import { lastValueFrom, take } from 'rxjs'; import { AssemblyValidationData, AssemblyValidationError } from '../models'; import { AssemblyValidationService } from '../services'; -import { User } from '@badman/backend-authorization'; type gameType = | 'single1' @@ -123,7 +123,7 @@ export class AssemblyController { const date = moment(data.encounter?.date).tz('Europe/Brussels').format('DD-MM-YYYY HH:mm'); this.logger.debug( - `Generating assembly for ${homeTeam.name} vs ${awayTeam?.name || 'empty'} on ${date}`, + `Generating assembly for ${homeTeam.name} vs ${awayTeam?.name ?? 'empty'} on ${date}`, ); const indexed: string[] = []; @@ -246,7 +246,11 @@ export class AssemblyController { private getLabels(data: AssemblyValidationData): string[] { const labels: string[] = []; for (let i = 0; i < 8; i++) { - const gameLabels = gameLabel(data.subEvent?.eventType as 'M' | 'F' | 'MX', i + 1); + if (!data.subEvent?.eventType) { + continue; + } + + const gameLabels = gameLabel(data.subEvent?.eventType, i + 1); let labelMessage = ''; for (const label of gameLabels) { diff --git a/libs/backend/competition/assembly/src/services/validate/assembly.service.spec.ts b/libs/backend/competition/assembly/src/services/validate/assembly.service.spec.ts index c48584ac36..e8ad41cd40 100644 --- a/libs/backend/competition/assembly/src/services/validate/assembly.service.spec.ts +++ b/libs/backend/competition/assembly/src/services/validate/assembly.service.spec.ts @@ -23,7 +23,7 @@ import { ConfigModule } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { Sequelize } from 'sequelize-typescript'; -import { RankingSystems, SubEventTypeEnum, TeamMembershipType } from '@badman/utils'; +import { LevelType, RankingSystems, SubEventTypeEnum, TeamMembershipType } from '@badman/utils'; import { AssemblyValidationError } from '../../models'; import { AssemblyValidationService } from './assembly.service'; import { @@ -84,14 +84,13 @@ describe('AssemblyValidationService', () => { const drawBuilder = DrawCompetitionBuilder.Create().WithName('Test draw'); - const subEventBuilder = SubEventCompetitionBuilder.Create(SubEventTypeEnum.MX) - .WithName('Test SubEvent') + const subEventBuilder = SubEventCompetitionBuilder.Create(SubEventTypeEnum.MX, 'Test SubEvent') .WithIndex(53, 70) .WitnMaxLevel(6); const encounterBuilder = EncounterCompetitionBuilder.Create(); - event = await EventCompetitionBuilder.Create() + event = await EventCompetitionBuilder.Create(LevelType.PROV) .WithYear(2020) .WithUsedRanking({ amount: 4, unit: 'months' }) .WithName('Test Event') diff --git a/libs/backend/competition/assembly/src/services/validate/assembly.service.ts b/libs/backend/competition/assembly/src/services/validate/assembly.service.ts index 9e55bdc606..18020d5e6d 100644 --- a/libs/backend/competition/assembly/src/services/validate/assembly.service.ts +++ b/libs/backend/competition/assembly/src/services/validate/assembly.service.ts @@ -194,6 +194,10 @@ export class AssemblyValidationService extends ValidationService< ?.filter((m) => m.teamId !== args.teamId) ?.map((m) => m.meta) ?? []) as MetaEntry[]; + if (!event.usedRankingUnit || !event.usedRankingAmount) { + throw new Error('EventCompetition usedRankingUnit is not set'); + } + const year = event?.season; const usedRankingDate = moment(); usedRankingDate.set('year', year); diff --git a/libs/backend/competition/assembly/src/services/validate/rules/player-min-level.rule.ts b/libs/backend/competition/assembly/src/services/validate/rules/player-min-level.rule.ts index 8d243c4381..098fe84047 100644 --- a/libs/backend/competition/assembly/src/services/validate/rules/player-min-level.rule.ts +++ b/libs/backend/competition/assembly/src/services/validate/rules/player-min-level.rule.ts @@ -1,12 +1,12 @@ import { Player } from '@badman/backend-database'; -import { SubEventTypeEnum } from '@badman/utils'; +import { Ranking, SubEventTypeEnum } from '@badman/utils'; import { AssemblyOutput, AssemblyValidationData, AssemblyValidationError } from '../../../models'; import { Rule } from './_rule.base'; export type PlayerMinLevelRuleParams = { player: Partial & { ranking: number }; minLevel: number; - rankingType: 'single' | 'double' | 'mix'; + rankingType: Ranking; }; /** diff --git a/libs/backend/competition/change-encounter/package.json b/libs/backend/competition/change-encounter/package.json index 7f38fd6cc8..e6b3299358 100644 --- a/libs/backend/competition/change-encounter/package.json +++ b/libs/backend/competition/change-encounter/package.json @@ -1,9 +1,9 @@ { "name": "@badman/backend-change-encounter", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { - "@badman/backend-database": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-database": "6.168.3", + "@badman/utils": "6.168.3", "@nestjs/common": "^10.3.8", "@nestjs/config": "^3.2.2", "@nestjs/graphql": "^12.1.1", @@ -12,7 +12,7 @@ "sequelize": "^6.37.3", "tslib": "^2.6.2", "moment-timezone": "^0.5.45", - "@badman/backend-validation": "6.166.3" + "@badman/backend-validation": "6.168.3" }, "type": "commonjs", "main": "./src/index.js", diff --git a/libs/backend/competition/change-encounter/src/models/assembly.model.ts b/libs/backend/competition/change-encounter/src/models/change-encounter.model.ts similarity index 80% rename from libs/backend/competition/change-encounter/src/models/assembly.model.ts rename to libs/backend/competition/change-encounter/src/models/change-encounter.model.ts index f0df69e101..ec59116f86 100644 --- a/libs/backend/competition/change-encounter/src/models/assembly.model.ts +++ b/libs/backend/competition/change-encounter/src/models/change-encounter.model.ts @@ -10,8 +10,17 @@ export class ChangeEncounterInput { @Field(() => ID, { nullable: true }) workingencounterId?: string; - @Field(() => [Date], { nullable: true }) - suggestedDates?: Date[]; + @Field(() => [Suggestions], { nullable: true }) + suggestedDates?: Suggestions[]; +} + +@InputType() +export class Suggestions { + @Field(() => Date) + date!: Date; + + @Field(() => ID) + locationId!: string; } @ObjectType() @@ -39,5 +48,8 @@ export class ChangeEncounterValidationData { locations!: Location[]; lowestYear!: number; workingencounterId?: string; - suggestedDates?: Date[]; + suggestedDates?: { + date: Date; + locationId: string; + }[]; } diff --git a/libs/backend/competition/change-encounter/src/models/index.ts b/libs/backend/competition/change-encounter/src/models/index.ts index d28809e7d2..55aeb03731 100644 --- a/libs/backend/competition/change-encounter/src/models/index.ts +++ b/libs/backend/competition/change-encounter/src/models/index.ts @@ -1,4 +1,4 @@ // start:ng42.barrel -export * from './assembly.model'; +export * from './change-encounter.model'; export * from './error.model'; // end:ng42.barrel diff --git a/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.spec.ts b/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.spec.ts index d09b8e8fcc..3a70e9348f 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.spec.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.spec.ts @@ -14,7 +14,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Sequelize } from 'sequelize-typescript'; import { ChangeEncounterValidationService } from './change-encounter.service'; -import { SubEventTypeEnum } from '@badman/utils'; +import { LevelType, SubEventTypeEnum } from '@badman/utils'; describe('ChangeEncounterValidationService', () => { let service: ChangeEncounterValidationService; @@ -43,14 +43,13 @@ describe('ChangeEncounterValidationService', () => { const drawBuilder = DrawCompetitionBuilder.Create().WithName('Test draw'); - const subEventBuilder = SubEventCompetitionBuilder.Create(SubEventTypeEnum.MX) - .WithName('Test SubEvent') + const subEventBuilder = SubEventCompetitionBuilder.Create(SubEventTypeEnum.MX, 'Test SubEvent') .WithIndex(53, 70) .WitnMaxLevel(6); const encounterBuilder = EncounterCompetitionBuilder.Create(); - event = await EventCompetitionBuilder.Create() + event = await EventCompetitionBuilder.Create(LevelType.PROV) .WithYear(2020) .WithUsedRanking({ amount: 4, unit: 'months' }) .WithName('Test Event') diff --git a/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.ts b/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.ts index 4835c77e96..37460f9d56 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/change-encounter.service.ts @@ -1,7 +1,7 @@ import { EncounterCompetition, Location, Team } from '@badman/backend-database'; import { ValidationService } from '@badman/backend-validation'; import { Logger } from '@nestjs/common'; -import { Op } from 'sequelize'; +import { Op, WhereOptions } from 'sequelize'; import { ChangeEncounterOutput, ChangeEncounterValidationData, @@ -33,7 +33,10 @@ export class ChangeEncounterValidationService extends ValidationService< override async fetchData(args: { teamId: string; workingencounterId?: string; - suggestedDates?: Date[]; + suggestedDates?: { + date: Date; + locationId: string; + }[]; }): Promise { const team = await Team.findByPk(args.teamId, { attributes: ['id', 'name', 'type', 'teamNumber', 'clubId', 'season'], @@ -45,7 +48,7 @@ export class ChangeEncounterValidationService extends ValidationService< // get encounters for the team const encounters = await EncounterCompetition.findAll({ - attributes: ['id', 'date', 'drawId', 'locationId'], + attributes: ['id', 'date', 'drawId', 'locationId', 'homeTeamId'], where: { [Op.or]: [ { @@ -69,10 +72,24 @@ export class ChangeEncounterValidationService extends ValidationService< ], }); - const lowestYear = Math.min(...encounters.map((r) => r.date?.getFullYear() || 0)); + const lowestYear = Math.min(...encounters.map((r) => r.date?.getFullYear() ?? 0)); const encountersSem1 = encounters.filter((r) => r.date?.getFullYear() === lowestYear); const encountersSem2 = encounters.filter((r) => r.date?.getFullYear() !== lowestYear); + // find the home clubId + const currentGame = encounters.find((r) => r.id == args.workingencounterId); + + let homeTeam: Team | null = null; + if (currentGame) { + if (currentGame?.homeTeamId !== team.id) { + homeTeam = await Team.findByPk(currentGame?.homeTeamId, { + attributes: ['id', 'clubId'], + }); + } else { + homeTeam = team; + } + } + if (encountersSem1.length === 0 || encountersSem2.length === 0) { throw new Error('Not enough encounters'); } @@ -93,11 +110,19 @@ export class ChangeEncounterValidationService extends ValidationService< ], }); + const loactionFinder: WhereOptions = [ + { id: encounters.map((r) => r.locationId)?.filter((r) => !!r) as string[] }, + ]; + if (homeTeam) { + loactionFinder.push({ clubId: homeTeam.clubId }); + } + const locations = await Location.findAll({ attributes: ['id', 'name'], where: { - id: encounters.map((r) => r.locationId)?.filter((r) => !!r) as string[], + [Op.or]: loactionFinder, }, + logging: console.log, include: [ { association: 'availabilities', @@ -129,7 +154,14 @@ export class ChangeEncounterValidationService extends ValidationService< * @returns Whether the ChangeEncounter is valid or not */ override async validate( - args: { teamId: string; workingencounterId?: string; suggestedDates?: Date[] }, + args: { + teamId: string; + workingencounterId?: string; + suggestedDates?: { + date: Date; + locationId: string; + }[]; + }, runFor?: { playerId?: string; teamId?: string; clubId?: string }, ) { const data = await super.validate(args, runFor); diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/dates-in-period.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/dates-in-period.ts index 688c60080c..e7a9afafc2 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/dates-in-period.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/dates-in-period.ts @@ -40,7 +40,7 @@ export class DatePeriodRule extends Rule { // if we have suggested dates for the working encounter, we need to check if that date would give a warning if (suggestedDates && changeEncounter.workingencounterId) { const warns = suggestedDates.map((suggestedDate) => - this.isBetween(suggestedDate, [...period], changeEncounter.workingencounterId), + this.isBetween(suggestedDate.date, [...period], changeEncounter.workingencounterId), ); for (const warn of warns) { diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/exceptions.rule.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/exceptions.rule.ts index a9e86eacac..b4734be267 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/exceptions.rule.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/exceptions.rule.ts @@ -53,7 +53,7 @@ export class ExceptionRule extends Rule { } as EncounterCompetition; for (const suggestedDate of suggestedDates) { - encounter.date = suggestedDate; + encounter.date = suggestedDate.date; const warning = this.findEncountersOnExceptionDays([encounter], infoEvents); warnings.push(...warning); } diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/location-free.rule.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/location-free.rule.ts index c95ad0a139..db1a8abb82 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/location-free.rule.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/location-free.rule.ts @@ -48,12 +48,14 @@ export class LocationRule extends Rule { } as EncounterCompetition; for (const suggestedDate of suggestedDates) { - if (moment(suggestedDate).isSame(encounter.date, 'minute')) { + if (moment(suggestedDate.date).isSame(encounter.date, 'minute')) { // if the suggested date is the same as the current date, we can skip this continue; } - encounter.date = suggestedDate; + encounter.date = suggestedDate.date; + encounter.locationId = suggestedDate.locationId; + const warning = await this.checkifLocationIsFree([encounter], locations, true); warnings.push(...warning); } diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/semester.rule.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/semester.rule.ts index 4973a27c69..204f109db6 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/semester.rule.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/semester.rule.ts @@ -64,9 +64,9 @@ export class SemesterRule extends Rule { } as EncounterCompetition; for (const suggestedDate of suggestedDates) { - const suggestedSemester1 = suggestedDate.getFullYear() === lowestYear; + const suggestedSemester1 = suggestedDate.date.getFullYear() === lowestYear; - encounter.date = suggestedDate; + encounter.date = suggestedDate.date const encountersSemester = suggestedSemester1 ? encountersSemester1 : encountersSemester2; const warns = this.findEncountersInSameSemester( [...encountersSemester, encounter], @@ -78,7 +78,7 @@ export class SemesterRule extends Rule { message: 'all.competition.change-encounter.errors.same-semester', params: { encounterId: warn.id, - date: suggestedDate, + date: suggestedDate.date, }, }); }); diff --git a/libs/backend/competition/change-encounter/src/services/validate/rules/team-club.rule.ts b/libs/backend/competition/change-encounter/src/services/validate/rules/team-club.rule.ts index 07373b5b10..6746cec7cf 100644 --- a/libs/backend/competition/change-encounter/src/services/validate/rules/team-club.rule.ts +++ b/libs/backend/competition/change-encounter/src/services/validate/rules/team-club.rule.ts @@ -63,9 +63,9 @@ export class TeamClubRule extends Rule { } as EncounterCompetition; for (const suggestedDate of suggestedDates) { - const suggestedSemester1 = suggestedDate.getFullYear() === lowestYear; + const suggestedSemester1 = suggestedDate.date.getFullYear() === lowestYear; - encounter.date = suggestedDate; + encounter.date = suggestedDate.date const encountersSemester = suggestedSemester1 ? encountersSemester1 : encountersSemester2; const warns = this.findIfSameClubIsFirst([...encountersSemester, encounter], team.id); @@ -74,7 +74,7 @@ export class TeamClubRule extends Rule { message: 'all.competition.change-encounter.errors.same-club', params: { encounterId: warn.id, - date: suggestedDate, + date: suggestedDate.date }, }); }); @@ -103,7 +103,7 @@ export class TeamClubRule extends Rule { const errors = []; let differentClubPassed = 0; - for (const enc of encounters.sort( + for (const enc of encounters.slice().sort( (a, b) => (a.date?.getTime() ?? 0) - (b.date?.getTime() ?? 0), )) { const otherClub = enc.home?.id == currentTeamId ? enc.away?.clubId : enc.home?.clubId; diff --git a/libs/backend/competition/enrollment/package.json b/libs/backend/competition/enrollment/package.json index 3cca262ea8..2c9c3372eb 100644 --- a/libs/backend/competition/enrollment/package.json +++ b/libs/backend/competition/enrollment/package.json @@ -1,12 +1,12 @@ { "name": "@badman/backend-enrollment", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "tslib": "^2.3.0", "@nestjs/common": "^10.3.1", "fastify": "^4.26.0", - "@badman/backend-database": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-database": "6.168.3", + "@badman/utils": "6.168.3", "@nestjs/graphql": "^12.2.0", "@nestjs/config": "^3.2.3", "graphql-type-json": "^0.3.2", diff --git a/libs/backend/competition/enrollment/src/services/excel.services.ts b/libs/backend/competition/enrollment/src/services/excel.services.ts index 6f9ea294f4..77432db972 100644 --- a/libs/backend/competition/enrollment/src/services/excel.services.ts +++ b/libs/backend/competition/enrollment/src/services/excel.services.ts @@ -36,7 +36,7 @@ export class ExcelService { const draws = await subEvent?.getDrawCompetitions(); for (const draw of draws ?? []) { - const entries = await draw?.getEntries({ + const entries = await draw?.getEventEntries({ include: [{ model: Team }], order: [['team', 'name', 'ASC']], }); diff --git a/libs/backend/competition/enrollment/src/services/validate/enrollment.service.ts b/libs/backend/competition/enrollment/src/services/validate/enrollment.service.ts index ce45ee9562..ceaf16dc0a 100644 --- a/libs/backend/competition/enrollment/src/services/validate/enrollment.service.ts +++ b/libs/backend/competition/enrollment/src/services/validate/enrollment.service.ts @@ -94,7 +94,7 @@ export class EnrollmentValidationService { const subEvents = await SubEventCompetition.findAll({ where: { - id: teams.map((e) => e.subEventId), + id: teams.map((e) => e.subEventId)?.filter((e) => !!e) as string[], }, include: [ { diff --git a/libs/backend/competition/transfer-loans/package.json b/libs/backend/competition/transfer-loans/package.json index 35aac625cb..9248a5d48e 100644 --- a/libs/backend/competition/transfer-loans/package.json +++ b/libs/backend/competition/transfer-loans/package.json @@ -1,15 +1,15 @@ { "name": "@badman/backend-transfer-loan", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@nestjs/common": "^10.3.8", "fastify": "^4.27.0", "tslib": "^2.6.2", "multer": "1.4.5-lts.1", - "@badman/backend-utils": "6.166.3", + "@badman/backend-utils": "6.168.3", "@fastify/multipart": "^8.3.0", - "@badman/backend-database": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-database": "6.168.3", + "@badman/utils": "6.168.3", "moment": "^2.30.1", "sequelize": "^6.37.3" }, diff --git a/libs/backend/compile/package.json b/libs/backend/compile/package.json index d7f45d5c3d..a8428fa173 100644 --- a/libs/backend/compile/package.json +++ b/libs/backend/compile/package.json @@ -1,6 +1,6 @@ { "name": "@badman/backend-compile", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@swc/helpers": "~0.5.2", "@nestjs/common": "^10.3.1", @@ -8,8 +8,8 @@ "juice": "^10.0.0", "rxjs": "~7.8.1", "puppeteer": "^22.0.0", - "@badman/backend-pupeteer": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-pupeteer": "6.168.3", + "@badman/utils": "6.168.3", "consolidate": "^1.0.3", "moment-timezone": "^0.5.45", "nestjs-i18n": "^10.4.5" diff --git a/libs/backend/database/package.json b/libs/backend/database/package.json index 24ea4d3b98..82901f3567 100644 --- a/libs/backend/database/package.json +++ b/libs/backend/database/package.json @@ -1,12 +1,12 @@ { "name": "@badman/backend-database", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@swc/helpers": "~0.5.2", "@nestjs/graphql": "^12.0.11", - "@badman/utils": "6.166.3", - "@badman/backend-cache": "6.166.3", - "@badman/backend-queue": "6.166.3", + "@badman/utils": "6.168.3", + "@badman/backend-cache": "6.168.3", + "@badman/backend-queue": "6.168.3", "sequelize-typescript": "^2.1.6", "sequelize": "^6.35.2", "geojson": "^0.5.0", diff --git a/libs/backend/database/src/_testing/clubBuilder.ts b/libs/backend/database/src/_testing/clubBuilder.ts index fbcd22c235..11f425fc4b 100644 --- a/libs/backend/database/src/_testing/clubBuilder.ts +++ b/libs/backend/database/src/_testing/clubBuilder.ts @@ -16,24 +16,24 @@ export class ClubBuilder { return new ClubBuilder(id); } - WithName(name: string): ClubBuilder { + WithName(name: string): this { this.club.name = name; return this; } - WithTeamName(name: string): ClubBuilder { + WithTeamName(name: string): this { this.club.teamName = name; return this; } - WithId(id: string): ClubBuilder { + WithId(id: string): this { this.club.id = id; return this; } - WithTeam(team: TeamBuilder): ClubBuilder { + WithTeam(team: TeamBuilder): this { team.ForClub(this.club); this.teams.push(team); return this; diff --git a/libs/backend/database/src/_testing/eventCompetitionBuilder.ts b/libs/backend/database/src/_testing/eventCompetitionBuilder.ts index 85dc855ea9..bb835f506b 100644 --- a/libs/backend/database/src/_testing/eventCompetitionBuilder.ts +++ b/libs/backend/database/src/_testing/eventCompetitionBuilder.ts @@ -1,4 +1,4 @@ -import { UsedRankingTiming } from '@badman/utils'; +import { LevelType, UsedRankingTiming } from '@badman/utils'; import { EventCompetition } from '../models'; import { SubEventCompetitionBuilder } from './eventCompetitionSubEventBuilder'; @@ -9,42 +9,58 @@ export class EventCompetitionBuilder { private subEvents: SubEventCompetitionBuilder[] = []; - constructor(id?: string) { + constructor( + type: LevelType, + official = true, + season = 2022, + usedRanking?: UsedRankingTiming, + id?: string, + ) { this.event = new EventCompetition({ id, + type, + official, + usedRanking: usedRanking ?? { amount: 0, unit: 'days' }, + season, }); } - static Create(id?: string): EventCompetitionBuilder { - return new EventCompetitionBuilder(id); + static Create( + type: LevelType, + official = true, + season = 2022, + usedRanking?: UsedRankingTiming, + id?: string, + ): EventCompetitionBuilder { + return new EventCompetitionBuilder(type, official, season, usedRanking, id); } - WithName(name: string): EventCompetitionBuilder { + WithName(name: string): this { this.event.name = name; return this; } - WithYear(year: number): EventCompetitionBuilder { + WithYear(year: number): this { this.event.season = year; return this; } - WithOfficial(official: boolean): EventCompetitionBuilder { + WithOfficial(official: boolean): this { this.event.official = official; return this; } - WithUsedRanking(usedRanking: UsedRankingTiming): EventCompetitionBuilder { + WithUsedRanking(usedRanking: UsedRankingTiming): this { this.event.usedRankingAmount = usedRanking.amount; this.event.usedRankingUnit = usedRanking.unit; return this; } - WithSubEvent(subEvent: SubEventCompetitionBuilder): EventCompetitionBuilder { + WithSubEvent(subEvent: SubEventCompetitionBuilder): this { this.subEvents.push(subEvent); return this; } diff --git a/libs/backend/database/src/_testing/eventCompetitionSubEventBuilder.ts b/libs/backend/database/src/_testing/eventCompetitionSubEventBuilder.ts index 26da76f095..6e559d67e4 100644 --- a/libs/backend/database/src/_testing/eventCompetitionSubEventBuilder.ts +++ b/libs/backend/database/src/_testing/eventCompetitionSubEventBuilder.ts @@ -13,44 +13,41 @@ export class SubEventCompetitionBuilder { private draws: DrawCompetitionBuilder[] = []; private entries: EventCompetitionEntryBuilder[] = []; - constructor(type: SubEventTypeEnum) { + constructor(type: SubEventTypeEnum, name?: string, eventId?: string) { this.subEvent = new SubEventCompetition({ eventType: type, + name: name ?? 'SubEvent', + eventId: eventId ?? '123', + levelWithModifier: 1, }); } - static Create(type: SubEventTypeEnum): SubEventCompetitionBuilder { - return new SubEventCompetitionBuilder(type); + static Create(type: SubEventTypeEnum, name?: string, eventId?: string) { + return new SubEventCompetitionBuilder(type, name, eventId); } - WithName(firstName: string): SubEventCompetitionBuilder { - this.subEvent.name = firstName; - + WithId(id: string): this { + this.subEvent.id = id; return this; } - WithId(id: string): SubEventCompetitionBuilder { - this.subEvent.id = id; + WithEventId(eventId: string): this { + this.subEvent.eventId = eventId; return this; } - WithIndex(minBaseIndex: number, maxBaseIndex: number): SubEventCompetitionBuilder { + WithIndex(minBaseIndex: number, maxBaseIndex: number): this { this.subEvent.minBaseIndex = minBaseIndex; this.subEvent.maxBaseIndex = maxBaseIndex; return this; } - WitnMaxLevel(maxLevel: number): SubEventCompetitionBuilder { + WitnMaxLevel(maxLevel: number): this { this.subEvent.maxLevel = maxLevel; return this; } - WithEventId(eventId: string): SubEventCompetitionBuilder { - this.subEvent.eventId = eventId; - return this; - } - - ForEvent(event: EventCompetitionBuilder): SubEventCompetitionBuilder { + ForEvent(event: EventCompetitionBuilder): this { event.WithSubEvent(this); return this; } @@ -60,12 +57,12 @@ export class SubEventCompetitionBuilder { return this; } - WithDraw(draw: DrawCompetitionBuilder): SubEventCompetitionBuilder { + WithDraw(draw: DrawCompetitionBuilder): this { this.draws.push(draw); return this; } - WithEntry(entry: EventCompetitionEntryBuilder): SubEventCompetitionBuilder { + WithEntry(entry: EventCompetitionEntryBuilder): this { this.entries.push(entry); return this; } diff --git a/libs/backend/database/src/_testing/load-test.ts b/libs/backend/database/src/_testing/load-test.ts index ff4fd5cbc8..68bb6f93ee 100644 --- a/libs/backend/database/src/_testing/load-test.ts +++ b/libs/backend/database/src/_testing/load-test.ts @@ -4,6 +4,7 @@ import { SubEventTypeEnum, TeamMembershipType, getSeason, + LevelType, } from '@badman/utils'; import { ClubBuilder } from './clubBuilder'; import { EventCompetitionBuilder } from './eventCompetitionBuilder'; @@ -114,7 +115,7 @@ export async function loadTest() { logger.log('Done building test data'); } catch (error) { console.error(error); - throw 'Loading test data failed'; + throw new Error('Loading test data failed'); } } @@ -138,26 +139,22 @@ function addEncounters(season: number) { const drawM = DrawCompetitionBuilder.Create().WithName('Test draw M'); const drawF = DrawCompetitionBuilder.Create().WithName('Test draw F'); - const subEventMX = SubEventCompetitionBuilder.Create(SubEventTypeEnum.MX) - .WithName('Test SubEvent') + const subEventMX = SubEventCompetitionBuilder.Create(SubEventTypeEnum.MX, 'Test SubEvent') .WithIndex(53, 70) .WitnMaxLevel(6); const subEventM = SubEventCompetitionBuilder.Create(SubEventTypeEnum.M) - .WithName('Test SubEvent') .WithIndex(53, 70) .WitnMaxLevel(6); const subEventF = SubEventCompetitionBuilder.Create(SubEventTypeEnum.F) - .WithName('Test SubEvent') .WithIndex(53, 70) .WitnMaxLevel(6); - const event = EventCompetitionBuilder.Create() - .WithYear(season) - .WithOfficial(true) - .WithUsedRanking({ amount: 4, unit: 'months' }) - .WithName('Test Event'); + const event = EventCompetitionBuilder.Create(LevelType.PROV, true, season, { + amount: 4, + unit: 'months', + }).WithName('Test Event'); event.WithSubEvent(subEventMX); event.WithSubEvent(subEventM); diff --git a/libs/backend/database/src/models/event/competition/draw-competition.model.ts b/libs/backend/database/src/models/event/competition/draw-competition.model.ts index dcec8a48d7..a463e80394 100644 --- a/libs/backend/database/src/models/event/competition/draw-competition.model.ts +++ b/libs/backend/database/src/models/event/competition/draw-competition.model.ts @@ -108,7 +108,7 @@ export class DrawCompetition extends Model { entryType: 'competition', }, }) - entries?: Relation; + eventEntries?: Relation; // Belongs to SubEvent getSubEventCompetition!: BelongsToGetAssociationMixin; @@ -125,16 +125,16 @@ export class DrawCompetition extends Model { hasEncounterCompetitions!: HasManyHasAssociationsMixin; countEncounterCompetitions!: HasManyCountAssociationsMixin; - // Has many Entries - getEntries!: HasManyGetAssociationsMixin; - setEntries!: HasManySetAssociationsMixin; - addEntries!: HasManyAddAssociationsMixin; - addEntry!: HasManyAddAssociationMixin; - removeEntries!: HasManyRemoveAssociationMixin; - removeEntry!: HasManyRemoveAssociationsMixin; - hasEntries!: HasManyHasAssociationMixin; - hasEntry!: HasManyHasAssociationsMixin; - countEntries!: HasManyCountAssociationsMixin; + // Has many EventEntrie + getEventEntries!: HasManyGetAssociationsMixin; + setEventEntries!: HasManySetAssociationsMixin; + addEventEntries!: HasManyAddAssociationsMixin; + addEventEntry!: HasManyAddAssociationMixin; + removeEventEntry!: HasManyRemoveAssociationMixin; + removeEventEntries!: HasManyRemoveAssociationsMixin; + hasEventEntry!: HasManyHasAssociationMixin; + hasEventEntries!: HasManyHasAssociationsMixin; + countEventEntries!: HasManyCountAssociationsMixin; } @InputType() diff --git a/libs/backend/database/src/models/event/competition/event-competition.model.ts b/libs/backend/database/src/models/event/competition/event-competition.model.ts index b2af2ff84c..6b110a4c83 100644 --- a/libs/backend/database/src/models/event/competition/event-competition.model.ts +++ b/libs/backend/database/src/models/event/competition/event-competition.model.ts @@ -1,6 +1,7 @@ import { LevelType, UsedRankingTiming } from '@badman/utils'; import { Field, ID, InputType, Int, ObjectType, OmitType, PartialType } from '@nestjs/graphql'; import { + CreationOptional, HasManyAddAssociationMixin, HasManyAddAssociationsMixin, HasManyCountAssociationsMixin, @@ -10,6 +11,8 @@ import { HasManyRemoveAssociationMixin, HasManyRemoveAssociationsMixin, HasManySetAssociationsMixin, + InferAttributes, + InferCreationAttributes, } from 'sequelize'; import { Column, @@ -43,13 +46,16 @@ import { SubEventCompetition, SubEventCompetitionUpdateInput } from './sub-event schema: 'event', } as TableOptions) @ObjectType({ description: 'A EventCompetition' }) -export class EventCompetition extends Model { +export class EventCompetition extends Model< + InferAttributes, + InferCreationAttributes +> { @Field(() => ID) @Default(DataType.UUIDV4) @IsUUID(4) @PrimaryKey @Column(DataType.UUIDV4) - override id!: string; + declare id: CreationOptional; @Field(() => Date, { nullable: true }) override updatedAt?: Date; @@ -148,13 +154,20 @@ export class EventCompetition extends Model { @Field(() => Int) @Column(DataType.NUMBER) - usedRankingAmount!: number; + usedRankingAmount?: number; @Field(() => String) @Column(DataType.ENUM('months', 'weeks', 'days')) - usedRankingUnit!: 'months' | 'weeks' | 'days'; + usedRankingUnit?: 'months' | 'weeks' | 'days'; get usedRanking(): UsedRankingTiming { + if (!this.usedRankingAmount || !this.usedRankingUnit) { + return { + amount: 0, + unit: 'days', + }; + } + return { amount: this.usedRankingAmount, unit: this.usedRankingUnit, diff --git a/libs/backend/database/src/models/event/competition/sub-event-competition.model.ts b/libs/backend/database/src/models/event/competition/sub-event-competition.model.ts index 7e1dcb2bdf..fccd260391 100644 --- a/libs/backend/database/src/models/event/competition/sub-event-competition.model.ts +++ b/libs/backend/database/src/models/event/competition/sub-event-competition.model.ts @@ -1,3 +1,4 @@ +import { LevelType, SubEventTypeEnum } from '@badman/utils'; import { Field, Float, @@ -20,7 +21,7 @@ import { BelongsToManyRemoveAssociationsMixin, BelongsToManySetAssociationsMixin, BelongsToSetAssociationMixin, - BuildOptions, + CreationOptional, HasManyAddAssociationMixin, HasManyAddAssociationsMixin, HasManyCountAssociationsMixin, @@ -30,6 +31,8 @@ import { HasManyRemoveAssociationMixin, HasManyRemoveAssociationsMixin, HasManySetAssociationsMixin, + InferAttributes, + InferCreationAttributes } from 'sequelize'; import { BelongsTo, @@ -45,30 +48,27 @@ import { Table, Unique, } from 'sequelize-typescript'; -import { LevelType, SubEventTypeEnum } from '@badman/utils'; +import { Relation } from '../../../wrapper'; import { RankingGroup } from '../../ranking'; import { EventEntry } from '../entry.model'; import { DrawCompetition } from './draw-competition.model'; import { EventCompetition } from './event-competition.model'; import { RankingGroupSubEventCompetitionMembership } from './group-subevent-membership.model'; -import { Relation } from '../../../wrapper'; - @Table({ timestamps: true, schema: 'event', }) @ObjectType({ description: 'A SubEventCompetition' }) -export class SubEventCompetition extends Model { - constructor(values?: Partial, options?: BuildOptions) { - super(values, options); - } - +export class SubEventCompetition extends Model< + InferAttributes, + InferCreationAttributes +> { + @Field(() => ID) @Default(DataType.UUIDV4) @IsUUID(4) @PrimaryKey - @Field(() => ID) @Column(DataType.UUIDV4) - override id!: string; + declare id: CreationOptional; @Field(() => Date, { nullable: true }) override updatedAt?: Date; @@ -98,9 +98,9 @@ export class SubEventCompetition extends Model { * If the event competition type is LIGA, the modifier is 100. * If the event competition type is NATIONAL, the modifier is 1. * The level is multiplied by the modifier to get the final level with modifier. - * + * * Developer note: This is now allowing 100 levels for each type of competition. if we need more levels we can add more zeros to the modifier. - * + * * @throws {Error} If the EventCompetition is not set. * @returns {number} The level of the sub-event competition with a modifier. */ @@ -170,7 +170,7 @@ export class SubEventCompetition extends Model { @Unique('SubEventCompetitions_unique_constraint') @Field(() => ID) @Column(DataType.UUIDV4) - visualCode!: string; + visualCode?: string; // Belongs to many Group getRankingGroups!: BelongsToManyGetAssociationsMixin; diff --git a/libs/backend/database/src/models/event/entry.model.ts b/libs/backend/database/src/models/event/entry.model.ts index 4812658d8a..4d2833bd96 100644 --- a/libs/backend/database/src/models/event/entry.model.ts +++ b/libs/backend/database/src/models/event/entry.model.ts @@ -1,9 +1,10 @@ import { getIndexFromPlayers } from '@badman/utils'; -import { NotFoundException } from '@nestjs/common'; +import { Logger, NotFoundException } from '@nestjs/common'; import { Field, ID, InputType, Int, ObjectType, OmitType, PartialType } from '@nestjs/graphql'; import moment from 'moment'; import { BelongsToGetAssociationMixin, + BelongsToGetAssociationMixinOptions, BelongsToSetAssociationMixin, CreationOptional, HasOneGetAssociationMixin, @@ -11,7 +12,7 @@ import { InferAttributes, InferCreationAttributes, Op, - SaveOptions + SaveOptions, } from 'sequelize'; import { BeforeCreate, @@ -156,11 +157,16 @@ export class EventEntry extends Model< getPlayer2!: BelongsToGetAssociationMixin; setPlayer2!: BelongsToSetAssociationMixin; - getPlayers() { + getPlayers(options?: BelongsToGetAssociationMixinOptions) { + if (!this.player1Id && !this.player2Id) { + Logger.warn("No id's set?"); + return Promise.resolve([]); + } + if (this.player1Id && this.player2Id) { - return Promise.all([this.getPlayer1(), this.getPlayer2()]); + return Promise.all([this.getPlayer1(options), this.getPlayer2(options)]); } else { - return Promise.all([this.getPlayer1()]); + return Promise.all([this.getPlayer1(options)]); } } @@ -225,6 +231,13 @@ export class EventEntry extends Model< return; } + if ( + !dbSubEvent.eventCompetition.usedRankingUnit || + !dbSubEvent.eventCompetition.usedRankingAmount + ) { + throw new Error('EventCompetition usedRanking is not set'); + } + const usedRankingDate = moment(); usedRankingDate.set('year', dbSubEvent.eventCompetition.season); usedRankingDate.set( diff --git a/libs/backend/generator/package.json b/libs/backend/generator/package.json index ccfecaa069..e10427c3de 100644 --- a/libs/backend/generator/package.json +++ b/libs/backend/generator/package.json @@ -1,11 +1,11 @@ { "name": "@badman/backend-generator", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { - "@badman/backend-enrollment": "6.166.3", - "@badman/backend-translate": "6.166.3", - "@badman/backend-database": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-enrollment": "6.168.3", + "@badman/backend-translate": "6.168.3", + "@badman/backend-database": "6.168.3", + "@badman/utils": "6.168.3", "@nestjs/common": "^10.3.8", "@nestjs/config": "^3.2.2", "moment": "^2.30.1", diff --git a/libs/backend/graphql/package.json b/libs/backend/graphql/package.json index 617830d14e..54241be75a 100644 --- a/libs/backend/graphql/package.json +++ b/libs/backend/graphql/package.json @@ -1,25 +1,25 @@ { "name": "@badman/backend-graphql", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@swc/helpers": "~0.5.2", "@nestjs/graphql": "^12.0.11", - "@badman/backend-authorization": "6.166.3", + "@badman/backend-authorization": "6.168.3", "@nestjs/apollo": "^12.0.11", "@nestjs/common": "^10.3.1", "@apollo/server-plugin-operation-registry": "^4.0.1", "@nestjs/config": "^3.1.1", - "@badman/utils": "6.166.3", - "@badman/backend-database": "6.166.3", + "@badman/utils": "6.168.3", + "@badman/backend-database": "6.168.3", "sequelize-typescript": "^2.1.6", - "@badman/backend-notifications": "6.166.3", + "@badman/backend-notifications": "6.168.3", "sequelize": "^6.35.2", "cron": "3.1.7", - "@badman/backend-assembly": "6.166.3", - "@badman/backend-cache": "6.166.3", - "@badman/backend-enrollment": "6.166.3", - "@badman/backend-queue": "6.166.3", - "@badman/backend-ranking": "6.166.3", + "@badman/backend-assembly": "6.168.3", + "@badman/backend-cache": "6.168.3", + "@badman/backend-enrollment": "6.168.3", + "@badman/backend-queue": "6.168.3", + "@badman/backend-ranking": "6.168.3", "moment-timezone": "^0.5.44", "@nestjs/bull": "^10.0.1", "bull": "^4.12.2", @@ -31,8 +31,8 @@ "class-validator": "^0.14.1", "graphql-type-json": "^0.3.2", "@apollo/server": "^4.10.4", - "@badman/backend-change-encounter": "6.166.3", - "@badman/backend-orchestrator": "6.166.3" + "@badman/backend-change-encounter": "6.168.3", + "@badman/backend-orchestrator": "6.168.3" }, "type": "commonjs", "main": "./src/index.js", diff --git a/libs/backend/graphql/src/resolvers/availability/availability.resolver.ts b/libs/backend/graphql/src/resolvers/availability/availability.resolver.ts index 2c9bf64ccc..b1b19d88ab 100644 --- a/libs/backend/graphql/src/resolvers/availability/availability.resolver.ts +++ b/libs/backend/graphql/src/resolvers/availability/availability.resolver.ts @@ -6,7 +6,7 @@ import { AvailiblyDayType, ExceptionType, Location, - Player + Player, } from '@badman/backend-database'; import { Logger, NotFoundException, UnauthorizedException } from '@nestjs/common'; import { Args, ID, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; @@ -35,8 +35,8 @@ export class AvailabilitysResolver { async days(@Parent() availability: Availability) { return availability.days?.map((day) => ({ ...day, - from: day.from ? new Date(day.from as Date) : undefined, - to: day.to ? new Date(day.to as Date) : undefined, + from: day.from ? new Date(day.from) : undefined, + to: day.to ? new Date(day.to) : undefined, })); } @@ -44,7 +44,7 @@ export class AvailabilitysResolver { async exceptions(@Parent() availability: Availability) { // return availability.exceptions and map the start en end as date return availability.exceptions - ?.filter((exception) => exception && exception.start && exception.end) + ?.filter((exception) => exception?.start && exception?.end) ?.map((exception) => ({ ...exception, start: new Date(exception.start as Date), @@ -128,7 +128,6 @@ export class AvailabilitysResolver { { transaction }, ); - // await dbLocation.update(location, { transaction }); await transaction.commit(); return dbAvailability; } catch (e) { diff --git a/libs/backend/graphql/src/resolvers/event/competition/draw.resolver.ts b/libs/backend/graphql/src/resolvers/event/competition/draw.resolver.ts index 94437d7eab..2e28d5b5b5 100644 --- a/libs/backend/graphql/src/resolvers/event/competition/draw.resolver.ts +++ b/libs/backend/graphql/src/resolvers/event/competition/draw.resolver.ts @@ -3,7 +3,9 @@ import { DrawCompetitionUpdateInput, EncounterCompetition, EventEntry, + Game, Player, + RankingSystem, Standing, SubEventCompetition, } from '@badman/backend-database'; @@ -13,12 +15,16 @@ import { ListArgs } from '../../../utils'; import { User } from '@badman/backend-authorization'; import { Sequelize } from 'sequelize-typescript'; import { sortStanding } from '@badman/utils'; +import { PointsService } from '@badman/backend-ranking'; @Resolver(() => DrawCompetition) export class DrawCompetitionResolver { private readonly logger = new Logger(DrawCompetitionResolver.name); - constructor(private _sequelize: Sequelize) {} + constructor( + private _sequelize: Sequelize, + private _pointService: PointsService, + ) {} @Query(() => DrawCompetition) async drawCompetition(@Args('id', { type: () => ID }) id: string): Promise { @@ -42,7 +48,7 @@ export class DrawCompetitionResolver { @ResolveField(() => [EventEntry]) async eventEntries(@Parent() draw: DrawCompetition): Promise { - return draw.getEntries(); + return draw.getEventEntries(); } @ResolveField(() => [EncounterCompetition]) @@ -76,7 +82,7 @@ export class DrawCompetitionResolver { drawCompetitionDb.risers !== updateDrawCompetitionData.risers || drawCompetitionDb.fallers !== updateDrawCompetitionData.fallers ) { - const entries = await drawCompetitionDb.getEntries({ transaction }); + const entries = await drawCompetitionDb.getEventEntries({ transaction }); const standings: Standing[] = []; for (const entry of entries) { const standing = await entry.getStanding({ transaction }); @@ -131,16 +137,63 @@ export class DrawCompetitionResolver { } } - // @Mutation(returns => DrawCompetition) - // async addDrawCompetition( - // @Args('newDrawCompetitionData') newDrawCompetitionData: NewDrawCompetitionInput, - // ): Promise { - // const recipe = await this.recipesService.create(newDrawCompetitionData); - // return recipe; - // } - - // @Mutation(returns => Boolean) - // async removeDrawCompetition(@Args('id') id: string) { - // return this.recipesService.remove(id); - // } + @Mutation(() => Boolean) + async recalculateDrawCompetitionRankingPoints( + @User() user: Player, + @Args('drawId', { type: () => ID }) drawId: string, + @Args('systemId', { type: () => ID, nullable: true }) systemId: string, + ): Promise { + if (!(await user.hasAnyPermission(['re-sync:points']))) { + throw new UnauthorizedException(`You do not have permission to sync points`); + } + + // Do transaction + const transaction = await this._sequelize.transaction(); + try { + const where = systemId ? { id: systemId } : { primary: true }; + const system = await RankingSystem.findOne({ + where, + }); + + if (!system) { + throw new NotFoundException(`${RankingSystem.name} not found for ${systemId || 'primary'}`); + } + + // find all games + const draw = await DrawCompetition.findByPk(drawId, { + transaction, + }); + + if (!draw) { + throw new NotFoundException(`${DrawCompetition.name} not found for ${drawId}`); + } + + const encounters = await draw.getEncounterCompetitions({ + transaction, + include: [{ model: Game }], + }); + + const games = encounters.reduce((acc, enc) => { + acc.push(...(enc.games ?? [])); + return acc; + }, [] as Game[]); + + for (const game of games ?? []) { + await this._pointService.createRankingPointforGame(system, game, { + transaction, + }); + } + + this.logger.log(`Recalculated ${games.length} ranking points for draw ${drawId}`); + + // Commit transaction + await transaction.commit(); + + return true; + } catch (error) { + this.logger.error(error); + await transaction.rollback(); + throw error; + } + } } diff --git a/libs/backend/graphql/src/resolvers/event/competition/encounter.resolver.ts b/libs/backend/graphql/src/resolvers/event/competition/encounter.resolver.ts index ebc4edcebb..55d23c3cf5 100644 --- a/libs/backend/graphql/src/resolvers/event/competition/encounter.resolver.ts +++ b/libs/backend/graphql/src/resolvers/event/competition/encounter.resolver.ts @@ -8,6 +8,7 @@ import { Game, Location, Player, + RankingSystem, Team, } from '@badman/backend-database'; import { Logger, NotFoundException, UnauthorizedException } from '@nestjs/common'; @@ -27,6 +28,8 @@ import { ListArgs } from '../../../utils'; import { InjectQueue } from '@nestjs/bull'; import { Sync, SyncQueue } from '@badman/backend-queue'; import { Queue } from 'bull'; +import { Sequelize } from 'sequelize-typescript'; +import { PointsService } from '@badman/backend-ranking'; @ObjectType() export class PagedEncounterCompetition { @@ -41,7 +44,11 @@ export class PagedEncounterCompetition { export class EncounterCompetitionResolver { private readonly logger = new Logger(EncounterCompetitionResolver.name); - constructor(@InjectQueue(SyncQueue) private syncQueue: Queue) {} + constructor( + @InjectQueue(SyncQueue) private syncQueue: Queue, + private _sequelize: Sequelize, + private _pointService: PointsService, + ) {} @Query(() => EncounterCompetition) async encounterCompetition( @@ -183,4 +190,56 @@ export class EncounterCompetitionResolver { return true; } + + @Mutation(() => Boolean) + async recalculateEncounterCompetitionRankingPoints( + @User() user: Player, + @Args('encounterId', { type: () => ID }) encounterId: string, + @Args('systemId', { type: () => ID, nullable: true }) systemId: string, + ): Promise { + if (!(await user.hasAnyPermission(['re-sync:points']))) { + throw new UnauthorizedException(`You do not have permission to sync points`); + } + + // Do transaction + const transaction = await this._sequelize.transaction(); + try { + const where = systemId ? { id: systemId } : { primary: true }; + const system = await RankingSystem.findOne({ + where, + }); + + if (!system) { + throw new NotFoundException(`${RankingSystem.name} not found for ${systemId || 'primary'}`); + } + + // find all games + const enc = await EncounterCompetition.findByPk(encounterId, { + transaction, + }); + + if (!enc) { + throw new NotFoundException(`${EncounterCompetition.name} not found for ${encounterId}`); + } + + const games = await enc.getGames({ transaction }); + + for (const game of games) { + await this._pointService.createRankingPointforGame(system, game, { + transaction, + }); + } + + this.logger.log(`Recalculated ${games.length} ranking points for encounter ${encounterId}`); + + // Commit transaction + await transaction.commit(); + + return true; + } catch (error) { + this.logger.error(error); + await transaction.rollback(); + throw error; + } + } } diff --git a/libs/backend/graphql/src/resolvers/event/competition/event.resolver.ts b/libs/backend/graphql/src/resolvers/event/competition/event.resolver.ts index b1a9f287b5..ea90a5ac17 100644 --- a/libs/backend/graphql/src/resolvers/event/competition/event.resolver.ts +++ b/libs/backend/graphql/src/resolvers/event/competition/event.resolver.ts @@ -1,18 +1,18 @@ import { User } from '@badman/backend-authorization'; import { - ExceptionType, Comment, DrawCompetition, EncounterCompetition, EventCompetition, EventCompetitionUpdateInput, + ExceptionType, Game, + InfoEventType, Player, RankingGroup, RankingPoint, RankingSystem, SubEventCompetition, - InfoEventType, } from '@badman/backend-database'; import { PointsService, StartVisualRankingDate } from '@badman/backend-ranking'; import { IsUUID } from '@badman/utils'; @@ -102,7 +102,7 @@ export class EventCompetitionResolver { async exceptions(@Parent() event: EventCompetition) { // return availability.exceptions and map the start en end as date return event.exceptions - ?.filter((exception) => exception && exception.start && exception.end) + ?.filter((exception) => exception?.start && exception?.end) ?.map((exception) => ({ ...exception, start: new Date(exception.start as Date), @@ -114,7 +114,7 @@ export class EventCompetitionResolver { async infoEvents(@Parent() event: EventCompetition) { // return availability.exceptions and map the start en end as date return event.infoEvents - ?.filter((info) => info && info.start && info.end) + ?.filter((info) => info?.start && info?.end) ?.map((info) => ({ ...info, start: new Date(info.start as Date), @@ -165,7 +165,7 @@ export class EventCompetitionResolver { transaction, }); - if (updateEventCompetitionData.official == true) { + if (updateEventCompetitionData.official) { this.logger.debug(`Adding ranking groups and points`); for (const subEvent of subEvents) { await subEvent.setRankingGroups(groups, { @@ -466,4 +466,79 @@ export class EventCompetitionResolver { ); } } + + @Mutation(() => Boolean) + async recalculateEventCompetitionRankingPoints( + @User() user: Player, + @Args('eventId', { type: () => ID }) eventId: string, + @Args('systemId', { type: () => ID, nullable: true }) systemId: string, + ): Promise { + if (!(await user.hasAnyPermission(['re-sync:points']))) { + throw new UnauthorizedException(`You do not have permission to sync points`); + } + + // Do transaction + const transaction = await this._sequelize.transaction(); + try { + const where = systemId ? { id: systemId } : { primary: true }; + const system = await RankingSystem.findOne({ + where, + }); + + if (!system) { + throw new NotFoundException(`${RankingSystem.name} not found for ${systemId || 'primary'}`); + } + + // find all games + const event = await EventCompetition.findByPk(eventId, { + transaction, + }); + + if (!event) { + throw new NotFoundException(`${EventCompetition.name} not found for ${eventId}`); + } + + const subEvents = await event.getSubEventCompetitions({ + transaction, + include: [ + { + model: DrawCompetition, + include: [{ model: EncounterCompetition, include: [{ model: Game }] }], + }, + ], + }); + + const games = subEvents.reduce((acc, subEvent) => { + acc.push( + ...(subEvent.drawCompetitions?.reduce((acc, draw) => { + acc.push( + ...(draw.encounterCompetitions?.reduce((acc, enc) => { + acc.push(...(enc.games ?? [])); + return acc; + }, [] as Game[]) ?? []), + ); + return acc; + }, [] as Game[]) ?? []), + ); + return acc; + }, [] as Game[]); + + for (const game of games ?? []) { + await this._pointService.createRankingPointforGame(system, game, { + transaction, + }); + } + + this.logger.log(`Recalculated ${games.length} ranking points for draw ${eventId}`); + + // Commit transaction + await transaction.commit(); + + return true; + } catch (error) { + this.logger.error(error); + await transaction.rollback(); + throw error; + } + } } diff --git a/libs/backend/graphql/src/resolvers/event/competition/subevent.resolver.ts b/libs/backend/graphql/src/resolvers/event/competition/subevent.resolver.ts index 999672f9d2..3ca7c10b0d 100644 --- a/libs/backend/graphql/src/resolvers/event/competition/subevent.resolver.ts +++ b/libs/backend/graphql/src/resolvers/event/competition/subevent.resolver.ts @@ -1,26 +1,37 @@ +import { User } from '@badman/backend-authorization'; +import { CACHE_TTL } from '@badman/backend-cache'; import { DrawCompetition, + EncounterCompetition, EventCompetition, EventEntry, + Game, + Player, RankingGroup, RankingLastPlace, RankingSystem, SubEventCompetition, SubEventCompetitionAverageLevel, } from '@badman/backend-database'; +import { PointsService } from '@badman/backend-ranking'; import { SubEventTypeEnum } from '@badman/utils'; -import { Inject, Logger, NotFoundException } from '@nestjs/common'; -import { Args, ID, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; -import { ListArgs } from '../../../utils'; -import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; -import { CACHE_TTL } from '@badman/backend-cache'; +import { Inject, Logger, NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { Args, ID, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { Cache } from 'cache-manager'; +import { Sequelize } from 'sequelize-typescript'; +import { ListArgs } from '../../../utils'; @Resolver(() => SubEventCompetition) export class SubEventCompetitionResolver { private readonly logger = new Logger(SubEventCompetitionResolver.name); - constructor(@Inject(CACHE_MANAGER) private readonly _cacheManager: Cache) {} + constructor( + @Inject(CACHE_MANAGER) private readonly _cacheManager: Cache, + + private _sequelize: Sequelize, + private _pointService: PointsService, + ) {} @Query(() => SubEventCompetition) async subEventCompetition( @@ -175,7 +186,7 @@ export class SubEventCompetitionResolver { if (!cur?.single) { return acc; } - const count = countPerMale.get(cur.playerId) || 0; + const count = countPerMale.get(cur.playerId) ?? 0; singleMales += count; return acc + cur.single * count; }, 0); @@ -189,7 +200,7 @@ export class SubEventCompetitionResolver { if (!cur?.double) { return acc; } - const count = countPerMale.get(cur.playerId) || 0; + const count = countPerMale.get(cur.playerId) ?? 0; doubleMales += count; return acc + cur.double * count; }, 0); @@ -203,7 +214,7 @@ export class SubEventCompetitionResolver { if (!cur?.mix) { return acc; } - const count = countPerMale.get(cur.playerId) || 0; + const count = countPerMale.get(cur.playerId) ?? 0; mixMales += count; return acc + cur.mix * count; }, 0); @@ -247,7 +258,7 @@ export class SubEventCompetitionResolver { if (!cur?.single) { return acc; } - const count = countPerFemale.get(cur.playerId) || 0; + const count = countPerFemale.get(cur.playerId) ?? 0; singleFemales += count; return acc + cur.single * count; }, 0); @@ -261,7 +272,7 @@ export class SubEventCompetitionResolver { if (!cur?.double) { return acc; } - const count = countPerFemale.get(cur.playerId) || 0; + const count = countPerFemale.get(cur.playerId) ?? 0; doubleFemales += count; return acc + cur.double * count; }, 0); @@ -274,7 +285,7 @@ export class SubEventCompetitionResolver { if (!cur?.mix) { return acc; } - const count = countPerFemale.get(cur.playerId) || 0; + const count = countPerFemale.get(cur.playerId) ?? 0; mixFemales += count; return acc + cur.mix * count; }, 0); @@ -289,4 +300,69 @@ export class SubEventCompetitionResolver { mixCount: mixFemales, } as SubEventCompetitionAverageLevel; } + + @Mutation(() => Boolean) + async recalculateSubEventCompetitionRankingPoints( + @User() user: Player, + @Args('subEventId', { type: () => ID }) subEventId: string, + @Args('systemId', { type: () => ID, nullable: true }) systemId: string, + ): Promise { + if (!(await user.hasAnyPermission(['re-sync:points']))) { + throw new UnauthorizedException(`You do not have permission to sync points`); + } + + // Do transaction + const transaction = await this._sequelize.transaction(); + try { + const where = systemId ? { id: systemId } : { primary: true }; + const system = await RankingSystem.findOne({ + where, + }); + + if (!system) { + throw new NotFoundException(`${RankingSystem.name} not found for ${systemId || 'primary'}`); + } + + // find all games + const subEvent = await SubEventCompetition.findByPk(subEventId, { + transaction, + }); + + if (!subEvent) { + throw new NotFoundException(`${SubEventCompetition.name} not found for ${subEventId}`); + } + + const draws = await subEvent.getDrawCompetitions({ + transaction, + include: [{ model: EncounterCompetition, include: [{ model: Game }] }], + }); + + const games = draws.reduce((acc, draw) => { + acc.push( + ...(draw.encounterCompetitions ?? []).reduce( + (acc, enc) => acc.concat(enc.games ?? []), + [] as Game[], + ), + ); + return acc; + }, [] as Game[]); + + for (const game of games ?? []) { + await this._pointService.createRankingPointforGame(system, game, { + transaction, + }); + } + + this.logger.log(`Recalculated ${games.length} ranking points for draw ${subEventId}`); + + // Commit transaction + await transaction.commit(); + + return true; + } catch (error) { + this.logger.error(error); + await transaction.rollback(); + throw error; + } + } } diff --git a/libs/backend/graphql/src/resolvers/event/tournament/draw.resolver.ts b/libs/backend/graphql/src/resolvers/event/tournament/draw.resolver.ts index 8fcea54b15..0bb35cb7f9 100644 --- a/libs/backend/graphql/src/resolvers/event/tournament/draw.resolver.ts +++ b/libs/backend/graphql/src/resolvers/event/tournament/draw.resolver.ts @@ -1,10 +1,27 @@ -import { DrawTournament, EventEntry, Game, SubEventTournament } from '@badman/backend-database'; -import { NotFoundException } from '@nestjs/common'; -import { Args, ID, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { User } from '@badman/backend-authorization'; +import { + DrawTournament, + EventEntry, + Game, + Player, + RankingSystem, + SubEventTournament, +} from '@badman/backend-database'; +import { PointsService } from '@badman/backend-ranking'; +import { Logger, NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { Args, ID, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { Sequelize } from 'sequelize-typescript'; import { ListArgs } from '../../../utils'; @Resolver(() => DrawTournament) export class DrawTournamentResolver { + private readonly logger = new Logger(DrawTournamentResolver.name); + + constructor( + private _sequelize: Sequelize, + private _pointService: PointsService, + ) {} + @Query(() => DrawTournament) async drawTournament(@Args('id', { type: () => ID }) id: string): Promise { const draw = await DrawTournament.findByPk(id); @@ -35,16 +52,55 @@ export class DrawTournamentResolver { return draw.getGames(); } - // @Mutation(returns => DrawTournament) - // async addDrawTournament( - // @Args('newDrawTournamentData') newDrawTournamentData: NewDrawTournamentInput, - // ): Promise { - // const recipe = await this.recipesService.create(newDrawTournamentData); - // return recipe; - // } - - // @Mutation(returns => Boolean) - // async removeDrawTournament(@Args('id') id: string) { - // return this.recipesService.remove(id); - // } + @Mutation(() => Boolean) + async recalculateDrawTournamentRankingPoints( + @User() user: Player, + @Args('drawId', { type: () => ID }) drawId: string, + @Args('systemId', { type: () => ID, nullable: true }) systemId: string, + ): Promise { + if (!(await user.hasAnyPermission(['re-sync:points']))) { + throw new UnauthorizedException(`You do not have permission to sync points`); + } + + // Do transaction + const transaction = await this._sequelize.transaction(); + try { + const where = systemId ? { id: systemId } : { primary: true }; + const system = await RankingSystem.findOne({ + where, + }); + + if (!system) { + throw new NotFoundException(`${RankingSystem.name} not found for ${systemId || 'primary'}`); + } + + // find all games + const enc = await DrawTournament.findByPk(drawId, { + transaction, + }); + + if (!enc) { + throw new NotFoundException(`${DrawTournament.name} not found for ${drawId}`); + } + + const games = await enc.getGames({ transaction }); + + for (const game of games) { + await this._pointService.createRankingPointforGame(system, game, { + transaction, + }); + } + + this.logger.log(`Recalculated ${games.length} ranking points for draw ${drawId}`); + + // Commit transaction + await transaction.commit(); + + return true; + } catch (error) { + this.logger.error(error); + await transaction.rollback(); + throw error; + } + } } diff --git a/libs/backend/graphql/src/resolvers/event/tournament/event.resolver.ts b/libs/backend/graphql/src/resolvers/event/tournament/event.resolver.ts index e8f66376a2..d564b797fd 100644 --- a/libs/backend/graphql/src/resolvers/event/tournament/event.resolver.ts +++ b/libs/backend/graphql/src/resolvers/event/tournament/event.resolver.ts @@ -97,10 +97,6 @@ export class EventTournamentResolver { } if (eventTournamentDb.official !== updateEventTournamentData.official) { - const subEvents = await eventTournamentDb.getSubEventTournaments({ - transaction, - }); - // we are making it official const ranking = await RankingSystem.findOne({ where: { @@ -116,8 +112,11 @@ export class EventTournamentResolver { const groups = await ranking.getRankingGroups({ transaction, }); + const subEvents = await eventTournamentDb.getSubEventTournaments({ + transaction, + }); - if (updateEventTournamentData.official == true) { + if (updateEventTournamentData.official) { for (const subEvent of subEvents) { await subEvent.setRankingGroups(groups, { transaction, @@ -319,4 +318,68 @@ export class EventTournamentResolver { ); } } + + @Mutation(() => Boolean) + async recalculateEventTournamentRankingPoints( + @User() user: Player, + @Args('eventId', { type: () => ID }) eventId: string, + @Args('systemId', { type: () => ID, nullable: true }) systemId: string, + ): Promise { + if (!(await user.hasAnyPermission(['re-sync:points']))) { + throw new UnauthorizedException(`You do not have permission to sync points`); + } + + // Do transaction + const transaction = await this._sequelize.transaction(); + try { + const where = systemId ? { id: systemId } : { primary: true }; + const system = await RankingSystem.findOne({ + where, + }); + + if (!system) { + throw new NotFoundException(`${RankingSystem.name} not found for ${systemId || 'primary'}`); + } + + // find all games + const event = await EventTournament.findByPk(eventId, { + transaction, + }); + + if (!event) { + throw new NotFoundException(`${EventTournament.name} not found for ${eventId}`); + } + + const subevents = await event.getSubEventTournaments({ + transaction, + include: [{ model: DrawTournament, include: [{ model: Game }] }], + }); + const games = subevents.reduce((acc, draw) => { + acc.push( + ...(draw.drawTournaments ?? []).reduce( + (acc, enc) => acc.concat(enc.games ?? []), + [] as Game[], + ), + ); + return acc; + }, [] as Game[]); + + for (const game of games ?? []) { + await this._pointService.createRankingPointforGame(system, game, { + transaction, + }); + } + + this.logger.log(`Recalculated ${games.length} ranking points for draw ${eventId}`); + + // Commit transaction + await transaction.commit(); + + return true; + } catch (error) { + this.logger.error(error); + await transaction.rollback(); + throw error; + } + } } diff --git a/libs/backend/graphql/src/resolvers/event/tournament/subevent.resolver.ts b/libs/backend/graphql/src/resolvers/event/tournament/subevent.resolver.ts index 14eae8b48b..6b7d808f13 100644 --- a/libs/backend/graphql/src/resolvers/event/tournament/subevent.resolver.ts +++ b/libs/backend/graphql/src/resolvers/event/tournament/subevent.resolver.ts @@ -1,15 +1,30 @@ import { DrawTournament, EventTournament, + Game, + Player, RankingGroup, + RankingSystem, SubEventTournament, } from '@badman/backend-database'; -import { NotFoundException } from '@nestjs/common'; -import { Args, ID, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { Logger, NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { Args, ID, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { ListArgs } from '../../../utils'; +import { User } from '@badman/backend-authorization'; +import { PointsService } from '@badman/backend-ranking'; +import { Sequelize } from 'sequelize-typescript'; @Resolver(() => SubEventTournament) export class SubEventTournamentResolver { + + private readonly logger = new Logger(SubEventTournamentResolver.name); + + constructor( + private _sequelize: Sequelize, + private _pointService: PointsService, + ) {} + + @Query(() => SubEventTournament) async subEventTournament( @Args('id', { type: () => ID }) id: string, @@ -45,16 +60,63 @@ export class SubEventTournamentResolver { return subEvent.getRankingGroups(); } - // @Mutation(returns => SubEventTournament) - // async addSubEventTournament( - // @Args('newSubEventTournamentData') newSubEventTournamentData: NewSubEventTournamentInput, - // ): Promise { - // const recipe = await this.recipesService.create(newSubEventTournamentData); - // return recipe; - // } - - // @Mutation(returns => Boolean) - // async removeSubEventTournament(@Args('id') id: string) { - // return this.recipesService.remove(id); - // } + @Mutation(() => Boolean) + async recalculateSubEventTournamentwRankingPoints( + @User() user: Player, + @Args('drawId', { type: () => ID }) drawId: string, + @Args('systemId', { type: () => ID, nullable: true }) systemId: string, + ): Promise { + if (!(await user.hasAnyPermission(['re-sync:points']))) { + throw new UnauthorizedException(`You do not have permission to sync points`); + } + + // Do transaction + const transaction = await this._sequelize.transaction(); + try { + const where = systemId ? { id: systemId } : { primary: true }; + const system = await RankingSystem.findOne({ + where, + }); + + if (!system) { + throw new NotFoundException(`${RankingSystem.name} not found for ${systemId || 'primary'}`); + } + + // find all games + const subEvent = await SubEventTournament.findByPk(drawId, { + transaction, + }); + + if (!subEvent) { + throw new NotFoundException(`${SubEventTournament.name} not found for ${drawId}`); + } + + const draws = await subEvent.getDrawTournaments({ + transaction, + include: [{ model: Game }], + }); + + const games = draws.reduce((acc, draw) => { + acc.push(...(draw.games ?? [])); + return acc; + }, [] as Game[]); + + for (const game of games) { + await this._pointService.createRankingPointforGame(system, game, { + transaction, + }); + } + + this.logger.log(`Recalculated ${games.length} ranking points for draw ${drawId}`); + + // Commit transaction + await transaction.commit(); + + return true; + } catch (error) { + this.logger.error(error); + await transaction.rollback(); + throw error; + } + } } diff --git a/libs/backend/graphql/src/resolvers/player/player.module.ts b/libs/backend/graphql/src/resolvers/player/player.module.ts index 2afbc13685..2afdd3a673 100644 --- a/libs/backend/graphql/src/resolvers/player/player.module.ts +++ b/libs/backend/graphql/src/resolvers/player/player.module.ts @@ -6,9 +6,10 @@ import { PlayersResolver, PlayerTeamResolver, } from './player.resolver'; +import { RankingModule } from '@badman/backend-ranking'; @Module({ - imports: [DatabaseModule], + imports: [DatabaseModule, RankingModule], providers: [PlayersResolver, PlayerClubResolver, GamePlayersResolver, PlayerTeamResolver], }) export class PlayerResolverModule {} diff --git a/libs/backend/graphql/src/resolvers/player/player.resolver.ts b/libs/backend/graphql/src/resolvers/player/player.resolver.ts index 9b779ce3a4..f8e692f87c 100644 --- a/libs/backend/graphql/src/resolvers/player/player.resolver.ts +++ b/libs/backend/graphql/src/resolvers/player/player.resolver.ts @@ -31,12 +31,16 @@ import { Args, ID, Mutation, Parent, Query, ResolveField, Resolver } from '@nest import { Op } from 'sequelize'; import { Sequelize } from 'sequelize-typescript'; import { ListArgs, WhereArgs, queryFixer } from '../../utils'; +import { PointsService } from '@badman/backend-ranking'; @Resolver(() => Player) export class PlayersResolver { protected readonly logger = new Logger(PlayersResolver.name); - constructor(private _sequelize: Sequelize) {} + constructor( + private _sequelize: Sequelize, + private pointService: PointsService, + ) {} @Query(() => Player) async player(@Args('id', { type: () => ID }) id: string): Promise { @@ -341,9 +345,7 @@ export class PlayersResolver { } @Mutation(() => Boolean) - async updateSetting( - @Args('settings') settingsInput: SettingUpdateInput, - ): Promise { + async updateSetting(@Args('settings') settingsInput: SettingUpdateInput): Promise { const user = await Player.findByPk(settingsInput.playerId); if (!user) { throw new UnauthorizedException(); @@ -415,6 +417,63 @@ export class PlayersResolver { return true; } + + @Mutation(() => Boolean) + async recalculatePlayerRankingPoints( + @User() user: Player, + @Args('playerId', { type: () => ID }) playerId: string, + @Args('startDate', { nullable: true }) startDate?: Date, + @Args('endDate', { nullable: true }) endDate?: Date, + @Args('systemId', { nullable: true }) systemId?: string, + ): Promise { + if (!(await user.hasAnyPermission(['re-sync:points']))) { + throw new UnauthorizedException(`You do not have permission to resync points`); + } + + const player = await Player.findByPk(playerId); + if (!player) { + throw new NotFoundException(`${Player.name}: ${playerId}`); + } + + // Do transaction + const transaction = await this._sequelize.transaction(); + try { + const where = systemId ? { id: systemId } : { primary: true }; + const system = await RankingSystem.findOne({ + where, + }); + + if (!system) { + throw new NotFoundException(`No ranking system found for ${systemId || 'primary'}`); + } + + // find all games + const games = await player.getGames({ + where: { + playedAt: { + [Op.between]: [startDate, endDate], + }, + }, + }); + + for (const game of games) { + await this.pointService.createRankingPointforGame(system, game, { + transaction, + }); + } + + this.logger.log(`Recalculated ${games.length} ranking points for player ${playerId}`); + + // Commit transaction + await transaction.commit(); + + return true; + } catch (error) { + this.logger.error(error); + await transaction.rollback(); + throw error; + } + } } @Resolver(() => GamePlayerMembershipType) diff --git a/libs/backend/graphql/src/resolvers/ranking/rankingPoint.resolver.ts b/libs/backend/graphql/src/resolvers/ranking/rankingPoint.resolver.ts index 941316d162..41ad41622e 100644 --- a/libs/backend/graphql/src/resolvers/ranking/rankingPoint.resolver.ts +++ b/libs/backend/graphql/src/resolvers/ranking/rankingPoint.resolver.ts @@ -1,10 +1,20 @@ -import { Player, RankingPoint, RankingSystem } from '@badman/backend-database'; -import { NotFoundException } from '@nestjs/common'; -import { Args, ID, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { Game, Player, RankingPoint, RankingSystem } from '@badman/backend-database'; +import { Logger, NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { Args, ID, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { ListArgs } from '../../utils'; +import { User } from '@badman/backend-authorization'; +import { Sequelize } from 'sequelize-typescript'; +import { PointsService } from '@badman/backend-ranking'; @Resolver(() => RankingPoint) export class RankingPointResolver { + protected readonly logger = new Logger(RankingPointResolver.name); + + constructor( + private _sequelize: Sequelize, + private pointService: PointsService, + ) {} + @Query(() => RankingPoint) async rankingPoint(@Args('id', { type: () => ID }) id: string): Promise { let rankingPoint = await RankingPoint.findByPk(id); @@ -38,16 +48,49 @@ export class RankingPointResolver { return rankingPoint.getSystem(); } - // @Mutation(returns => RankingPoint) - // async RankingPoint( - // @Args('RankingPointData') RankingPointData: RankingPointInput, - // ): Promise { - // const recipe = await this.recipesService.create(RankingPointData); - // return recipe; - // } - - // @Mutation(returns => Boolean) - // async RankingPoint(@Args('id') id: string) { - // return this.recipesService.remove(id); - // } + @Mutation(() => Boolean) + async recalculateRankingPoints( + @User() user: Player, + @Args('gameId', { type: () => [ID] }) gameId: string[], + @Args('systemId', { type: () => ID, nullable: true }) systemId: string, + ): Promise { + if (!(await user.hasAnyPermission(['re-sync:points']))) { + throw new UnauthorizedException(`You do not have permission to sync points`); + } + + // Do transaction + const transaction = await this._sequelize.transaction(); + try { + const where = systemId ? { id: systemId } : { primary: true }; + const system = await RankingSystem.findOne({ + where, + }); + + if (!system) { + throw new NotFoundException(`No ranking system found for ${systemId || 'primary'}`); + } + + // find all games + const games = await Game.findAll({ + where: { + id: gameId, + }, + }); + + for (const game of games) { + await this.pointService.createRankingPointforGame(system, game, { + transaction, + }); + } + + // Commit transaction + await transaction.commit(); + + return true; + } catch (error) { + this.logger.error(error); + await transaction.rollback(); + throw error; + } + } } diff --git a/libs/backend/health/package.json b/libs/backend/health/package.json index 69126bf286..34341c9afc 100644 --- a/libs/backend/health/package.json +++ b/libs/backend/health/package.json @@ -1,6 +1,6 @@ { "name": "@badman/backend-health", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@nestjs/common": "^10.3.8", "@nestjs/terminus": "^10.2.3", diff --git a/libs/backend/logging/package.json b/libs/backend/logging/package.json index f5384146e2..44a803bd31 100644 --- a/libs/backend/logging/package.json +++ b/libs/backend/logging/package.json @@ -1,9 +1,9 @@ { "name": "@badman/backend-logging", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@swc/helpers": "~0.5.2", - "@badman/utils": "6.166.3", + "@badman/utils": "6.168.3", "@logtail/node": "~0.4.21", "@logtail/winston": "^0.4.21", "@nestjs/common": "^10.3.10", diff --git a/libs/backend/mailing/package.json b/libs/backend/mailing/package.json index fe524eee65..a277a87e37 100644 --- a/libs/backend/mailing/package.json +++ b/libs/backend/mailing/package.json @@ -1,10 +1,10 @@ { "name": "@badman/backend-mailing", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { - "@badman/backend-database": "6.166.3", - "@badman/backend-compile": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-database": "6.168.3", + "@badman/backend-compile": "6.168.3", + "@badman/utils": "6.168.3", "@nestjs/common": "^10.3.8", "@nestjs/config": "^3.2.2", "moment-timezone": "^0.5.45", diff --git a/libs/backend/micro/package.json b/libs/backend/micro/package.json index 2a625bcdf9..eeb06b4660 100644 --- a/libs/backend/micro/package.json +++ b/libs/backend/micro/package.json @@ -1,6 +1,6 @@ { "name": "@badman/backend-micro", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@nestjs/common": "^10.3.8", "@nestjs/config": "^3.2.2", diff --git a/libs/backend/notifications/package.json b/libs/backend/notifications/package.json index 345aebd244..35558b2d00 100644 --- a/libs/backend/notifications/package.json +++ b/libs/backend/notifications/package.json @@ -1,14 +1,14 @@ { "name": "@badman/backend-notifications", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { - "@badman/backend-database": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-database": "6.168.3", + "@badman/utils": "6.168.3", "@nestjs/common": "^10.3.8", "@nestjs/config": "^3.2.2", "@swc/helpers": "^0.5.11", - "@badman/backend-mailing": "6.166.3", - "@badman/backend-change-encounter": "6.166.3", + "@badman/backend-mailing": "6.168.3", + "@badman/backend-change-encounter": "6.168.3", "web-push": "^3.6.7", "moment": "^2.30.1", "nestjs-i18n": "^10.4.5" diff --git a/libs/backend/notifications/src/notifiers/notifier.base.ts b/libs/backend/notifications/src/notifiers/notifier.base.ts index ebc8d82c74..d8b2a1f0d0 100644 --- a/libs/backend/notifications/src/notifiers/notifier.base.ts +++ b/libs/backend/notifications/src/notifiers/notifier.base.ts @@ -70,7 +70,7 @@ export abstract class Notifier { }, }); - await Logging.create({ + const logAction = await Logging.create({ action: LoggingAction.SendNotification, playerId: player.id, meta: { linkId, linkType: this.linkType, type: NotificationType[type] }, @@ -84,6 +84,9 @@ export abstract class Notifier { this.allowedInterval } (send on: ${lastSend.format('DD-MM-YYYY HH:mm:ss')})`, ); + (logAction.meta as any)['reason'] = 'Already sent in the last interval'; + logAction.changed('meta', true); + await logAction.save(); return; } @@ -91,6 +94,9 @@ export abstract class Notifier { this.logger.debug( `Notification already sent to ${player.fullName} enough (${totalAmount}) times`, ); + (logAction.meta as any)['reason'] = 'Already sent enough times'; + logAction.changed('meta', true); + await logAction.save(); return; } } diff --git a/libs/backend/orchestrator/package.json b/libs/backend/orchestrator/package.json index 7f57d8c181..55007cb147 100644 --- a/libs/backend/orchestrator/package.json +++ b/libs/backend/orchestrator/package.json @@ -1,11 +1,11 @@ { "name": "@badman/backend-orchestrator", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { - "@badman/backend-database": "6.166.3", - "@badman/backend-queue": "6.166.3", - "@badman/utils": "6.166.3", - "@badman/backend-websockets": "6.166.3", + "@badman/backend-database": "6.168.3", + "@badman/backend-queue": "6.168.3", + "@badman/utils": "6.168.3", + "@badman/backend-websockets": "6.168.3", "@nestjs/bull": "^10.1.1", "@nestjs/common": "^10.3.8", "@nestjs/config": "^3.2.2", diff --git a/libs/backend/pupeteer/package.json b/libs/backend/pupeteer/package.json index 3df52dfac7..af67f8a304 100644 --- a/libs/backend/pupeteer/package.json +++ b/libs/backend/pupeteer/package.json @@ -1,6 +1,6 @@ { "name": "@badman/backend-pupeteer", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "puppeteer": "^22.8.0", "@nestjs/common": "^10.3.8", diff --git a/libs/backend/queue/package.json b/libs/backend/queue/package.json index 3fa9205161..9cfece3cfb 100644 --- a/libs/backend/queue/package.json +++ b/libs/backend/queue/package.json @@ -1,9 +1,9 @@ { "name": "@badman/backend-queue", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@swc/helpers": "~0.5.2", - "@badman/utils": "6.166.3", + "@badman/utils": "6.168.3", "@nestjs/common": "^10.3.10", "@nestjs/config": "^3.2.3", "@nestjs/bull": "^10.1.1" diff --git a/libs/backend/ranking/package.json b/libs/backend/ranking/package.json index dfc517b000..09356b47fa 100644 --- a/libs/backend/ranking/package.json +++ b/libs/backend/ranking/package.json @@ -1,13 +1,13 @@ { "name": "@badman/backend-ranking", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { - "@badman/backend-database": "6.166.3", - "@badman/backend-queue": "6.166.3", - "@badman/belgium-flanders-places": "6.166.3", - "@badman/belgium-flanders-points": "6.166.3", - "@badman/backend-utils": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-database": "6.168.3", + "@badman/backend-queue": "6.168.3", + "@badman/belgium-flanders-places": "6.168.3", + "@badman/belgium-flanders-points": "6.168.3", + "@badman/backend-utils": "6.168.3", + "@badman/utils": "6.168.3", "@nestjs/common": "^10.3.8", "fastify": "^4.27.0", "moment": "^2.30.1", diff --git a/libs/backend/ranking/src/utils/point-calculator.ts b/libs/backend/ranking/src/utils/point-calculator.ts index 85188f4b4f..8aa6e7b515 100644 --- a/libs/backend/ranking/src/utils/point-calculator.ts +++ b/libs/backend/ranking/src/utils/point-calculator.ts @@ -1,5 +1,5 @@ import { RankingSystem, Game, Player } from '@badman/backend-database'; -import { GameType } from '@badman/utils'; +import { GameType, Ranking } from '@badman/utils'; export class PointCalculator { constructor(private _type: RankingSystem) {} @@ -42,7 +42,7 @@ export class PointCalculator { const rankingPlayer1Team2 = player1Team2.rankingLastPlaces?.[0] ?? maxRanking; const rankingPlayer2Team2 = player2Team2.rankingLastPlaces?.[0] ?? maxRanking; - let pointsFrom: 'single' | 'mix' | 'double' | undefined = undefined; + let pointsFrom: Ranking | undefined = undefined; switch (game.gameType) { case GameType.S: diff --git a/libs/backend/search/package.json b/libs/backend/search/package.json index 29cda3c1d6..90dadf9445 100644 --- a/libs/backend/search/package.json +++ b/libs/backend/search/package.json @@ -1,14 +1,14 @@ { "name": "@badman/backend-search", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@swc/helpers": "~0.5.2", "@nestjs/common": "^10.3.1", "@nestjs/config": "^3.1.1", - "@badman/backend-database": "6.166.3", + "@badman/backend-database": "6.168.3", "@nestjs/graphql": "^12.0.11", "sequelize": "^6.35.2", - "@badman/utils": "6.166.3" + "@badman/utils": "6.168.3" }, "type": "commonjs", "main": "./src/index.js", diff --git a/libs/backend/translate/assets/i18n/en/all.json b/libs/backend/translate/assets/i18n/en/all.json index 15fa5e078f..62f2a98621 100644 --- a/libs/backend/translate/assets/i18n/en/all.json +++ b/libs/backend/translate/assets/i18n/en/all.json @@ -22,6 +22,7 @@ "none": "None", "open-in-new-tab": "Open in toernooi.nl", "prev": "Previous", + "re-sync": "Recalculate points", "save": "Save", "save-and-continue": "Save and continue", "set-primary": "Make primary", @@ -681,6 +682,13 @@ "backup": "Backup player", "base": "Baseplayer", "claimed": "Claimed", + "ical": { + "click": "Copy to clipboard", + "copied": "Link copied to clipboard", + "description": "The link for all season then continues to follow the team as it promotes/degrades. even if the team changes numbers (as long as the league responsible transfers the team from the previous year)", + "linked": "All seasons", + "team-season": "Just this season" + }, "menu": { "add": "New player", "claim": "Claim account", @@ -689,6 +697,12 @@ }, "no-memberid": "Unknown member id", "no-players": "No Players", + "re-calculate": { + "calculate": "Calculate", + "from": "From", + "title": "Recover points for player in next period", + "to": "To" + }, "regular": "Regular player", "search": { "backup-player": "Player is listed with reserve players", @@ -718,11 +732,34 @@ "can-not-upgrade": "More than {points} = rising from {level} to {newLevel}", "can-upgrade": "With this number of points (>{points}), the player will rise from {level} to {newLevel}", "corrected": "Originally {original} games, average is being calculated with minimum {corrected} games.", + "countsForDowngrade": "# Promotion", + "countsForUpgrade": "# Degradation", "date": "Date", "disclaimer": "Currently, this is a proof of concept. This is because we don't have the full data set from an external system to show the following correctly. We are working on this.", - "downgrade": "Downgrade", + "downgrade-average": "Avg. demotion", "drops-next-period": "Drops next period", "evolution": "Evolution", + "export": { + "avgDowngrade": "Degradation average", + "avgUpgrade": "Pomotion average", + "countsFor": "Counts for", + "date": "Date", + "LOST_DOWNGRADE": "Promotion + Degradation", + "LOST_IGNORED": "Does not count", + "LOST_UPGRADE": "Promotion", + "opponent1": "Opponent 1", + "opponent2": "Opponent 2", + "OUT_SCOPE": "Out of scope", + "player1": "Player 1", + "player2": "Player 2", + "points": "Points", + "title": "Export current filter", + "usedForDowngrade": "Used for degradation", + "usedForUpgrade": "Used for promotion", + "WON": "Won" + }, + "games-downgrade": "# Games relegation", + "games-upgrade": "# Matches promotion", "hint": "Click on a level for simulation and breakdown", "ignored": "Doesn't count", "includeOutOfScope": "Out of scope", @@ -740,6 +777,9 @@ "one": "One game", "other": "{count} games" }, + "outOfScopeDowngrade": "Out of scope for downgrade", + "outOfScopeUpgrade": "Out of scope for promotion", + "outOfScopeWonGames": "Unused matches won", "period": { "last-point-update": "Past points update", "last-ranking-update": "Past rankings update", @@ -758,9 +798,9 @@ "point": "Points update", "ranking": "Ranking update" }, - "upgrade": "Upgrade", - "usedForDowngrade": "Used for up- and downgrade", - "usedForUpgrade": "Used for upgrade" + "upgrade-average": "Avg. promotion", + "used-for-downgrade": "Used for downgrade", + "used-for-upgrade": "Used for promotion" }, "clone-points": "Copy points to this system", "double": "Double", diff --git a/libs/backend/translate/assets/i18n/fr_BE/all.json b/libs/backend/translate/assets/i18n/fr_BE/all.json index 1084cf3414..54661a85f5 100644 --- a/libs/backend/translate/assets/i18n/fr_BE/all.json +++ b/libs/backend/translate/assets/i18n/fr_BE/all.json @@ -22,6 +22,7 @@ "none": "Non", "open-in-new-tab": "Open in toernooi.nl", "prev": "Précédent", + "re-sync": "Recalculer les points", "save": "Enregistrer", "save-and-continue": "Sauvegarder et continuer", "set-primary": "Rendre primaire", @@ -681,6 +682,13 @@ "backup": "Joueur de réserve", "base": "Joueur de base", "claimed": "Revendiqué", + "ical": { + "click": "Copier dans le presse-papiers", + "copied": "Lien copié dans le presse-papiers", + "description": "Le lien pour toute la saison continue à suivre l'équipe au fur et à mesure de sa promotion/dégradation, même si l'équipe change de numéro (à condition que la ligue responsable transfère l'équipe de l'année précédente).", + "linked": "Toutes les saisons", + "team-season": "Juste cette saison " + }, "menu": { "add": "Nouveau joueur", "claim": "Réclamer un compte", @@ -689,6 +697,12 @@ }, "no-memberid": "Numéro de membre inconnu", "no-players": "Aucun joueur", + "re-calculate": { + "calculate": "", + "from": "De", + "title": "Récupérer des points pour le joueur dans la période suivante", + "to": "Pour" + }, "regular": "Joueur régulier", "search": { "backup-player": "Le joueur est répertorié avec les joueurs de réserve", @@ -718,11 +732,34 @@ "can-not-upgrade": "Plus de {points} = passer de {level} à {newLevel}", "can-upgrade": "Avec ce nombre de points (>{points}), le joueur passera de {level} à {newLevel}", "corrected": " {original} correspondances d'origine, la moyenne est calculée avec un minimum de {corrected} correspondances.", + "countsForDowngrade": "# Promotion", + "countsForUpgrade": "# Dégradation", "date": "Date", "disclaimer": "Il s'agit actuellement d'une preuve de concept. En effet, nous ne pouvons pas demander toutes les données d'un système externe pour afficher correctement ce qui suit. Nous y travaillons.", - "downgrade": "Rétrograder", + "downgrade-average": "Moyenne des déclassements", "drops-next-period": "Ne compte pas dans la période suivante", "evolution": "Évolution", + "export": { + "avgDowngrade": "Moyenne de dégradation", + "avgUpgrade": "Moyenne de la promotion", + "countsFor": "Compte pour", + "date": "Date", + "LOST_DOWNGRADE": "Promotion + Dégradation", + "LOST_IGNORED": "Ne compte pas", + "LOST_UPGRADE": "Promotion", + "opponent1": "Opposant 1", + "opponent2": "Opposant 2", + "OUT_SCOPE": "Hors champ d'application", + "player1": "Joueur 1", + "player2": "Joueur 2", + "points": "Points", + "title": "Exporter le filtre actuel", + "usedForDowngrade": "Utilisé pour la dégradation", + "usedForUpgrade": "Utilisé pour la promotion", + "WON": "Gagné" + }, + "games-downgrade": "# Jeux relégation", + "games-upgrade": "# Correspond à la promotion", "hint": "Cliquez sur un classement pour une simulation et une analyse", "ignored": "Non compté", "includeOutOfScope": "Hors champ", @@ -740,6 +777,9 @@ "one": "1 match", "other": "{count} correspondances" }, + "outOfScopeDowngrade": "Hors de portée d'une révision à la baisse", + "outOfScopeUpgrade": "Pas de possibilité de promotion", + "outOfScopeWonGames": "Matchs gagnés non utilisés", "period": { "last-point-update": "Mise à jour des points précédents", "last-ranking-update": "Mise à jour des classements antérieurs", @@ -758,9 +798,9 @@ "point": "Mise à jour des points", "ranking": "Mise à jour du classement" }, - "upgrade": "Surclassement", - "usedForDowngrade": "Utilisé pour la rétrogradation et la mise à niveau", - "usedForUpgrade": "Utilisé pour la mise à niveau uniquement" + "upgrade-average": "Moyenne. promotion", + "used-for-downgrade": "Utilisé pour le déclassement", + "used-for-upgrade": "Utilisé pour la promotion" }, "clone-points": "Copier les points vers ce système", "double": "Double", diff --git a/libs/backend/translate/assets/i18n/nl_BE/all.json b/libs/backend/translate/assets/i18n/nl_BE/all.json index 44168ce4b7..301e234d6e 100644 --- a/libs/backend/translate/assets/i18n/nl_BE/all.json +++ b/libs/backend/translate/assets/i18n/nl_BE/all.json @@ -22,6 +22,7 @@ "none": "Geen", "open-in-new-tab": "Open in toernooi.nl", "prev": "Vorige", + "re-sync": "Herbereken punten", "save": "Opslaan", "save-and-continue": "Opslaan en verdergaan", "set-primary": "Maak primair", @@ -681,6 +682,13 @@ "backup": "Reservespeler", "base": "Basisspeler", "claimed": "Claimed", + "ical": { + "click": "Kopieer naar klembord", + "copied": "Link gekopieerd naar klembord", + "description": "De link voor alle seizoen blijft dan de ploeg volgen als deze promoveert/degradeert. zelfs als de ploeg van nummer veranderd (zolang de competitie verantwoordelijke de ploeg overzet van het vorig jaar)", + "linked": "Alle seizoenen", + "team-season": "Enkel dit seizoen" + }, "menu": { "add": "Nieuwe speler", "claim": "Claim account", @@ -689,6 +697,12 @@ }, "no-memberid": "Onbekend lidnummer", "no-players": "Geen spelers", + "re-calculate": { + "calculate": "Bereken", + "from": "Van", + "title": "Herberekenen van de punten voor speler in periode:", + "to": "Tot" + }, "regular": "Vaste speler", "search": { "backup-player": "Speler staat bij reservespelers", @@ -718,11 +732,34 @@ "can-not-upgrade": "Meer dan {points} = stijgen van {level} naar {newLevel}", "can-upgrade": "Met dit aantal punten (>{points}) zal de speler stijgen van {level} naar {newLevel}", "corrected": "Origineel {original} wedstrijden, gemiddelde wordt berekend met minimum {corrected} wedstrijden.", + "countsForDowngrade": "# Promotie", + "countsForUpgrade": "# Degradatie", "date": "Datum", "disclaimer": "Momenteel is dit een proof of concept. Dit komt omdat we niet alle data kunnen opvragen van een extern systeem om onderstaande correct te tonen. We werken hieraan.", - "downgrade": "Downgrade", + "downgrade-average": "Avg. degradatie", "drops-next-period": "Telt niet meer mee in volgende periode", "evolution": "Evolutie", + "export": { + "avgDowngrade": "Degradatie gemiddelde", + "avgUpgrade": "Pomotie gemiddelde", + "countsFor": "Telt voor", + "date": "Datum", + "LOST_DOWNGRADE": "Promotie + Degradatie", + "LOST_IGNORED": "Telt niet mee", + "LOST_UPGRADE": "Promotie", + "opponent1": "Tegenstander 1", + "opponent2": "Tegenstander 2", + "OUT_SCOPE": "Buiten scope", + "player1": "Speler 1", + "player2": "Speler 2", + "points": "Punten", + "title": "Export huidige filter", + "usedForDowngrade": "Gebruikt voor degradatie", + "usedForUpgrade": "Gebruikt voor promotie", + "WON": "Gewonnen" + }, + "games-downgrade": "# Wedstrijden degradatie", + "games-upgrade": "# Wedstrijden promotie", "hint": "Klik op een klassement voor simulatie en analyse", "ignored": "Niet meegeteld", "includeOutOfScope": "Out of scope", @@ -740,6 +777,9 @@ "one": "1 wedstrijd", "other": "{count} wedstrijden" }, + "outOfScopeDowngrade": "Buiten scope voor degradatie", + "outOfScopeUpgrade": "Buiten scope voor promotie", + "outOfScopeWonGames": "Niet gebruikte gewonnen wedstrijden", "period": { "last-point-update": "Afgelopen punten update", "last-ranking-update": "Afgelopen klassementen update", @@ -758,9 +798,9 @@ "point": "Punten update", "ranking": "Ranking update" }, - "upgrade": "Upgrade", - "usedForDowngrade": "Gebruikt voor downgrade en upgrade", - "usedForUpgrade": "Gebruikt enkel voor upgrade" + "upgrade-average": "Avg. promotie", + "used-for-downgrade": "Gebruikt voor degradatie", + "used-for-upgrade": "Gebruikt voor promotie" }, "clone-points": "Kopieer punten naar dit systeem", "double": "Dubbel", diff --git a/libs/backend/translate/package.json b/libs/backend/translate/package.json index 086a114bde..158d732265 100644 --- a/libs/backend/translate/package.json +++ b/libs/backend/translate/package.json @@ -1,10 +1,10 @@ { "name": "@badman/backend-translate", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@swc/helpers": "~0.5.2", - "@badman/backend-authorization": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-authorization": "6.168.3", + "@badman/utils": "6.168.3", "@nestjs/common": "^10.3.1", "nestjs-i18n": "^10.4.0", "fastify": "^4.28.1" diff --git a/libs/backend/twizzit/package.json b/libs/backend/twizzit/package.json index 204e01d9a0..1d33cc6a4f 100644 --- a/libs/backend/twizzit/package.json +++ b/libs/backend/twizzit/package.json @@ -1,8 +1,8 @@ { "name": "@badman/backend-twizzit", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { - "@badman/backend-database": "6.166.3", + "@badman/backend-database": "6.168.3", "@nestjs/common": "^10.3.8", "fastify": "^4.27.0", "moment-timezone": "^0.5.45", diff --git a/libs/backend/utils/package.json b/libs/backend/utils/package.json index a2d43c1c96..57f4196b28 100644 --- a/libs/backend/utils/package.json +++ b/libs/backend/utils/package.json @@ -1,6 +1,6 @@ { "name": "@badman/backend-utils", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@nestjs/common": "^10.3.8", "fastify": "^4.27.0", diff --git a/libs/backend/validation/package.json b/libs/backend/validation/package.json index 3e7b555f10..529a2502b1 100644 --- a/libs/backend/validation/package.json +++ b/libs/backend/validation/package.json @@ -1,10 +1,10 @@ { "name": "@badman/backend-validation", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@nestjs/common": "^10.3.8", "@swc/helpers": "^0.5.11", - "@badman/backend-database": "6.166.3" + "@badman/backend-database": "6.168.3" }, "type": "commonjs", "main": "./src/index.js", diff --git a/libs/backend/visual/package.json b/libs/backend/visual/package.json index 2dc46e6b0b..ae85f17d7f 100644 --- a/libs/backend/visual/package.json +++ b/libs/backend/visual/package.json @@ -1,9 +1,9 @@ { "name": "@badman/backend-visual", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { - "@badman/backend-cache": "6.166.3", - "@badman/utils": "6.166.3", + "@badman/backend-cache": "6.168.3", + "@badman/utils": "6.168.3", "axios": "^1.6.8", "axios-retry": "^4.1.0", "fast-xml-parser": "^4.3.6", diff --git a/libs/backend/websockets/package.json b/libs/backend/websockets/package.json index a13161758b..eb5a67cbfe 100644 --- a/libs/backend/websockets/package.json +++ b/libs/backend/websockets/package.json @@ -1,6 +1,6 @@ { "name": "@badman/backend-websockets", - "version": "6.166.3", + "version": "6.168.3", "dependencies": { "@nestjs/platform-socket.io": "^10.3.8", "socket.io": "^4.7.5", diff --git a/libs/frontend/components/package.json b/libs/frontend/components/package.json index 44631585b9..2581d4445e 100644 --- a/libs/frontend/components/package.json +++ b/libs/frontend/components/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-components", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/common": "18.1.0", "@angular/core": "18.1.0", @@ -29,7 +29,7 @@ "@angular/cdk": "18.1.0", "ngx-socket-io": "~4.7.0", "@angular/service-worker": "18.1.0", - "xng-breadcrumb": "^11.0.0" + "xng-breadcrumb": "^12.0.0" }, "dependencies": {}, "sideEffects": false diff --git a/libs/frontend/components/src/games/recent-games/list-encounters/list-encounters.component.ts b/libs/frontend/components/src/games/recent-games/list-encounters/list-encounters.component.ts index 669ef3856d..f68d56dce4 100644 --- a/libs/frontend/components/src/games/recent-games/list-encounters/list-encounters.component.ts +++ b/libs/frontend/components/src/games/recent-games/list-encounters/list-encounters.component.ts @@ -96,7 +96,7 @@ export class ListEncountersComponent implements OnInit, OnChanges { return null; } - const gameType = encounter.drawCompetition?.subEventCompetition?.eventType as 'M' | 'F' | 'MX'; + const gameType = encounter.drawCompetition?.subEventCompetition?.eventType; const gameNumber = game.order ?? 0; return gameLabel(gameType, gameNumber) as string[]; diff --git a/libs/frontend/components/src/games/recent-games/list-games/list-games.component.html b/libs/frontend/components/src/games/recent-games/list-games/list-games.component.html index 08919b4f9b..80b66c1275 100644 --- a/libs/frontend/components/src/games/recent-games/list-games/list-games.component.html +++ b/libs/frontend/components/src/games/recent-games/list-games/list-games.component.html @@ -59,7 +59,7 @@ @if (this.getPoints(game, 1); as points) {
@@ -116,7 +116,7 @@ @if (this.getPoints(game, 2); as points) {
diff --git a/libs/frontend/components/src/shell/shell/shell.component.html b/libs/frontend/components/src/shell/shell/shell.component.html index caab2daea3..2ef01b1e7d 100644 --- a/libs/frontend/components/src/shell/shell/shell.component.html +++ b/libs/frontend/components/src/shell/shell/shell.component.html @@ -186,6 +186,7 @@ {{ breadcrumb | translate }} +
diff --git a/libs/frontend/components/src/shell/shell/shell.component.ts b/libs/frontend/components/src/shell/shell/shell.component.ts index 8415733f36..ba66f864e9 100644 --- a/libs/frontend/components/src/shell/shell/shell.component.ts +++ b/libs/frontend/components/src/shell/shell/shell.component.ts @@ -37,9 +37,9 @@ import { Banner } from '@badman/frontend-models'; import { DEVICE } from '@badman/frontend-utils'; import { TranslateModule } from '@ngx-translate/core'; import { Apollo, gql } from 'apollo-angular'; -import { computedAsync } from 'ngxtension/computed-async'; +import { derivedAsync } from 'ngxtension/derived-async'; import { filter, map } from 'rxjs/operators'; -import { BreadcrumbComponent } from 'xng-breadcrumb'; +import { BreadcrumbComponent, BreadcrumbItemDirective } from 'xng-breadcrumb'; import { HasClaimComponent } from '../../has-claim'; import { BannerComponent, @@ -64,6 +64,7 @@ import { ServiceStatusComponent, ServiceWorkerModule, BreadcrumbComponent, + BreadcrumbItemDirective, TranslateModule, MatSidenavModule, MatSlideToggleModule, @@ -108,7 +109,7 @@ export class ShellComponent { canAnyChange = this.auth.hasClaimSignal('change-any:encounter'); canViewChange = this.auth.hasClaimSignal('*change:encounter'); - openEnrollments = computedAsync(() => + openEnrollments = derivedAsync (() => this.apollo .query<{ eventTournaments: { count: number }; @@ -138,7 +139,7 @@ export class ShellComponent { ), ); - openChangeEncounter = computedAsync(() => + openChangeEncounter = derivedAsync (() => this.apollo .query<{ eventTournaments: { count: number }; diff --git a/libs/frontend/models/package.json b/libs/frontend/models/package.json index 53f7494bd9..6bbaeb058d 100644 --- a/libs/frontend/models/package.json +++ b/libs/frontend/models/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-models", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@badman/utils": "*", "moment": "^2.30.1" diff --git a/libs/frontend/models/src/models/events/sub-event.model.ts b/libs/frontend/models/src/models/events/sub-event.model.ts index 5cb169a44f..669973c1b3 100644 --- a/libs/frontend/models/src/models/events/sub-event.model.ts +++ b/libs/frontend/models/src/models/events/sub-event.model.ts @@ -1,3 +1,4 @@ +import { SubEventTypeEnum } from '@badman/utils'; import { EventEntry } from '../entry.model'; import { Game } from '../game.model'; import { RankingGroup } from '../group.model'; @@ -6,7 +7,7 @@ import { Player } from '../player.model'; export class SubEvent { id?: string; name?: string; - eventType?: string; + eventType?: SubEventTypeEnum; eventId?: string; level?: number; maxLevel?: number; diff --git a/libs/frontend/modules/auth/package.json b/libs/frontend/modules/auth/package.json index 2449c90d86..61f2219f0b 100644 --- a/libs/frontend/modules/auth/package.json +++ b/libs/frontend/modules/auth/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-auth", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/common": "18.1.0", "@angular/core": "18.1.0", diff --git a/libs/frontend/modules/cp/package.json b/libs/frontend/modules/cp/package.json index 7e4cfb090c..49b6499013 100644 --- a/libs/frontend/modules/cp/package.json +++ b/libs/frontend/modules/cp/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-cp", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/common": "18.1.0", "@angular/core": "18.1.0", diff --git a/libs/frontend/modules/excel/package.json b/libs/frontend/modules/excel/package.json index 9653e229db..7558bff31f 100644 --- a/libs/frontend/modules/excel/package.json +++ b/libs/frontend/modules/excel/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-excel", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@badman/frontend-models": "*", "@angular/core": "18.1.0", diff --git a/libs/frontend/modules/graphql/package.json b/libs/frontend/modules/graphql/package.json index 44434ac0ba..2f378d3fd1 100644 --- a/libs/frontend/modules/graphql/package.json +++ b/libs/frontend/modules/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-graphql", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/common": "18.1.0", "@angular/core": "18.1.0", diff --git a/libs/frontend/modules/graphql/src/graphql.module.ts b/libs/frontend/modules/graphql/src/graphql.module.ts index 50fc975219..60387c782b 100644 --- a/libs/frontend/modules/graphql/src/graphql.module.ts +++ b/libs/frontend/modules/graphql/src/graphql.module.ts @@ -46,7 +46,6 @@ export function createApollo( const isBrowser = isPlatformBrowser(platformId); const isServer = isPlatformServer(platformId); - // const hasKey = transferState.hasKey(STATE_KEY); const auth = setContext(async (_, { headers }) => { if (isBrowser) { diff --git a/libs/frontend/modules/html-injects/package.json b/libs/frontend/modules/html-injects/package.json index c27ef93538..cd016fde76 100644 --- a/libs/frontend/modules/html-injects/package.json +++ b/libs/frontend/modules/html-injects/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-html-injects", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/common": "18.1.0", "@angular/core": "18.1.0", diff --git a/libs/frontend/modules/pdf/package.json b/libs/frontend/modules/pdf/package.json index 3c2b877003..2bf548fddc 100644 --- a/libs/frontend/modules/pdf/package.json +++ b/libs/frontend/modules/pdf/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-pdf", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/common": "18.1.0", "@angular/core": "18.1.0" diff --git a/libs/frontend/modules/queue/package.json b/libs/frontend/modules/queue/package.json index 9662104ba4..d2c79e4e3d 100644 --- a/libs/frontend/modules/queue/package.json +++ b/libs/frontend/modules/queue/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-queue", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/core": "18.1.0", "@badman/frontend-models": "*", diff --git a/libs/frontend/modules/seo/package.json b/libs/frontend/modules/seo/package.json index 47afa59a1c..482557544f 100644 --- a/libs/frontend/modules/seo/package.json +++ b/libs/frontend/modules/seo/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-seo", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/common": "18.1.0", "@angular/core": "18.1.0", diff --git a/libs/frontend/modules/translation/package.json b/libs/frontend/modules/translation/package.json index 4413cbe286..cce5f40fa6 100644 --- a/libs/frontend/modules/translation/package.json +++ b/libs/frontend/modules/translation/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-translation", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/core": "18.1.0", "@badman/utils": "*", diff --git a/libs/frontend/modules/twizzit/package.json b/libs/frontend/modules/twizzit/package.json index 4dd40d2e14..0ae35290f6 100644 --- a/libs/frontend/modules/twizzit/package.json +++ b/libs/frontend/modules/twizzit/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-twizzit", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@badman/frontend-models": "*", "@angular/common": "18.1.0", diff --git a/libs/frontend/modules/utils/package.json b/libs/frontend/modules/utils/package.json index 68455ab267..6dce79c2a8 100644 --- a/libs/frontend/modules/utils/package.json +++ b/libs/frontend/modules/utils/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-utils", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/common": "18.1.0", "@angular/core": "18.1.0", diff --git a/libs/frontend/modules/vitals/package.json b/libs/frontend/modules/vitals/package.json index aa6dc64d60..e83e370489 100644 --- a/libs/frontend/modules/vitals/package.json +++ b/libs/frontend/modules/vitals/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-vitals", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/common": "18.1.0", "@angular/core": "18.1.0", diff --git a/libs/frontend/pages/club/package.json b/libs/frontend/pages/club/package.json index edf717405d..7da61ff0ef 100644 --- a/libs/frontend/pages/club/package.json +++ b/libs/frontend/pages/club/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-club", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@angular/common": "18.1.0", "@angular/core": "18.1.0", @@ -28,7 +28,7 @@ "@angular/platform-browser": "18.1.0", "ngx-moment": "^6.0.2", "@apollo/client": "^3.10.8", - "xng-breadcrumb": "^11.0.0", + "xng-breadcrumb": "^12.0.0", "@ng-matero/extensions": "^18.0.3" }, "dependencies": {}, diff --git a/libs/frontend/pages/club/src/pages/edit/components/club-edit-team/club-edit-team.component.ts b/libs/frontend/pages/club/src/pages/edit/components/club-edit-team/club-edit-team.component.ts index 3ece168501..50aee9944e 100644 --- a/libs/frontend/pages/club/src/pages/edit/components/club-edit-team/club-edit-team.component.ts +++ b/libs/frontend/pages/club/src/pages/edit/components/club-edit-team/club-edit-team.component.ts @@ -5,8 +5,8 @@ import { OnInit, TemplateRef, ViewChild, - input, inject, + input, output, } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; diff --git a/libs/frontend/pages/competition/change-encounter/package.json b/libs/frontend/pages/competition/change-encounter/package.json index 23f3cce73f..cadc0d9c98 100644 --- a/libs/frontend/pages/competition/change-encounter/package.json +++ b/libs/frontend/pages/competition/change-encounter/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-change-encounter", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@badman/frontend-components": "*", "@badman/frontend-models": "*", @@ -22,7 +22,7 @@ "ngx-moment": "^6.0.2", "@angular/cdk": "18.1.0", "ngxtension": "^3.2.0", - "xng-breadcrumb": "^11.0.0", + "xng-breadcrumb": "^12.0.0", "apollo-angular": "^7.0.2", "seed-to-color": "^1.0.5", "@ng-matero/extensions": "^18.0.3", diff --git a/libs/frontend/pages/competition/change-encounter/src/components/calendar/calendar.component.ts b/libs/frontend/pages/competition/change-encounter/src/components/calendar/calendar.component.ts index 1930cc36e9..207ba041fa 100644 --- a/libs/frontend/pages/competition/change-encounter/src/components/calendar/calendar.component.ts +++ b/libs/frontend/pages/competition/change-encounter/src/components/calendar/calendar.component.ts @@ -369,7 +369,7 @@ export class CalendarComponent implements OnInit { } const dayEvent = this.dayEvents.get(format); - if (dayEvent && dayEvent.some((e) => e.allowCompetition == false)) { + if (dayEvent?.some((e) => !e.allowCompetition)) { return false; } @@ -696,7 +696,7 @@ export class CalendarComponent implements OnInit { map((result) => { return result.data?.club.teams?.map((team) => new Team(team)); }), - map((teams) => teams.sort(sortTeams)), + map((teams) => teams.slice().sort(sortTeams)), ), ); } @@ -876,7 +876,6 @@ export class CalendarComponent implements OnInit { return; } - if (time) { // splite time to hour and minute const timeSplit = time?.split(':'); @@ -971,11 +970,10 @@ export class CalendarComponent implements OnInit { // if there is an request if (encounter.encounterChange && !encounter.encounterChange.accepted) { dayInfo.locations[infoIndex].removed.push(encounter); - } else { - // if the home team is visible - if (this._isVisible(encounter.homeTeamId)) { - dayInfo.locations[infoIndex].encounters.push(encounter); - } + } + // if the home team is visible + else if (this._isVisible(encounter.homeTeamId)) { + dayInfo.locations[infoIndex].encounters.push(encounter); } dayInfo.locations[infoIndex].space = Math.max(0, dayInfo.locations[infoIndex].space - 1); diff --git a/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/change-encounter.component.ts b/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/change-encounter.component.ts index 34cb1040f2..39ddeb4850 100644 --- a/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/change-encounter.component.ts +++ b/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/change-encounter.component.ts @@ -88,5 +88,7 @@ export class ChangeEncounterComponent implements OnInit { this.breadcrumbsService.set('competition/change-encounter', result[changeEncounterKey]); this.breadcrumbsService.set('competition', result[competition]); }); + + } } diff --git a/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.ts b/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.ts index ce69916845..fed9d73591 100644 --- a/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.ts +++ b/libs/frontend/pages/competition/change-encounter/src/pages/change-encounter/components/show-requests/show-requests.component.ts @@ -240,6 +240,16 @@ export class ShowRequestsComponent implements OnInit { } validate() { + const suggestedDates = this.dateControls + .getRawValue() + .map((r) => r['calendar']) + ?.map((r) => ({ + date: r.date, + locationId: r.locationId, + })); + + console.log('suggestedDates', suggestedDates); + return this.apollo .query<{ validateChangeEncounter: { @@ -274,7 +284,7 @@ export class ShowRequestsComponent implements OnInit { data: { teamId: this.group().get('team')?.value, workingencounterId: this.encounter.id, - suggestedDates: this.dateControls.getRawValue().map((r) => r['calendar'].date), + suggestedDates, }, }, }) @@ -291,7 +301,7 @@ export class ShowRequestsComponent implements OnInit { // get the last date lastDate = dates .map((d) => d?.['calendar']?.['date']) - .reduce((a, b) => (a > b ? a : b)) as Date; + .reduce((a, b) => (a > b ? a : b), new Date()) as Date; } let newDate = moment(lastDate).add(1, 'week'); @@ -363,7 +373,7 @@ export class ShowRequestsComponent implements OnInit { ); const ids = dates.map((o) => o.date?.getTime()); change.dates = dates.filter(({ date }, index) => !ids.includes(date?.getTime(), index + 1)); - change.accepted = change.dates.some((r) => r.selected == true); + change.accepted = change.dates.some((r) => r.selected); if (change.dates == null || (change.dates?.length ?? 0) == 0) { if (this.home) { @@ -424,13 +434,6 @@ export class ShowRequestsComponent implements OnInit { }, }); - // const teamControl = this.group().get('team'); - // if (!teamControl) { - // throw new Error('Team control not found'); - // } - - // teamControl.setValue(teamControl.value); - // this.group().get(this.dependsOn())?.setValue(null); this.snackBar.open( await this.translate.instant('all.competition.change-encounter.requested'), 'OK', @@ -454,7 +457,7 @@ export class ShowRequestsComponent implements OnInit { }; if (change.accepted) { - const changed = change.dates.find((r) => r.selected == true); + const changed = change.dates.find((r) => r.selected); const dialog = this.dialog.open(this.confirmDialog, { data: { changedLocation: changed?.locationId != this.encounter?.location?.id, diff --git a/libs/frontend/pages/competition/event/package.json b/libs/frontend/pages/competition/event/package.json index 154232bfaf..844234b4c6 100644 --- a/libs/frontend/pages/competition/event/package.json +++ b/libs/frontend/pages/competition/event/package.json @@ -1,6 +1,6 @@ { "name": "@badman/frontend-event", - "version": "6.166.3", + "version": "6.168.3", "peerDependencies": { "@badman/frontend-team-assembly": "*", "@badman/frontend-team-enrollment": "*", @@ -27,7 +27,7 @@ "ngxtension": "^3.2.0", "rxjs": "~7.8.1", "ng-apexcharts": "^1.10.0", - "xng-breadcrumb": "^11.0.0", + "xng-breadcrumb": "^12.0.0", "@ng-maps/google": "^6.0.0", "ngx-moment": "^6.0.2", "@angular/animations": "18.1.0", diff --git a/libs/frontend/pages/competition/event/src/pages/detail-avg/detail-avg.page.ts b/libs/frontend/pages/competition/event/src/pages/detail-avg/detail-avg.page.ts index 7a97e24ae6..4a4bfc6c76 100644 --- a/libs/frontend/pages/competition/event/src/pages/detail-avg/detail-avg.page.ts +++ b/libs/frontend/pages/competition/event/src/pages/detail-avg/detail-avg.page.ts @@ -6,6 +6,7 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; import { ActivatedRoute, RouterModule } from '@angular/router'; import { LoadingBlockComponent } from '@badman/frontend-components'; import { EventCompetition, SubEventCompetition } from '@badman/frontend-models'; +import { Ranking } from '@badman/utils'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { Apollo, gql } from 'apollo-angular'; import { ApexAxisChartSeries, ApexOptions, NgApexchartsModule } from 'ng-apexcharts'; @@ -38,7 +39,7 @@ export class DetailAvgPageComponent implements OnInit { eventCompetition!: EventCompetition; genders: ('M' | 'F')[] = ['M', 'F']; - chartTypes: ('single' | 'double' | 'mix')[] = ['single', 'double', 'mix']; + chartTypes: Ranking[] = ['single', 'double', 'mix']; eventTypes: ('M' | 'F' | 'MX')[] = ['M', 'F', 'MX']; chartOptions: ApexOptions = { diff --git a/libs/frontend/pages/competition/event/src/pages/detail-draw/detail-draw.page.html b/libs/frontend/pages/competition/event/src/pages/detail-draw/detail-draw.page.html index 1563ac8fbb..b45c815242 100644 --- a/libs/frontend/pages/competition/event/src/pages/detail-draw/detail-draw.page.html +++ b/libs/frontend/pages/competition/event/src/pages/detail-draw/detail-draw.page.html @@ -1,15 +1,15 @@ -
{{ drawCompetition().name }}
+
{{ drawCompetition()?.name }}
- @if (drawCompetition().visualCode && eventCompetition().visualCode) { + @if (drawCompetition()?.visualCode && eventCompetition()?.visualCode) { open_in_new } + + + + + + + + + +

{{ 'all.event.standing.title' | translate }}

- +
@if (teams()) {
diff --git a/libs/frontend/pages/competition/event/src/pages/detail-draw/detail-draw.page.ts b/libs/frontend/pages/competition/event/src/pages/detail-draw/detail-draw.page.ts index ae6789a4d8..ab7490ae57 100644 --- a/libs/frontend/pages/competition/event/src/pages/detail-draw/detail-draw.page.ts +++ b/libs/frontend/pages/competition/event/src/pages/detail-draw/detail-draw.page.ts @@ -1,17 +1,12 @@ import { CommonModule } from '@angular/common'; -import { - ChangeDetectionStrategy, - Component, - Injector, - computed, - effect, - inject -} from '@angular/core'; -import { toSignal } from '@angular/core/rxjs-interop'; +import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { ActivatedRoute, RouterModule } from '@angular/router'; +import { RouterModule } from '@angular/router'; import { + HasClaimComponent, PageHeaderComponent, RecentGamesComponent, StandingComponent, @@ -20,10 +15,12 @@ import { import { DrawCompetition, EventCompetition, Team } from '@badman/frontend-models'; import { SeoService } from '@badman/frontend-seo'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { Apollo, gql } from 'apollo-angular'; +import { injectDestroy } from 'ngxtension/inject-destroy'; +import { injectRouteData } from 'ngxtension/inject-route-data'; +import { take, takeUntil } from 'rxjs/operators'; import { BreadcrumbService } from 'xng-breadcrumb'; import { DrawLocationMapComponent } from './components'; -import { injectDestroy } from 'ngxtension/inject-destroy'; -import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'badman-detail-draw-competition', @@ -37,27 +34,26 @@ import { takeUntil } from 'rxjs/operators'; TranslateModule, MatTooltipModule, MatIconModule, + MatButtonModule, + StandingComponent, RecentGamesComponent, UpcomingGamesComponent, PageHeaderComponent, DrawLocationMapComponent, + HasClaimComponent, + MatMenuModule, ], }) export class DetailDrawCompetitionComponent { private readonly destroy$ = injectDestroy(); private readonly translateService = inject(TranslateService); - - private readonly route = inject(ActivatedRoute); - // private readonly router = inject(Router); private readonly breadcrumbService = inject(BreadcrumbService); private readonly seoService = inject(SeoService); - private readonly injector = inject(Injector); - - private routeData = toSignal(this.route.data); + private readonly apollo = inject(Apollo); - drawCompetition = computed(() => this.routeData()?.['drawCompetition'] as DrawCompetition); - eventCompetition = computed(() => this.routeData()?.['eventCompetition'] as EventCompetition); + drawCompetition = injectRouteData('drawCompetition'); + eventCompetition = injectRouteData('eventCompetition'); teams = computed(() => this.drawCompetition()?.eventEntries?.map((e) => e.team as Team)); constructor() { @@ -67,24 +63,35 @@ export class DetailDrawCompetitionComponent { .get([compTitle]) .pipe(takeUntil(this.destroy$)) .subscribe((translations) => { - this.breadcrumbService.set('competition', translations[compTitle]); + this.breadcrumbService.set('competitwion', translations[compTitle]); }); - - effect( - () => { - const drawCompetitionName = `${this.drawCompetition().name}`; - this.seoService.update({ - title: drawCompetitionName, - description: `Competition draw ${drawCompetitionName}`, - type: 'website', - keywords: ['event', 'competition', 'badminton'], - }); - this.breadcrumbService.set('@eventCompetition', this.eventCompetition().name || ''); - this.breadcrumbService.set('@drawCompetition', drawCompetitionName); - }, - { - injector: this.injector, - }, - ); + + effect(() => { + const drawCompetitionName = `${this.drawCompetition()?.name}`; + this.seoService.update({ + title: drawCompetitionName, + description: `Competition draw ${drawCompetitionName}`, + type: 'website', + keywords: ['event', 'competition', 'badminton'], + }); + this.breadcrumbService.set('@eventCompetition', this.eventCompetition()?.name ?? ''); + this.breadcrumbService.set('@drawCompetition', drawCompetitionName); + }); + } + + reCalculatePoints() { + this.apollo + .mutate({ + mutation: gql` + mutation RecalculateDrawCompetitionRankingPoints($drawId: ID!) { + recalculateDrawCompetitionRankingPoints(drawId: $drawId) + } + `, + variables: { + drawId: this.drawCompetition()?.id, + }, + }) + .pipe(take(1)) + .subscribe(); } } diff --git a/libs/frontend/pages/competition/event/src/pages/detail-encounter/detail-encounter.page.html b/libs/frontend/pages/competition/event/src/pages/detail-encounter/detail-encounter.page.html index cb5860ad47..eb68cd242e 100644 --- a/libs/frontend/pages/competition/event/src/pages/detail-encounter/detail-encounter.page.html +++ b/libs/frontend/pages/competition/event/src/pages/detail-encounter/detail-encounter.page.html @@ -1,69 +1,80 @@ - {{ this.encounterCompetitionName }} + {{ this.encounterCompetitionName() }} - @if (eventCompetition.visualCode && encounterCompetition.visualCode) { + @if (eventCompetition()?.visualCode && encounterCompetition()?.visualCode) { open_in_new } - + - - - - + + + + + + + + + + + - @if (encounterCompetition.date) { - {{ encounterCompetition.date | amDateFormat: 'llll' }} + @if (encounterCompetition()?.date) { + {{ encounterCompetition()?.date | amDateFormat: 'llll' }} } - @if (encounterCompetition.location?.name) { - {{ encounterCompetition.location?.name }} + @if (encounterCompetition()?.location?.name) { + {{ encounterCompetition()?.location?.name }} } - @if (encounterCompetition.shuttle) { + @if (encounterCompetition()?.shuttle) { - {{ encounterCompetition.shuttle }} } - @if (encounterCompetition.startHour && encounterCompetition.endHour) { + @if (encounterCompetition()?.startHour && encounterCompetition()?.endHour) { - {{ encounterCompetition.startHour }} - {{ encounterCompetition.endHour }} + {{ encounterCompetition()?.startHour }} - {{ encounterCompetition()?.endHour }} } - @if (encounterCompetition.gameLeader?.fullName) { + @if (encounterCompetition()?.gameLeader?.fullName) { - {{ encounterCompetition.gameLeader?.fullName }} + {{ encounterCompetition()?.gameLeader?.fullName }} } -@for (game of encounterCompetition.games; track game; let index = $index) { +@for (game of encounterCompetition()?.games; track game; let index = $index) {
@for (label of getGameLabel(index); track label) { diff --git a/libs/frontend/pages/competition/event/src/pages/detail-encounter/detail-encounter.page.ts b/libs/frontend/pages/competition/event/src/pages/detail-encounter/detail-encounter.page.ts index 93d9888d4a..56908fa54c 100644 --- a/libs/frontend/pages/competition/event/src/pages/detail-encounter/detail-encounter.page.ts +++ b/libs/frontend/pages/competition/event/src/pages/detail-encounter/detail-encounter.page.ts @@ -1,5 +1,12 @@ import { CommonModule, isPlatformBrowser } from '@angular/common'; -import { ChangeDetectionStrategy, Component, OnInit, PLATFORM_ID, inject } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + PLATFORM_ID, + computed, + effect, + inject, +} from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; @@ -9,13 +16,12 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { ActivatedRoute, RouterModule } from '@angular/router'; +import { RouterModule } from '@angular/router'; import { GameScoreComponentComponent, HasClaimComponent, PageHeaderComponent, } from '@badman/frontend-components'; -import { JobsService } from '@badman/frontend-queue'; import { DrawCompetition, EncounterCompetition, @@ -23,12 +29,16 @@ import { Game, GamePlayer, } from '@badman/frontend-models'; +import { JobsService } from '@badman/frontend-queue'; import { SeoService } from '@badman/frontend-seo'; import { GameType, gameLabel } from '@badman/utils'; -import { TranslateModule } from '@ngx-translate/core'; -import { lastValueFrom } from 'rxjs'; -import { BreadcrumbService } from 'xng-breadcrumb'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { Apollo, gql } from 'apollo-angular'; import { MomentModule } from 'ngx-moment'; +import { injectDestroy } from 'ngxtension/inject-destroy'; +import { injectRouteData } from 'ngxtension/inject-route-data'; +import { lastValueFrom, take } from 'rxjs'; +import { BreadcrumbService } from 'xng-breadcrumb'; @Component({ selector: 'badman-detail-encounter', @@ -52,54 +62,53 @@ import { MomentModule } from 'ngx-moment'; GameScoreComponentComponent, PageHeaderComponent, HasClaimComponent, - MomentModule + MomentModule, ], }) -export class DetailEncounterComponent implements OnInit { +export class DetailEncounterComponent { private seoService = inject(SeoService); - private route = inject(ActivatedRoute); + private translateService = inject(TranslateService); + private readonly apollo = inject(Apollo); private breadcrumbsService = inject(BreadcrumbService); private jobService = inject(JobsService); private platformId = inject(PLATFORM_ID); - encounterCompetition!: EncounterCompetition; - drawCompetition!: DrawCompetition; - eventCompetition!: EventCompetition; - encounterCompetitionName!: string; + + encounterCompetition = injectRouteData('encounterCompetition'); + drawCompetition = injectRouteData('drawCompetition'); + eventCompetition = injectRouteData('eventCompetition'); + + destroy$ = injectDestroy(); + + encounterCompetitionName = computed(() => { + return `${this.encounterCompetition()?.home?.name} vs ${this.encounterCompetition()?.away?.name}`; + }); get isClient(): boolean { return isPlatformBrowser(this.platformId); } - ngOnInit(): void { - this.route.data.subscribe((data) => { - this.encounterCompetition = data['encounterCompetition']; - this.encounterCompetitionName = `${this.encounterCompetition.home?.name} vs ${this.encounterCompetition.away?.name}`; - this.eventCompetition = data['eventCompetition']; - this.drawCompetition = data['drawCompetition']; - + constructor() { + effect(() => { this.seoService.update({ - title: this.encounterCompetitionName, - description: `Encounter ${this.encounterCompetitionName}`, + title: this.encounterCompetitionName(), + description: `Encounter ${this.encounterCompetitionName()}`, type: 'website', keywords: [ 'encounter', 'competition', 'badminton', - this.encounterCompetition.home?.name ?? '', - this.encounterCompetition.away?.name ?? '', + this.encounterCompetition()?.home?.name ?? '', + this.encounterCompetition()?.away?.name ?? '', ], }); - this.breadcrumbsService.set('@eventCompetition', this.eventCompetition.name ?? ''); - this.breadcrumbsService.set('@drawCompetition', this.drawCompetition.name ?? ''); - this.breadcrumbsService.set('@encounterCompetition', this.encounterCompetitionName); + this.breadcrumbsService.set('@eventCompetition', this.eventCompetition()?.name ?? ''); + this.breadcrumbsService.set('@drawCompetition', this.drawCompetition()?.name ?? ''); + this.breadcrumbsService.set('@encounterCompetition', this.encounterCompetitionName()); }); } getGameLabel(game: number) { - const gameType = this.encounterCompetition.drawCompetition?.subEventCompetition?.eventType as - | 'M' - | 'F' - | 'MX'; + const gameType = this.encounterCompetition()?.drawCompetition?.subEventCompetition?.eventType; if (!gameType) { return []; @@ -125,14 +134,31 @@ export class DetailEncounterComponent implements OnInit { } async syncNotifications() { - if (!this.encounterCompetition.id) { + const id = this.encounterCompetition()?.id; + if (!id) { return; } await lastValueFrom( this.jobService.checkNotifications({ - id: this.encounterCompetition.id, + id: id, }), ); } + + reCalculatePoints() { + this.apollo + .mutate({ + mutation: gql` + mutation RecalculateEncounterCompetitionRankingPoints($encounterId: ID!) { + recalculateEncounterCompetitionRankingPoints(encounterId: $encounterId) + } + `, + variables: { + encounterId: this.encounterCompetition()?.id, + }, + }) + .pipe(take(1)) + .subscribe(); + } } diff --git a/libs/frontend/pages/competition/event/src/pages/detail/detail.page.html b/libs/frontend/pages/competition/event/src/pages/detail/detail.page.html index 6474a6ff5e..83a285fb22 100644 --- a/libs/frontend/pages/competition/event/src/pages/detail/detail.page.html +++ b/libs/frontend/pages/competition/event/src/pages/detail/detail.page.html @@ -11,7 +11,9 @@ open_in_new } - + @@ -29,6 +31,12 @@ } + + +