diff --git a/README.md b/README.md index 23db48e40..58d5bd50b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![GitHub last commit](https://img.shields.io/github/last-commit/softwaremagico/KendoTournamentManager)](https://github.com/softwaremagico/KendoTournamentManager) [![Issues](https://img.shields.io/github/issues/softwaremagico/KendoTournamentManager.svg)](https://github.com/softwaremagico/KendoTournamentManager/issues) [![CircleCI](https://circleci.com/gh/softwaremagico/KendoTournamentManager.svg?style=shield)](https://circleci.com/gh/softwaremagico/KendoTournamentManager) -[![Time](https://img.shields.io/badge/development-716h-blueviolet.svg)]() +[![Time](https://img.shields.io/badge/development-717.5h-blueviolet.svg)]() [![Powered by](https://img.shields.io/badge/powered%20by%20java-orange.svg?logo=OpenJDK&logoColor=white)]() [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=kendo-tournament-backend&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=kendo-tournament-backend) diff --git a/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/GroupController.java b/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/GroupController.java index b8259fae7..2dfd2f6f8 100644 --- a/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/GroupController.java +++ b/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/GroupController.java @@ -336,4 +336,17 @@ public long delete(TournamentDTO tournamentDTO) { return getProvider().delete(tournamentConverter.reverse(tournamentDTO)); } + public void refreshGroupContent(Integer tournamentId, Integer level) { + final List tournamentGroups = getProvider().getGroups(tournamentProvider.get(tournamentId).orElseThrow(() + -> new TournamentNotFoundException(this.getClass(), "No tournament found with id '" + tournamentId + "'."))); + + //Remove teams assignation. + for (Group group : tournamentGroups) { + if (group.getLevel() >= level && (group.getFights() == null || group.getFights().isEmpty())) { + group.getTeams().clear(); + getProvider().save(group); + } + } + } + } diff --git a/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/tournaments/TreeTournamentHandler.java b/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/tournaments/TreeTournamentHandler.java index 8898e3154..70c4d8af4 100644 --- a/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/tournaments/TreeTournamentHandler.java +++ b/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/tournaments/TreeTournamentHandler.java @@ -367,7 +367,8 @@ private void populateLevel(Tournament tournament, int level) throws LevelNotFini for (GroupLink link : levelLinks) { final List teamsRanking = rankingProvider.getTeamsScoreRanking(link.getSource()); checkDrawScore(link.getSource(), teamsRanking, link.getWinner()); - if (link.getWinner() != null && teamsRanking.get(link.getWinner()) != null && teamsRanking.get(link.getWinner()).getTeam() != null) { + if (link.getWinner() != null && teamsRanking.get(link.getWinner()) != null && teamsRanking.get(link.getWinner()).getTeam() != null + && !link.getDestination().getTeams().contains(teamsRanking.get(link.getWinner()).getTeam())) { link.getDestination().getTeams().add(teamsRanking.get(link.getWinner()).getTeam()); } else { KendoTournamentLogger.warning(this.getClass(), "Missing data for level '{}' population with winner '{}' using ranking:\n\t{}", diff --git a/backend/kendo-tournament-rest/src/main/java/com/softwaremagico/kt/rest/services/GroupServices.java b/backend/kendo-tournament-rest/src/main/java/com/softwaremagico/kt/rest/services/GroupServices.java index 48b6f6a45..4bbd15ad1 100644 --- a/backend/kendo-tournament-rest/src/main/java/com/softwaremagico/kt/rest/services/GroupServices.java +++ b/backend/kendo-tournament-rest/src/main/java/com/softwaremagico/kt/rest/services/GroupServices.java @@ -192,4 +192,16 @@ public byte[] getAllFromTournamentAsPdf(@Parameter(description = "Id of an exist throw new BadRequestException(this.getClass(), e.getMessage()); } } + + + @PreAuthorize("hasAnyAuthority(@securityService.viewerPrivilege, @securityService.editorPrivilege, @securityService.adminPrivilege)") + @Operation(summary = "Recalculate teams and fights from a group that has not started.", security = @SecurityRequirement(name = "bearerAuth")) + @PatchMapping(value = "/refresh/tournaments/{tournamentId}/levels/{level}", produces = {MediaType.APPLICATION_PDF_VALUE, MediaType.APPLICATION_JSON_VALUE}) + public void refreshNonStartedGroups(@Parameter(description = "Id of an existing tournament", required = true) @PathVariable("tournamentId") + Integer tournamentId, + @Parameter(description = "Group level to starting the checks.", required = false) @PathVariable("level") + Integer level, + HttpServletResponse response, HttpServletRequest request) { + getController().refreshGroupContent(tournamentId, level == null ? 0 : level); + } } diff --git a/frontend/src/app/services/group.service.ts b/frontend/src/app/services/group.service.ts index e48392606..16ab8d37e 100644 --- a/frontend/src/app/services/group.service.ts +++ b/frontend/src/app/services/group.service.ts @@ -206,4 +206,17 @@ export class GroupService { }); } + refreshNonStartedGroups(tournamentId: number, level:number): Observable { + const url: string = `${this.baseUrl}/refresh/tournaments/${tournamentId}/levels/${level}`; + return this.http.patch(url, null) + .pipe( + tap({ + next: () => this.loggerService.info(`Refreshing groups from tournament ${tournamentId}`), + error: () => this.systemOverloadService.isBusy.next(false), + complete: () => this.systemOverloadService.isBusy.next(false), + }), + catchError(this.messageService.handleError(`Refreshing groups from tournament ${tournamentId}`)) + ); + } + } diff --git a/frontend/src/app/services/rbac/rbac.activity.ts b/frontend/src/app/services/rbac/rbac.activity.ts index 4e5c28cc2..d2ce9173b 100644 --- a/frontend/src/app/services/rbac/rbac.activity.ts +++ b/frontend/src/app/services/rbac/rbac.activity.ts @@ -71,6 +71,7 @@ export enum RbacActivity { EDIT_GROUP = 'EDIT_GROUP', DELETE_GROUP = 'DELETE_GROUP', SELECT_GROUP = 'SELECT_GROUP', + REFRESH_GROUPS = 'REFRESH_GROUPS', CLEAN_UP_GROUPS = 'CLEAN_UP_GROUPS', DOWNLOAD_GROUPS_PDF = 'DOWNLOAD_GROUPS_PDF', diff --git a/frontend/src/app/views/fight-list/fight-list.component.ts b/frontend/src/app/views/fight-list/fight-list.component.ts index dfe27a8a9..83753712b 100644 --- a/frontend/src/app/views/fight-list/fight-list.component.ts +++ b/frontend/src/app/views/fight-list/fight-list.component.ts @@ -509,6 +509,7 @@ export class FightListComponent extends RbacBasedComponent implements OnInit, On this.groupService.update(this.selectedGroup).subscribe((): void => { this.messageService.infoMessage("fightDeleted"); this.refreshFights(); + this.selectFirstUnfinishedDuel(); }); } } @@ -873,7 +874,7 @@ export class FightListComponent extends RbacBasedComponent implements OnInit, On } for (const duel of fight.duels) { if (!duel.finished) { - this.selectedFight = fight; + this.selectFight(fight); this.selectDuel(duel); return true; } diff --git a/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.html b/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.html index 0c0b8c42d..19237cceb 100644 --- a/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.html +++ b/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.html @@ -41,8 +41,16 @@ matTooltip="{{'delete' | translate}}"> clear + diff --git a/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.scss b/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.scss index 2b0b2a2a3..12cd061c7 100644 --- a/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.scss +++ b/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.scss @@ -21,3 +21,18 @@ height: 75vh; } } + +@media screen and (max-width: 800px) { + .refresh-group { + display: none !important; + } + + .download-divider { + display: none !important; + } + + .download { + display: none !important; + } + +} diff --git a/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.ts b/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.ts index 9380d8426..07cb7d197 100644 --- a/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.ts +++ b/frontend/src/app/views/fight-list/tournament-generator/tournament-generator.component.ts @@ -168,4 +168,10 @@ export class TournamentGeneratorComponent extends RbacBasedComponent implements this.messageService.infoMessage('infoTournamentUpdated'); }); } + + refreshGroups() { + this.groupService.refreshNonStartedGroups(this.tournamentId, 1).subscribe((): void => { + this.tournamentBracketsEditorComponent.updateData(true, true); + }); + } } diff --git a/frontend/src/assets/i18n/ca.json b/frontend/src/assets/i18n/ca.json index 7de8f477a..0097256f6 100644 --- a/frontend/src/assets/i18n/ca.json +++ b/frontend/src/assets/i18n/ca.json @@ -745,5 +745,6 @@ "darkMode": "Mode Fosc", "lightMode": "Mode Clar", "newVersionAvailable": "La versió '{{newVersion}}' està disponible! La versió actual és '{{currentVersion}}'.", - "input_data_is_invalid": "Les dades proporcionades no són vàlides!" + "input_data_is_invalid": "Les dades proporcionades no són vàlides!", + "refreshStructure": "Corrige la estructura de los grupos que no tienen ningún combate asignado." } diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 934f26c37..a2401d0ae 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -723,5 +723,6 @@ "darkMode": "Donkere Modus", "lightMode": "Lichte Modus", "newVersionAvailable": "Version '{{newVersion}}' ist verfügbar! Aktuelle Version ist '{{currentVersion}}'.", - "input_data_is_invalid": "Die angegebenen Daten sind ungültig!" + "input_data_is_invalid": "Die angegebenen Daten sind ungültig!", + "refreshStructure": "Korrigieren Sie die Struktur der Gruppen, denen keine Kampfeinsätze zugewiesen sind." } diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json index bcba9cc63..88f263be8 100644 --- a/frontend/src/assets/i18n/en.json +++ b/frontend/src/assets/i18n/en.json @@ -740,5 +740,6 @@ "darkMode": "Dark Mode", "lightMode": "Light Mode", "newVersionAvailable": "Version '{{newVersion}}' is available! Current version is '{{currentVersion}}'", - "input_data_is_invalid": "Provided data are invalid!" + "input_data_is_invalid": "Provided data are invalid!", + "refreshStructure": "Correct the structure of groups that do not have any assigned fight." } diff --git a/frontend/src/assets/i18n/es.json b/frontend/src/assets/i18n/es.json index fb61a1ae5..3dca1bf35 100644 --- a/frontend/src/assets/i18n/es.json +++ b/frontend/src/assets/i18n/es.json @@ -741,5 +741,6 @@ "darkMode": "Modo Oscuro", "lightMode": "Modo Claro", "newVersionAvailable": "¡La versión '{{newVersion}}' está disponible! La versión actual es '{{currentVersion}}'.", - "input_data_is_invalid": "¡Los datos proporcionados no son válidos!" + "input_data_is_invalid": "¡Los datos proporcionados no son válidos!", + "refreshStructure": "Corrige la estructura de los grupos que no tienen ningún combate asignado." } diff --git a/frontend/src/assets/i18n/it.json b/frontend/src/assets/i18n/it.json index eb259a9f9..5a019af68 100644 --- a/frontend/src/assets/i18n/it.json +++ b/frontend/src/assets/i18n/it.json @@ -729,5 +729,6 @@ "darkMode": "Modalità Scura", "lightMode": "Modalità Chiara", "newVersionAvailable": "La versione '{{newVersion}}' è disponibile! La versione attuale è '{{currentVersion}}'.", - "input_data_is_invalid": "I dati forniti non sono validi!" + "input_data_is_invalid": "I dati forniti non sono validi!", + "refreshStructure": "Correggere la struttura dei gruppi a cui non è assegnato alcun combattimento." } diff --git a/frontend/src/assets/i18n/nl.json b/frontend/src/assets/i18n/nl.json index 3bcedcc42..79fc386a6 100644 --- a/frontend/src/assets/i18n/nl.json +++ b/frontend/src/assets/i18n/nl.json @@ -723,5 +723,6 @@ "darkMode": "Dunkler Modus", "lightMode": "Heller Modus", "newVersionAvailable": "Versie '{{newVersion}}' is beschikbaar! Huidige versie is '{{currentVersion}}'.", - "input_data_is_invalid": "De opgegeven gegevens zijn ongeldig!" + "input_data_is_invalid": "De opgegeven gegevens zijn ongeldig!", + "refreshStructure": "Verbeter de structuur van groepen waaraan geen gevechtstaken zijn toegewezen." }