From 707cd002f820bfc0c3b3731fb19bc1c4dbe67c1a Mon Sep 17 00:00:00 2001 From: chanhengseang Date: Mon, 26 May 2025 17:56:59 -0700 Subject: [PATCH 1/4] checkin --- .../srr/repository/TeamPlayerRepository.java | 2 + .../com/srr/rest/TeamPlayerController.java | 69 ++++++++++++++++++ .../com/srr/service/TeamPlayerService.java | 52 ++++++++++++++ .../service/impl/TeamPlayerServiceImpl.java | 71 +++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 sport/src/main/java/com/srr/rest/TeamPlayerController.java create mode 100644 sport/src/main/java/com/srr/service/TeamPlayerService.java create mode 100644 sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java diff --git a/sport/src/main/java/com/srr/repository/TeamPlayerRepository.java b/sport/src/main/java/com/srr/repository/TeamPlayerRepository.java index b833aeed..f6414f88 100644 --- a/sport/src/main/java/com/srr/repository/TeamPlayerRepository.java +++ b/sport/src/main/java/com/srr/repository/TeamPlayerRepository.java @@ -26,4 +26,6 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor; **/ public interface TeamPlayerRepository extends JpaRepository, JpaSpecificationExecutor { boolean existsByTeamIdAndPlayerId(Long teamId, Long playerId); + + TeamPlayer findByTeamIdAndPlayerId(Long teamId, Long playerId); } diff --git a/sport/src/main/java/com/srr/rest/TeamPlayerController.java b/sport/src/main/java/com/srr/rest/TeamPlayerController.java new file mode 100644 index 00000000..a92563e9 --- /dev/null +++ b/sport/src/main/java/com/srr/rest/TeamPlayerController.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.srr.rest; + +import com.srr.dto.TeamPlayerDto; +import com.srr.service.TeamPlayerService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import me.zhengjie.annotation.Log; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +/** + * @author Chanheng + * @website https://eladmin.vip + * @date 2025-05-26 + **/ +@RestController +@RequiredArgsConstructor +@Api(tags = "Team Player Management") +@RequestMapping("/api/team-player") +public class TeamPlayerController { + + private final TeamPlayerService teamPlayerService; + + @GetMapping("/{id}") + @ApiOperation("Get team player details") + @PreAuthorize("@el.check('event:list')") + public ResponseEntity getTeamPlayer(@PathVariable Long id) { + return new ResponseEntity<>(teamPlayerService.findById(id), HttpStatus.OK); + } + + @PutMapping("/{id}/check-in") + @Log("Check in player") + @ApiOperation("Check in player for an event") + @PreAuthorize("@el.check('event:edit')") + public ResponseEntity checkIn(@PathVariable Long id) { + return new ResponseEntity<>(teamPlayerService.checkIn(id), HttpStatus.OK); + } + + @GetMapping("/find") + @ApiOperation("Find team player by team and player IDs") + @PreAuthorize("@el.check('event:list')") + public ResponseEntity findByTeamAndPlayer( + @RequestParam Long teamId, + @RequestParam Long playerId) { + TeamPlayerDto teamPlayer = teamPlayerService.findByTeamIdAndPlayerId(teamId, playerId); + if (teamPlayer == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(teamPlayer, HttpStatus.OK); + } +} diff --git a/sport/src/main/java/com/srr/service/TeamPlayerService.java b/sport/src/main/java/com/srr/service/TeamPlayerService.java new file mode 100644 index 00000000..303f1872 --- /dev/null +++ b/sport/src/main/java/com/srr/service/TeamPlayerService.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.srr.service; + +import com.srr.domain.TeamPlayer; +import com.srr.dto.TeamPlayerDto; +import org.springframework.data.domain.Pageable; +import me.zhengjie.utils.PageResult; + +/** + * @author Chanheng + * @website https://eladmin.vip + * @description Service interface for TeamPlayer + * @date 2025-05-26 + **/ +public interface TeamPlayerService { + + /** + * Get a specific TeamPlayer by ID + * @param id TeamPlayer ID + * @return TeamPlayerDto + */ + TeamPlayerDto findById(Long id); + + /** + * Check in a player for an event + * @param id TeamPlayer ID + * @return The updated TeamPlayerDto + */ + TeamPlayerDto checkIn(Long id); + + /** + * Find TeamPlayer by teamId and playerId + * @param teamId the team ID + * @param playerId the player ID + * @return TeamPlayerDto if found, null otherwise + */ + TeamPlayerDto findByTeamIdAndPlayerId(Long teamId, Long playerId); +} diff --git a/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java b/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java new file mode 100644 index 00000000..a67c0d78 --- /dev/null +++ b/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java @@ -0,0 +1,71 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.srr.service.impl; + +import com.srr.domain.TeamPlayer; +import com.srr.dto.TeamPlayerDto; +import com.srr.dto.mapstruct.TeamPlayerMapper; +import com.srr.repository.TeamPlayerRepository; +import com.srr.service.TeamPlayerService; +import lombok.RequiredArgsConstructor; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.exception.EntityNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author Chanheng + * @website https://eladmin.vip + * @date 2025-05-26 + **/ +@Service +@RequiredArgsConstructor +public class TeamPlayerServiceImpl implements TeamPlayerService { + + private final TeamPlayerRepository teamPlayerRepository; + private final TeamPlayerMapper teamPlayerMapper; + + @Override + @Transactional(readOnly = true) + public TeamPlayerDto findById(Long id) { + TeamPlayer teamPlayer = teamPlayerRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException(TeamPlayer.class, "id", id.toString())); + return teamPlayerMapper.toDto(teamPlayer); + } + + @Override + @Transactional + public TeamPlayerDto checkIn(Long id) { + TeamPlayer teamPlayer = teamPlayerRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException(TeamPlayer.class, "id", id.toString())); + + if (teamPlayer.isCheckedIn()) { + throw new BadRequestException("Player is already checked in"); + } + + teamPlayer.setCheckedIn(true); + teamPlayerRepository.save(teamPlayer); + + return teamPlayerMapper.toDto(teamPlayer); + } + + @Override + @Transactional(readOnly = true) + public TeamPlayerDto findByTeamIdAndPlayerId(Long teamId, Long playerId) { + TeamPlayer teamPlayer = teamPlayerRepository.findByTeamIdAndPlayerId(teamId, playerId); + return teamPlayer != null ? teamPlayerMapper.toDto(teamPlayer) : null; + } +} From d181fb2e639ccd4a339e4bdbdc415025bf88e4d0 Mon Sep 17 00:00:00 2001 From: chanhengseang Date: Mon, 26 May 2025 18:01:08 -0700 Subject: [PATCH 2/4] team pl --- .../com/srr/repository/TeamPlayerRepository.java | 7 +++++++ .../src/main/java/com/srr/rest/EventController.java | 11 +++++++++++ .../java/com/srr/rest/TeamPlayerController.java | 13 ------------- .../java/com/srr/service/TeamPlayerService.java | 11 ++++++----- .../com/srr/service/impl/TeamPlayerServiceImpl.java | 11 ++++++++--- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/sport/src/main/java/com/srr/repository/TeamPlayerRepository.java b/sport/src/main/java/com/srr/repository/TeamPlayerRepository.java index f6414f88..881b4972 100644 --- a/sport/src/main/java/com/srr/repository/TeamPlayerRepository.java +++ b/sport/src/main/java/com/srr/repository/TeamPlayerRepository.java @@ -18,6 +18,10 @@ package com.srr.repository; import com.srr.domain.TeamPlayer; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; /** * @website https://eladmin.vip @@ -28,4 +32,7 @@ public interface TeamPlayerRepository extends JpaRepository, J boolean existsByTeamIdAndPlayerId(Long teamId, Long playerId); TeamPlayer findByTeamIdAndPlayerId(Long teamId, Long playerId); + + @Query("SELECT tp FROM TeamPlayer tp JOIN tp.team t JOIN t.event e WHERE e.id = :eventId") + List findByEventId(@Param("eventId") Long eventId); } diff --git a/sport/src/main/java/com/srr/rest/EventController.java b/sport/src/main/java/com/srr/rest/EventController.java index 695509f4..7b7219ce 100644 --- a/sport/src/main/java/com/srr/rest/EventController.java +++ b/sport/src/main/java/com/srr/rest/EventController.java @@ -19,8 +19,10 @@ import com.srr.domain.Event; import com.srr.dto.EventDto; import com.srr.dto.EventQueryCriteria; import com.srr.dto.JoinEventDto; +import com.srr.dto.TeamPlayerDto; import com.srr.enumeration.EventStatus; import com.srr.service.EventService; +import com.srr.service.TeamPlayerService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -36,6 +38,7 @@ import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.List; /** * @author Chanheng @@ -49,6 +52,7 @@ import java.io.IOException; public class EventController { private final EventService eventService; + private final TeamPlayerService teamPlayerService; @ApiOperation("Export Data") @GetMapping(value = "/download") @@ -104,6 +108,13 @@ public class EventController { return new ResponseEntity<>(result, HttpStatus.OK); } + @GetMapping("/{id}/players") + @ApiOperation("Find all team players in an event") + @PreAuthorize("@el.check('event:list')") + public ResponseEntity> findEventPlayers(@PathVariable("id") Long eventId) { + return new ResponseEntity<>(teamPlayerService.findByEventId(eventId), HttpStatus.OK); + } + @DeleteMapping @Log("Delete event") @ApiOperation("Delete event") diff --git a/sport/src/main/java/com/srr/rest/TeamPlayerController.java b/sport/src/main/java/com/srr/rest/TeamPlayerController.java index a92563e9..5560e7f6 100644 --- a/sport/src/main/java/com/srr/rest/TeamPlayerController.java +++ b/sport/src/main/java/com/srr/rest/TeamPlayerController.java @@ -53,17 +53,4 @@ public class TeamPlayerController { public ResponseEntity checkIn(@PathVariable Long id) { return new ResponseEntity<>(teamPlayerService.checkIn(id), HttpStatus.OK); } - - @GetMapping("/find") - @ApiOperation("Find team player by team and player IDs") - @PreAuthorize("@el.check('event:list')") - public ResponseEntity findByTeamAndPlayer( - @RequestParam Long teamId, - @RequestParam Long playerId) { - TeamPlayerDto teamPlayer = teamPlayerService.findByTeamIdAndPlayerId(teamId, playerId); - if (teamPlayer == null) { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } - return new ResponseEntity<>(teamPlayer, HttpStatus.OK); - } } diff --git a/sport/src/main/java/com/srr/service/TeamPlayerService.java b/sport/src/main/java/com/srr/service/TeamPlayerService.java index 303f1872..51632fba 100644 --- a/sport/src/main/java/com/srr/service/TeamPlayerService.java +++ b/sport/src/main/java/com/srr/service/TeamPlayerService.java @@ -20,6 +20,8 @@ import com.srr.dto.TeamPlayerDto; import org.springframework.data.domain.Pageable; import me.zhengjie.utils.PageResult; +import java.util.List; + /** * @author Chanheng * @website https://eladmin.vip @@ -43,10 +45,9 @@ public interface TeamPlayerService { TeamPlayerDto checkIn(Long id); /** - * Find TeamPlayer by teamId and playerId - * @param teamId the team ID - * @param playerId the player ID - * @return TeamPlayerDto if found, null otherwise + * Find all TeamPlayers by event ID + * @param eventId the event ID + * @return List of TeamPlayerDto objects */ - TeamPlayerDto findByTeamIdAndPlayerId(Long teamId, Long playerId); + List findByEventId(Long eventId); } diff --git a/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java b/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java index a67c0d78..5e395946 100644 --- a/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java +++ b/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java @@ -26,6 +26,9 @@ import me.zhengjie.exception.EntityNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + /** * @author Chanheng * @website https://eladmin.vip @@ -64,8 +67,10 @@ public class TeamPlayerServiceImpl implements TeamPlayerService { @Override @Transactional(readOnly = true) - public TeamPlayerDto findByTeamIdAndPlayerId(Long teamId, Long playerId) { - TeamPlayer teamPlayer = teamPlayerRepository.findByTeamIdAndPlayerId(teamId, playerId); - return teamPlayer != null ? teamPlayerMapper.toDto(teamPlayer) : null; + public List findByEventId(Long eventId) { + List teamPlayers = teamPlayerRepository.findByEventId(eventId); + return teamPlayers.stream() + .map(teamPlayerMapper::toDto) + .collect(Collectors.toList()); } } From f910c6590b5a852ee65a7069a7aed5cd5abce640 Mon Sep 17 00:00:00 2001 From: chanhengseang Date: Mon, 26 May 2025 18:05:37 -0700 Subject: [PATCH 3/4] re-assign --- .../com/srr/dto/TeamPlayerReassignDto.java | 38 +++++++++++++++++ .../com/srr/rest/TeamPlayerController.java | 10 +++++ .../com/srr/service/TeamPlayerService.java | 8 ++++ .../service/impl/TeamPlayerServiceImpl.java | 41 +++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 sport/src/main/java/com/srr/dto/TeamPlayerReassignDto.java diff --git a/sport/src/main/java/com/srr/dto/TeamPlayerReassignDto.java b/sport/src/main/java/com/srr/dto/TeamPlayerReassignDto.java new file mode 100644 index 00000000..4fad79c3 --- /dev/null +++ b/sport/src/main/java/com/srr/dto/TeamPlayerReassignDto.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.srr.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author Chanheng + * @website https://eladmin.vip + * @date 2025-05-26 + **/ +@Data +public class TeamPlayerReassignDto { + + @NotNull + @ApiModelProperty(value = "Team player ID to reassign") + private Long teamPlayerId; + + @NotNull + @ApiModelProperty(value = "Target team ID") + private Long targetTeamId; +} diff --git a/sport/src/main/java/com/srr/rest/TeamPlayerController.java b/sport/src/main/java/com/srr/rest/TeamPlayerController.java index 5560e7f6..bad7d72c 100644 --- a/sport/src/main/java/com/srr/rest/TeamPlayerController.java +++ b/sport/src/main/java/com/srr/rest/TeamPlayerController.java @@ -16,6 +16,7 @@ package com.srr.rest; import com.srr.dto.TeamPlayerDto; +import com.srr.dto.TeamPlayerReassignDto; import com.srr.service.TeamPlayerService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -24,6 +25,7 @@ import me.zhengjie.annotation.Log; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; /** @@ -53,4 +55,12 @@ public class TeamPlayerController { public ResponseEntity checkIn(@PathVariable Long id) { return new ResponseEntity<>(teamPlayerService.checkIn(id), HttpStatus.OK); } + + @PostMapping("/reassign") + @Log("Reassign player to another team") + @ApiOperation("Reassign player to another team") + @PreAuthorize("@el.check('event:admin')") + public ResponseEntity reassignPlayer(@Validated @RequestBody TeamPlayerReassignDto dto) { + return new ResponseEntity<>(teamPlayerService.reassignPlayer(dto), HttpStatus.OK); + } } diff --git a/sport/src/main/java/com/srr/service/TeamPlayerService.java b/sport/src/main/java/com/srr/service/TeamPlayerService.java index 51632fba..5da3698d 100644 --- a/sport/src/main/java/com/srr/service/TeamPlayerService.java +++ b/sport/src/main/java/com/srr/service/TeamPlayerService.java @@ -17,6 +17,7 @@ package com.srr.service; import com.srr.domain.TeamPlayer; import com.srr.dto.TeamPlayerDto; +import com.srr.dto.TeamPlayerReassignDto; import org.springframework.data.domain.Pageable; import me.zhengjie.utils.PageResult; @@ -50,4 +51,11 @@ public interface TeamPlayerService { * @return List of TeamPlayerDto objects */ List findByEventId(Long eventId); + + /** + * Reassign a player from one team to another + * @param dto Contains teamPlayerId and targetTeamId + * @return The updated TeamPlayerDto + */ + TeamPlayerDto reassignPlayer(TeamPlayerReassignDto dto); } diff --git a/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java b/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java index 5e395946..0c9da130 100644 --- a/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java +++ b/sport/src/main/java/com/srr/service/impl/TeamPlayerServiceImpl.java @@ -15,10 +15,13 @@ */ package com.srr.service.impl; +import com.srr.domain.Team; import com.srr.domain.TeamPlayer; import com.srr.dto.TeamPlayerDto; +import com.srr.dto.TeamPlayerReassignDto; import com.srr.dto.mapstruct.TeamPlayerMapper; import com.srr.repository.TeamPlayerRepository; +import com.srr.repository.TeamRepository; import com.srr.service.TeamPlayerService; import lombok.RequiredArgsConstructor; import me.zhengjie.exception.BadRequestException; @@ -39,6 +42,7 @@ import java.util.stream.Collectors; public class TeamPlayerServiceImpl implements TeamPlayerService { private final TeamPlayerRepository teamPlayerRepository; + private final TeamRepository teamRepository; private final TeamPlayerMapper teamPlayerMapper; @Override @@ -73,4 +77,41 @@ public class TeamPlayerServiceImpl implements TeamPlayerService { .map(teamPlayerMapper::toDto) .collect(Collectors.toList()); } + + @Override + @Transactional + public TeamPlayerDto reassignPlayer(TeamPlayerReassignDto dto) { + // Find the team player to reassign + TeamPlayer teamPlayer = teamPlayerRepository.findById(dto.getTeamPlayerId()) + .orElseThrow(() -> new EntityNotFoundException(TeamPlayer.class, "id", dto.getTeamPlayerId().toString())); + + // Find the target team + Team targetTeam = teamRepository.findById(dto.getTargetTeamId()) + .orElseThrow(() -> new EntityNotFoundException(Team.class, "id", dto.getTargetTeamId().toString())); + + // Store the original team for potential deletion + Team originalTeam = teamPlayer.getTeam(); + + // Check if target team is in the same event as the original team + if (!originalTeam.getEvent().getId().equals(targetTeam.getEvent().getId())) { + throw new BadRequestException("Cannot reassign player to a team in a different event"); + } + + // Check if the target team is full + if (targetTeam.getTeamPlayers().size() >= targetTeam.getTeamSize()) { + throw new BadRequestException("Target team is already full"); + } + + // Reassign the player to the new team + teamPlayer.setTeam(targetTeam); + teamPlayerRepository.save(teamPlayer); + + // Check if original team is now empty, and delete if it is + if (originalTeam.getTeamPlayers().stream() + .noneMatch(tp -> !tp.getId().equals(teamPlayer.getId()))) { + teamRepository.delete(originalTeam); + } + + return teamPlayerMapper.toDto(teamPlayer); + } } From 9cc769c1e39f3f8cda8b71a2df91708858848662 Mon Sep 17 00:00:00 2001 From: chanhengseang Date: Mon, 26 May 2025 18:29:11 -0700 Subject: [PATCH 4/4] re-assign --- .../V14__Add_Average_Score_To_Team.sql | 2 + sport/src/main/java/com/srr/domain/Event.java | 6 + sport/src/main/java/com/srr/domain/Team.java | 4 + .../main/java/com/srr/domain/TeamPlayer.java | 34 ++++ .../com/srr/dto/MatchGroupGenerationDto.java | 34 ++++ sport/src/main/java/com/srr/dto/TeamDto.java | 3 + .../java/com/srr/rest/EventController.java | 19 +++ .../com/srr/service/MatchGroupService.java | 34 ++++ .../service/impl/MatchGroupServiceImpl.java | 146 ++++++++++++++++++ 9 files changed, 282 insertions(+) create mode 100644 eladmin-system/src/main/resources/db/migration/V14__Add_Average_Score_To_Team.sql create mode 100644 sport/src/main/java/com/srr/dto/MatchGroupGenerationDto.java create mode 100644 sport/src/main/java/com/srr/service/MatchGroupService.java create mode 100644 sport/src/main/java/com/srr/service/impl/MatchGroupServiceImpl.java diff --git a/eladmin-system/src/main/resources/db/migration/V14__Add_Average_Score_To_Team.sql b/eladmin-system/src/main/resources/db/migration/V14__Add_Average_Score_To_Team.sql new file mode 100644 index 00000000..1b012acc --- /dev/null +++ b/eladmin-system/src/main/resources/db/migration/V14__Add_Average_Score_To_Team.sql @@ -0,0 +1,2 @@ +-- Add average_score column to team table +ALTER TABLE team ADD COLUMN average_score DOUBLE DEFAULT 0.0; diff --git a/sport/src/main/java/com/srr/domain/Event.java b/sport/src/main/java/com/srr/domain/Event.java index 57ff4a7d..bb341a19 100644 --- a/sport/src/main/java/com/srr/domain/Event.java +++ b/sport/src/main/java/com/srr/domain/Event.java @@ -159,6 +159,12 @@ public class Event implements Serializable { inverseJoinColumns = {@JoinColumn(name = "player_id",referencedColumnName = "id")}) private List coHostPlayers = new ArrayList<>(); + @OneToMany(mappedBy = "event") + private List matchGroups = new ArrayList<>(); + + @OneToMany(mappedBy = "event") + private List teams = new ArrayList<>(); + public void copy(Event source){ BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); } diff --git a/sport/src/main/java/com/srr/domain/Team.java b/sport/src/main/java/com/srr/domain/Team.java index 488fd593..6a915dcd 100644 --- a/sport/src/main/java/com/srr/domain/Team.java +++ b/sport/src/main/java/com/srr/domain/Team.java @@ -35,6 +35,10 @@ public class Team implements Serializable { @Column(name = "team_size") @ApiModelProperty(value = "Team size") private int teamSize; + + @Column(name = "average_score") + @ApiModelProperty(value = "Average team score based on player scores") + private Double averageScore = 0.0; @OneToMany(mappedBy = "team") @ApiModelProperty(value = "teamPlayers") diff --git a/sport/src/main/java/com/srr/domain/TeamPlayer.java b/sport/src/main/java/com/srr/domain/TeamPlayer.java index 88c6d1d7..61acb544 100644 --- a/sport/src/main/java/com/srr/domain/TeamPlayer.java +++ b/sport/src/main/java/com/srr/domain/TeamPlayer.java @@ -6,6 +6,7 @@ import lombok.Setter; import javax.persistence.*; import java.io.Serializable; +import java.util.List; @Getter @Setter @@ -31,4 +32,37 @@ public class TeamPlayer implements Serializable { @Column(name = "is_checked_in") private boolean isCheckedIn; + + @PrePersist + @PreUpdate + public void updateTeamScore() { + if (team != null) { + calculateAndUpdateTeamScore(team); + } + } + + /** + * Calculates and updates the average score for the team + * @param team The team to update the score for + */ + private void calculateAndUpdateTeamScore(Team team) { + List players = team.getTeamPlayers(); + if (players == null || players.isEmpty()) { + team.setAverageScore(0.0); + return; + } + + double totalScore = 0; + int playerCount = 0; + + for (TeamPlayer player : players) { + if (player.getScore() != null) { + totalScore += player.getScore(); + playerCount++; + } + } + + double averageScore = playerCount > 0 ? totalScore / playerCount : 0; + team.setAverageScore(averageScore); + } } diff --git a/sport/src/main/java/com/srr/dto/MatchGroupGenerationDto.java b/sport/src/main/java/com/srr/dto/MatchGroupGenerationDto.java new file mode 100644 index 00000000..830bb7f7 --- /dev/null +++ b/sport/src/main/java/com/srr/dto/MatchGroupGenerationDto.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.srr.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * @author Chanheng + * @website https://eladmin.vip + * @date 2025-05-26 + **/ +@Data +public class MatchGroupGenerationDto { + + @NotNull + @ApiModelProperty(value = "Event ID") + private Long eventId; +} diff --git a/sport/src/main/java/com/srr/dto/TeamDto.java b/sport/src/main/java/com/srr/dto/TeamDto.java index b3850122..ded4f069 100644 --- a/sport/src/main/java/com/srr/dto/TeamDto.java +++ b/sport/src/main/java/com/srr/dto/TeamDto.java @@ -44,6 +44,9 @@ public class TeamDto implements Serializable { @ApiModelProperty(value = "Team size") private Integer teamSize; + + @ApiModelProperty(value = "Average team score based on player scores") + private Double averageScore; @ApiModelProperty(value = "Team players") private List teamPlayers; diff --git a/sport/src/main/java/com/srr/rest/EventController.java b/sport/src/main/java/com/srr/rest/EventController.java index 7b7219ce..c4a9f89e 100644 --- a/sport/src/main/java/com/srr/rest/EventController.java +++ b/sport/src/main/java/com/srr/rest/EventController.java @@ -19,9 +19,11 @@ import com.srr.domain.Event; import com.srr.dto.EventDto; import com.srr.dto.EventQueryCriteria; import com.srr.dto.JoinEventDto; +import com.srr.dto.MatchGroupGenerationDto; import com.srr.dto.TeamPlayerDto; import com.srr.enumeration.EventStatus; import com.srr.service.EventService; +import com.srr.service.MatchGroupService; import com.srr.service.TeamPlayerService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -38,7 +40,9 @@ import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author Chanheng @@ -53,6 +57,7 @@ public class EventController { private final EventService eventService; private final TeamPlayerService teamPlayerService; + private final MatchGroupService matchGroupService; @ApiOperation("Export Data") @GetMapping(value = "/download") @@ -114,6 +119,20 @@ public class EventController { public ResponseEntity> findEventPlayers(@PathVariable("id") Long eventId) { return new ResponseEntity<>(teamPlayerService.findByEventId(eventId), HttpStatus.OK); } + + @PostMapping("/generate-groups") + @Log("Generate match groups") + @ApiOperation("Generate match groups based on team scores") + @PreAuthorize("@el.check('event:admin')") + public ResponseEntity generateMatchGroups(@Validated @RequestBody MatchGroupGenerationDto dto) { + Integer groupsCreated = matchGroupService.generateMatchGroups(dto); + + Map result = new HashMap<>(); + result.put("groupsCreated", groupsCreated); + result.put("message", "Successfully created " + groupsCreated + " match groups based on team scores"); + + return new ResponseEntity<>(result, HttpStatus.OK); + } @DeleteMapping @Log("Delete event") diff --git a/sport/src/main/java/com/srr/service/MatchGroupService.java b/sport/src/main/java/com/srr/service/MatchGroupService.java new file mode 100644 index 00000000..0402c1bf --- /dev/null +++ b/sport/src/main/java/com/srr/service/MatchGroupService.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.srr.service; + +import com.srr.dto.MatchGroupGenerationDto; +import java.util.List; + +/** + * @author Chanheng + * @website https://eladmin.vip + * @date 2025-05-26 + **/ +public interface MatchGroupService { + + /** + * Generate match groups for teams in an event based on score similarity + * @param dto parameters for group generation + * @return Number of groups created + */ + Integer generateMatchGroups(MatchGroupGenerationDto dto); +} diff --git a/sport/src/main/java/com/srr/service/impl/MatchGroupServiceImpl.java b/sport/src/main/java/com/srr/service/impl/MatchGroupServiceImpl.java new file mode 100644 index 00000000..4bc3831c --- /dev/null +++ b/sport/src/main/java/com/srr/service/impl/MatchGroupServiceImpl.java @@ -0,0 +1,146 @@ +/* + * Copyright 2019-2025 Zheng Jie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.srr.service.impl; + +import com.srr.domain.Event; +import com.srr.domain.MatchGroup; +import com.srr.domain.Team; +import com.srr.dto.MatchGroupGenerationDto; +import com.srr.repository.EventRepository; +import com.srr.repository.MatchGroupRepository; +import com.srr.repository.TeamRepository; +import com.srr.service.MatchGroupService; +import lombok.RequiredArgsConstructor; +import me.zhengjie.exception.BadRequestException; +import me.zhengjie.exception.EntityNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Chanheng + * @website https://eladmin.vip + * @date 2025-05-26 + **/ +@Service +@RequiredArgsConstructor +public class MatchGroupServiceImpl implements MatchGroupService { + + private final EventRepository eventRepository; + private final TeamRepository teamRepository; + private final MatchGroupRepository matchGroupRepository; + + @Override + @Transactional + public Integer generateMatchGroups(MatchGroupGenerationDto dto) { + // Find the event + Event event = eventRepository.findById(dto.getEventId()) + .orElseThrow(() -> new EntityNotFoundException(Event.class, "id", dto.getEventId().toString())); + + // Check if the event has a group count + if (event.getGroupCount() == null || event.getGroupCount() <= 0) { + throw new BadRequestException("Event has no valid group count defined"); + } + + // Get all teams for the event + List teams = event.getTeams(); + + if (teams.isEmpty()) { + throw new BadRequestException("No teams found for event with ID: " + dto.getEventId()); + } + + // Clear existing match groups for this event + List existingGroups = event.getMatchGroups(); + if (existingGroups != null && !existingGroups.isEmpty()) { + for (MatchGroup group : existingGroups) { + // Detach teams from groups + for (Team team : group.getTeams()) { + team.setMatchGroup(null); + } + matchGroupRepository.delete(group); + } + } + + // Sort teams by their average score (which is now stored on the Team entity) + List sortedTeams = teams.stream() + .sorted(Comparator.comparing(Team::getAverageScore, Comparator.nullsLast(Comparator.naturalOrder()))) + .collect(Collectors.toList()); + + // Group teams based on their sorted order and the target group count + int targetGroupCount = event.getGroupCount(); + List> teamGroups = createTeamGroups(sortedTeams, targetGroupCount); + + // Create match groups + int groupCount = 0; + for (List teamGroup : teamGroups) { + if (!teamGroup.isEmpty()) { + createMatchGroup(event, teamGroup, "Group " + (++groupCount), teamGroup.size()); + } + } + + return groupCount; + } + + /** + * Group teams based strictly on their score order + */ + private List> createTeamGroups(List sortedTeams, int targetGroupCount) { + int totalTeams = sortedTeams.size(); + + // Don't create more groups than we have teams + int actualGroupCount = Math.min(targetGroupCount, totalTeams); + + // Initialize the groups + List> groups = new ArrayList<>(actualGroupCount); + for (int i = 0; i < actualGroupCount; i++) { + groups.add(new ArrayList<>()); + } + + // Distribute teams to groups in a round-robin fashion based on their sorted order + // Teams with similar scores will naturally be placed in different groups + for (int i = 0; i < sortedTeams.size(); i++) { + Team team = sortedTeams.get(i); + // Use modulo to distribute teams evenly among groups + int groupIndex = i % actualGroupCount; + groups.get(groupIndex).add(team); + } + + return groups; + } + + /** + * Create a match group from a list of teams + */ + private void createMatchGroup(Event event, List teams, String name, int groupTeamSize) { + MatchGroup matchGroup = new MatchGroup(); + matchGroup.setName(name); + matchGroup.setEvent(event); + matchGroup.setGroupTeamSize(groupTeamSize); + + // Save the match group first + matchGroup = matchGroupRepository.save(matchGroup); + + // Update the teams with the match group + for (Team team : teams) { + team.setMatchGroup(matchGroup); + teamRepository.save(team); + } + } +}