feat: 集体项目按总成员数拆分而非队伍数

- 修改 ParticipantGroupingService 按场地容量(maxParticipants)判断拆分
- 当所有队伍的总成员数超过场地容量时自动拆分成多组
- 新增 getTeamMemberCount 和 calculateTotalMembers 方法

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
2026-01-23 16:38:49 +08:00
co-authored by factory-droid[bot]
parent 2e8dc9bb77
commit 57806172d4
@@ -4,10 +4,15 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.modules.martial.config.ScheduleConfig;
import org.springblade.modules.martial.mapper.MartialProjectMapper;
import org.springblade.modules.martial.mapper.MartialTeamMapper;
import org.springblade.modules.martial.mapper.MartialTeamMemberMapper;
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
import org.springblade.modules.martial.pojo.entity.MartialProject;
import org.springblade.modules.martial.pojo.entity.MartialTeam;
import org.springblade.modules.martial.pojo.entity.MartialTeamMember;
import org.springblade.modules.martial.service.schedule.model.ScheduleGroupData;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import java.util.*;
import java.util.stream.Collectors;
@@ -15,8 +20,6 @@ import java.util.stream.Collectors;
/**
* Participant grouping service
* Groups athletes by project and category for schedule arrangement
*
* @author Refactored from MartialScheduleArrangeServiceImpl
*/
@Slf4j
@Service
@@ -24,11 +27,10 @@ import java.util.stream.Collectors;
public class ParticipantGroupingService {
private final MartialProjectMapper projectMapper;
private final MartialTeamMapper teamMapper;
private final MartialTeamMemberMapper teamMemberMapper;
private final ScheduleConfig scheduleConfig;
/**
* Auto group participants by project and category
*/
public List<ScheduleGroupData> autoGroup(List<MartialAthlete> athletes) {
List<ScheduleGroupData> groups = new ArrayList<>();
int displayOrder = 1;
@@ -108,16 +110,51 @@ public class ParticipantGroupingService {
.collect(Collectors.toList());
}
/**
* Get member count for a team by team name
*/
private int getTeamMemberCount(String teamName) {
if (teamName == null || teamName.isEmpty()) {
return 1;
}
MartialTeam team = teamMapper.selectOne(
new LambdaQueryWrapper<MartialTeam>()
.eq(MartialTeam::getTeamName, teamName)
.eq(MartialTeam::getIsDeleted, 0)
.last("LIMIT 1")
);
if (team == null) {
return 1;
}
Long count = teamMemberMapper.selectCount(
new LambdaQueryWrapper<MartialTeamMember>()
.eq(MartialTeamMember::getTeamId, team.getId())
.eq(MartialTeamMember::getIsDeleted, 0)
);
return count != null && count > 0 ? count.intValue() : 1;
}
/**
* Calculate total members across all teams
*/
private int calculateTotalMembers(List<MartialAthlete> teams) {
int total = 0;
for (MartialAthlete team : teams) {
total += getTeamMemberCount(team.getPlayerName());
}
return total;
}
private int groupTeamAthletes(List<MartialAthlete> teamAthletes, Map<Long, MartialProject> projectMap,
List<ScheduleGroupData> groups, int displayOrder) {
Map<String, List<MartialAthlete>> teamGroupMap = teamAthletes.stream()
.collect(Collectors.groupingBy(a -> String.valueOf(a.getProjectId())));
for (Map.Entry<String, List<MartialAthlete>> entry : teamGroupMap.entrySet()) {
List<MartialAthlete> members = entry.getValue();
if (members.isEmpty()) continue;
List<MartialAthlete> teams = entry.getValue();
if (teams.isEmpty()) continue;
MartialAthlete first = members.get(0);
MartialAthlete first = teams.get(0);
MartialProject project = projectMap.get(first.getProjectId());
if (project == null) {
log.warn("Project not found, projectId: {}, skipping", first.getProjectId());
@@ -126,66 +163,84 @@ public class ParticipantGroupingService {
String projectName = project.getProjectName();
String categoryName = getProjectCategoryName(project);
int maxTeamsPerGroup = (project.getMaxParticipants() != null && project.getMaxParticipants() > 0)
? project.getMaxParticipants() : members.size();
int maxParticipants = (project.getMaxParticipants() != null && project.getMaxParticipants() > 0)
? project.getMaxParticipants() : Integer.MAX_VALUE;
int durationPerTeam = (project.getEstimatedDuration() != null && project.getEstimatedDuration() > 0)
? project.getEstimatedDuration() : 5;
// Team projects: each record in members represents one team
int teamCount = members.size();
int totalMembers = calculateTotalMembers(teams);
int teamCount = teams.size();
if (teamCount <= maxTeamsPerGroup) {
// No need to split, create single group
log.info("Team project '{}': {} teams, {} total members, venue capacity: {}",
projectName + " " + categoryName, teamCount, totalMembers, maxParticipants);
if (totalMembers <= maxParticipants) {
ScheduleGroupData group = ScheduleGroupData.builder()
.groupName(projectName + (categoryName.isEmpty() ? "" : " " + categoryName))
.projectId(first.getProjectId())
.projectType(2)
.maxParticipants(maxTeamsPerGroup)
.maxParticipants(maxParticipants)
.category(categoryName)
.displayOrder(displayOrder++)
.totalParticipants(members.size())
.totalParticipants(totalMembers)
.totalTeams(teamCount)
.athletes(members)
.athletes(teams)
.estimatedDuration(teamCount * durationPerTeam)
.build();
groups.add(group);
} else {
// Split into sub-groups like individual projects
displayOrder = splitTeamIntoSubGroups(members, project, projectName, categoryName,
maxTeamsPerGroup, durationPerTeam, groups, displayOrder);
displayOrder = splitTeamsByMemberCount(teams, project, projectName, categoryName,
maxParticipants, durationPerTeam, groups, displayOrder);
}
}
return displayOrder;
}
/**
* Split team project into sub-groups when team count exceeds maxParticipants
* Split teams into sub-groups based on total member count and venue capacity
*/
private int splitTeamIntoSubGroups(List<MartialAthlete> members, MartialProject project,
String projectName, String categoryName, int maxTeamsPerGroup,
int durationPerTeam, List<ScheduleGroupData> groups, int displayOrder) {
int totalTeams = members.size();
int numSubGroups = (int) Math.ceil((double) totalTeams / maxTeamsPerGroup);
private int splitTeamsByMemberCount(List<MartialAthlete> teams, MartialProject project,
String projectName, String categoryName, int maxParticipants,
int durationPerTeam, List<ScheduleGroupData> groups, int displayOrder) {
List<List<MartialAthlete>> subGroups = new ArrayList<>();
List<MartialAthlete> currentGroup = new ArrayList<>();
int currentMemberCount = 0;
log.info("Team project '{}' has {} teams, splitting into {} groups (max {} per group)",
projectName + " " + categoryName, totalTeams, numSubGroups, maxTeamsPerGroup);
for (MartialAthlete team : teams) {
int teamMemberCount = getTeamMemberCount(team.getPlayerName());
for (int i = 0; i < numSubGroups; i++) {
int startIdx = i * maxTeamsPerGroup;
int endIdx = Math.min(startIdx + maxTeamsPerGroup, totalTeams);
List<MartialAthlete> subGroupMembers = new ArrayList<>(members.subList(startIdx, endIdx));
int subGroupTeamCount = subGroupMembers.size();
if (currentMemberCount + teamMemberCount > maxParticipants && !currentGroup.isEmpty()) {
subGroups.add(new ArrayList<>(currentGroup));
currentGroup.clear();
currentMemberCount = 0;
}
currentGroup.add(team);
currentMemberCount += teamMemberCount;
}
if (!currentGroup.isEmpty()) {
subGroups.add(currentGroup);
}
log.info("Team project '{}' split into {} groups based on venue capacity {}",
projectName + " " + categoryName, subGroups.size(), maxParticipants);
for (int i = 0; i < subGroups.size(); i++) {
List<MartialAthlete> subGroupTeams = subGroups.get(i);
int subGroupMemberCount = calculateTotalMembers(subGroupTeams);
int subGroupTeamCount = subGroupTeams.size();
ScheduleGroupData group = ScheduleGroupData.builder()
.groupName(projectName + " " + categoryName + "" + (i + 1) + "")
.projectId(project.getId())
.projectType(2)
.maxParticipants(maxTeamsPerGroup)
.maxParticipants(maxParticipants)
.displayOrder(displayOrder++)
.category(categoryName)
.totalParticipants(subGroupMembers.size())
.totalParticipants(subGroupMemberCount)
.totalTeams(subGroupTeamCount)
.athletes(subGroupMembers)
.athletes(subGroupTeams)
.estimatedDuration(subGroupTeamCount * durationPerTeam)
.build();
groups.add(group);