mirror of https://github.com/elunez/eladmin
re-assign
parent
f910c6590b
commit
9cc769c1e3
|
@ -0,0 +1,2 @@
|
||||||
|
-- Add average_score column to team table
|
||||||
|
ALTER TABLE team ADD COLUMN average_score DOUBLE DEFAULT 0.0;
|
|
@ -159,6 +159,12 @@ public class Event implements Serializable {
|
||||||
inverseJoinColumns = {@JoinColumn(name = "player_id",referencedColumnName = "id")})
|
inverseJoinColumns = {@JoinColumn(name = "player_id",referencedColumnName = "id")})
|
||||||
private List<Player> coHostPlayers = new ArrayList<>();
|
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){
|
public void copy(Event source){
|
||||||
BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
|
BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,10 @@ public class Team implements Serializable {
|
||||||
@Column(name = "team_size")
|
@Column(name = "team_size")
|
||||||
@ApiModelProperty(value = "Team size")
|
@ApiModelProperty(value = "Team size")
|
||||||
private int teamSize;
|
private int teamSize;
|
||||||
|
|
||||||
|
@Column(name = "average_score")
|
||||||
|
@ApiModelProperty(value = "Average team score based on player scores")
|
||||||
|
private Double averageScore = 0.0;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "team")
|
@OneToMany(mappedBy = "team")
|
||||||
@ApiModelProperty(value = "teamPlayers")
|
@ApiModelProperty(value = "teamPlayers")
|
||||||
|
|
|
@ -6,6 +6,7 @@ import lombok.Setter;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@ -31,4 +32,37 @@ public class TeamPlayer implements Serializable {
|
||||||
|
|
||||||
@Column(name = "is_checked_in")
|
@Column(name = "is_checked_in")
|
||||||
private boolean isCheckedIn;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -44,6 +44,9 @@ public class TeamDto implements Serializable {
|
||||||
|
|
||||||
@ApiModelProperty(value = "Team size")
|
@ApiModelProperty(value = "Team size")
|
||||||
private Integer teamSize;
|
private Integer teamSize;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "Average team score based on player scores")
|
||||||
|
private Double averageScore;
|
||||||
|
|
||||||
@ApiModelProperty(value = "Team players")
|
@ApiModelProperty(value = "Team players")
|
||||||
private List<TeamPlayerDto> teamPlayers;
|
private List<TeamPlayerDto> teamPlayers;
|
||||||
|
|
|
@ -19,9 +19,11 @@ import com.srr.domain.Event;
|
||||||
import com.srr.dto.EventDto;
|
import com.srr.dto.EventDto;
|
||||||
import com.srr.dto.EventQueryCriteria;
|
import com.srr.dto.EventQueryCriteria;
|
||||||
import com.srr.dto.JoinEventDto;
|
import com.srr.dto.JoinEventDto;
|
||||||
|
import com.srr.dto.MatchGroupGenerationDto;
|
||||||
import com.srr.dto.TeamPlayerDto;
|
import com.srr.dto.TeamPlayerDto;
|
||||||
import com.srr.enumeration.EventStatus;
|
import com.srr.enumeration.EventStatus;
|
||||||
import com.srr.service.EventService;
|
import com.srr.service.EventService;
|
||||||
|
import com.srr.service.MatchGroupService;
|
||||||
import com.srr.service.TeamPlayerService;
|
import com.srr.service.TeamPlayerService;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
@ -38,7 +40,9 @@ import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Chanheng
|
* @author Chanheng
|
||||||
|
@ -53,6 +57,7 @@ public class EventController {
|
||||||
|
|
||||||
private final EventService eventService;
|
private final EventService eventService;
|
||||||
private final TeamPlayerService teamPlayerService;
|
private final TeamPlayerService teamPlayerService;
|
||||||
|
private final MatchGroupService matchGroupService;
|
||||||
|
|
||||||
@ApiOperation("Export Data")
|
@ApiOperation("Export Data")
|
||||||
@GetMapping(value = "/download")
|
@GetMapping(value = "/download")
|
||||||
|
@ -114,6 +119,20 @@ public class EventController {
|
||||||
public ResponseEntity<List<TeamPlayerDto>> findEventPlayers(@PathVariable("id") Long eventId) {
|
public ResponseEntity<List<TeamPlayerDto>> findEventPlayers(@PathVariable("id") Long eventId) {
|
||||||
return new ResponseEntity<>(teamPlayerService.findByEventId(eventId), HttpStatus.OK);
|
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
|
@DeleteMapping
|
||||||
@Log("Delete event")
|
@Log("Delete event")
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue