re-assign

pull/882/head^2
chanhengseang 2025-05-26 18:29:11 -07:00
parent f910c6590b
commit 9cc769c1e3
9 changed files with 282 additions and 0 deletions

View File

@ -0,0 +1,2 @@
-- Add average_score column to team table
ALTER TABLE team ADD COLUMN average_score DOUBLE DEFAULT 0.0;

View File

@ -159,6 +159,12 @@ public class Event implements Serializable {
inverseJoinColumns = {@JoinColumn(name = "player_id",referencedColumnName = "id")})
private List<Player> coHostPlayers = new ArrayList<>();
@OneToMany(mappedBy = "event")
private List<MatchGroup> matchGroups = new ArrayList<>();
@OneToMany(mappedBy = "event")
private List<Team> teams = new ArrayList<>();
public void copy(Event source){
BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
}

View File

@ -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")

View File

@ -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<TeamPlayer> 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);
}
}

View File

@ -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;
}

View File

@ -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<TeamPlayerDto> teamPlayers;

View File

@ -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<List<TeamPlayerDto>> 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<Object> generateMatchGroups(@Validated @RequestBody MatchGroupGenerationDto dto) {
Integer groupsCreated = matchGroupService.generateMatchGroups(dto);
Map<String, Object> 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")

View File

@ -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);
}

View File

@ -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<Team> 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<MatchGroup> 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<Team> 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<List<Team>> teamGroups = createTeamGroups(sortedTeams, targetGroupCount);
// Create match groups
int groupCount = 0;
for (List<Team> teamGroup : teamGroups) {
if (!teamGroup.isEmpty()) {
createMatchGroup(event, teamGroup, "Group " + (++groupCount), teamGroup.size());
}
}
return groupCount;
}
/**
* Group teams based strictly on their score order
*/
private List<List<Team>> createTeamGroups(List<Team> 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<List<Team>> 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<Team> 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);
}
}
}