refactor: Phase 2 - Extract ParticipantGroupingService from God Class
- Create ScheduleGroupData model class in schedule/model/ - Create ParticipantGroupingService in schedule/grouping/ - Modify MartialScheduleArrangeServiceImpl to use ParticipantGroupingService - Remove internal ScheduleGroupData class and grouping methods - All 438 tests passing Related to Issue #11
This commit is contained in:
+9
-388
@@ -26,7 +26,9 @@ import org.springblade.modules.martial.pojo.entity.*;
|
||||
import org.springblade.modules.martial.config.ScheduleConfig;
|
||||
import org.springblade.modules.martial.service.IMartialScheduleArrangeService;
|
||||
import org.springblade.modules.martial.service.schedule.generator.TimeSlotGenerator;
|
||||
import org.springblade.modules.martial.service.schedule.grouping.ParticipantGroupingService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springblade.modules.martial.service.schedule.model.ScheduleGroupData;
|
||||
import org.springblade.modules.martial.service.schedule.model.TimeSlot;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -59,6 +61,7 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
||||
private final ScheduleConfig scheduleConfig;
|
||||
private final TimeSlotGenerator timeSlotGenerator;
|
||||
|
||||
private final ParticipantGroupingService participantGroupingService;
|
||||
@Override
|
||||
public List<Long> getUnlockedCompetitions() {
|
||||
// 查询所有未锁定的赛事(schedule_status != 2)
|
||||
@@ -372,250 +375,18 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
||||
* @param project 项目
|
||||
* @return 分组类别名称: 男子/女子/混合/团体
|
||||
*/
|
||||
private String getProjectCategoryName(MartialProject project) {
|
||||
if (project == null || project.getCategory() == null) {
|
||||
return "";
|
||||
}
|
||||
String category = project.getCategory();
|
||||
switch (category) {
|
||||
case "1": return "男子";
|
||||
case "2": return "女子";
|
||||
case "3": return "混合";
|
||||
case "4": return "团体";
|
||||
default: return category; // 如果是自定义文字,直接返回
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据项目的分组类别过滤运动员
|
||||
* @param athletes 运动员列表
|
||||
* @param project 项目
|
||||
* @return 过滤后的运动员列表
|
||||
* Auto group participants
|
||||
* @deprecated Use ParticipantGroupingService.autoGroup() directly
|
||||
*/
|
||||
private List<MartialAthlete> filterAthletesByProjectCategory(List<MartialAthlete> athletes, MartialProject project) {
|
||||
if (project == null || project.getCategory() == null) {
|
||||
return athletes;
|
||||
}
|
||||
String category = project.getCategory();
|
||||
switch (category) {
|
||||
case "1": // 男子项目,只保留男性运动员
|
||||
return athletes.stream()
|
||||
.filter(a -> a.getGender() == null || a.getGender() == 1)
|
||||
.collect(Collectors.toList());
|
||||
case "2": // 女子项目,只保留女性运动员
|
||||
return athletes.stream()
|
||||
.filter(a -> a.getGender() != null && a.getGender() == 2)
|
||||
.collect(Collectors.toList());
|
||||
case "3": // 混合项目,所有运动员
|
||||
case "4": // 团体项目,所有运动员
|
||||
default:
|
||||
return athletes;
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private List<ScheduleGroupData> autoGroupParticipants(List<MartialAthlete> athletes) {
|
||||
List<ScheduleGroupData> groups = new ArrayList<>();
|
||||
int displayOrder = 1;
|
||||
|
||||
// 先加载所有项目信息(用于获取项目类型和名称)
|
||||
Set<Long> projectIds = athletes.stream()
|
||||
.map(MartialAthlete::getProjectId)
|
||||
.filter(id -> id != null)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<Long, MartialProject> projectMap = new HashMap<>();
|
||||
for (Long projectId : projectIds) {
|
||||
MartialProject project = projectMapper.selectById(projectId);
|
||||
if (project != null) {
|
||||
projectMap.put(projectId, project);
|
||||
}
|
||||
}
|
||||
|
||||
// 分离集体和个人项目(根据项目表的type字段: 1=个人, 2=双人, 3=集体)
|
||||
List<MartialAthlete> teamAthletes = athletes.stream()
|
||||
.filter(a -> {
|
||||
MartialProject project = projectMap.get(a.getProjectId());
|
||||
return project != null && (project.getType() == 2 || project.getType() == 3);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<MartialAthlete> individualAthletes = athletes.stream()
|
||||
.filter(a -> {
|
||||
MartialProject project = projectMap.get(a.getProjectId());
|
||||
return project != null && project.getType() == 1;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 集体项目分组:按"项目ID_组别"分组
|
||||
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;
|
||||
|
||||
MartialAthlete first = members.get(0);
|
||||
MartialProject project = projectMap.get(first.getProjectId());
|
||||
|
||||
// 跳过没有项目信息的分组
|
||||
if (project == null) {
|
||||
log.warn("项目不存在, projectId: {}, 跳过该分组", first.getProjectId());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据项目的分组类别过滤运动员(男子项目只保留男性,女子项目只保留女性)
|
||||
members = filterAthletesByProjectCategory(members, project);
|
||||
if (members.isEmpty()) {
|
||||
log.info("项目 '{}' 过滤后无符合性别要求的运动员,跳过", project.getProjectName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 统计队伍数(按单位分组)
|
||||
long teamCount = members.stream()
|
||||
.map(MartialAthlete::getTeamName)
|
||||
.filter(org -> org != null && !org.isEmpty())
|
||||
.distinct()
|
||||
.count();
|
||||
|
||||
ScheduleGroupData group = new ScheduleGroupData();
|
||||
String projectName = project.getProjectName();
|
||||
String categoryName = getProjectCategoryName(project);
|
||||
group.setGroupName(projectName + (categoryName.isEmpty() ? "" : " " + categoryName));
|
||||
group.setProjectId(first.getProjectId());
|
||||
group.setProjectType((project.getType() == 2 || project.getType() == 3) ? 2 : 1); // type=2(双人)或type=3(集体)映射为projectType=2(集体)
|
||||
group.setMaxParticipants(project.getMaxParticipants()); // 设置项目单位容纳人数
|
||||
group.setCategory(categoryName); // 设置组别
|
||||
group.setDisplayOrder(displayOrder++);
|
||||
group.setTotalParticipants(members.size());
|
||||
group.setTotalTeams((int) teamCount);
|
||||
group.setAthletes(members);
|
||||
|
||||
// 计算预计时长:使用项目的 estimatedDuration
|
||||
// 如果项目设置了时长,使用 队伍数 × 项目时长
|
||||
// 否则使用默认计算: 队伍数 × 5分钟 + 间隔时间
|
||||
int duration;
|
||||
if (project.getEstimatedDuration() != null && project.getEstimatedDuration() > 0) {
|
||||
duration = (int) teamCount * project.getEstimatedDuration();
|
||||
log.debug("集体项目 '{}': 使用项目时长 {}分钟/队, {}队, 总计{}分钟",
|
||||
projectName, project.getEstimatedDuration(), teamCount, duration);
|
||||
} else {
|
||||
duration = (int) teamCount * 5 + Math.max(0, (int) teamCount - 1) * 2;
|
||||
log.debug("集体项目 '{}': 使用默认时长, {}队, 总计{}分钟", projectName, teamCount, duration);
|
||||
}
|
||||
group.setEstimatedDuration(duration);
|
||||
|
||||
groups.add(group);
|
||||
}
|
||||
|
||||
// 个人项目分组:按"项目ID_组别"分组
|
||||
Map<String, List<MartialAthlete>> individualGroupMap = individualAthletes.stream()
|
||||
.collect(Collectors.groupingBy(a ->
|
||||
String.valueOf(a.getProjectId())
|
||||
));
|
||||
|
||||
for (Map.Entry<String, List<MartialAthlete>> entry : individualGroupMap.entrySet()) {
|
||||
List<MartialAthlete> members = entry.getValue();
|
||||
if (members.isEmpty()) continue;
|
||||
|
||||
MartialAthlete first = members.get(0);
|
||||
MartialProject project = projectMap.get(first.getProjectId());
|
||||
|
||||
// 跳过没有项目信息的分组
|
||||
if (project == null) {
|
||||
log.warn("项目不存在, projectId: {}, 跳过该分组", first.getProjectId());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据项目的分组类别过滤运动员(男子项目只保留男性,女子项目只保留女性)
|
||||
members = filterAthletesByProjectCategory(members, project);
|
||||
if (members.isEmpty()) {
|
||||
log.info("项目 '{}' 过滤后无符合性别要求的运动员,跳过", project.getProjectName());
|
||||
continue;
|
||||
}
|
||||
|
||||
String projectName = project.getProjectName();
|
||||
String categoryName = getProjectCategoryName(project);
|
||||
|
||||
// 计算单人时长
|
||||
int durationPerPerson = scheduleConfig.getDefaultDurationPerPerson();
|
||||
if (project.getEstimatedDuration() != null && project.getEstimatedDuration() > 0) {
|
||||
durationPerPerson = project.getEstimatedDuration();
|
||||
}
|
||||
|
||||
// 自动拆分大组:如果人数过多,拆分成多个小组
|
||||
// 优先使用项目配置的单位容纳人数(maxParticipants)
|
||||
int maxPeoplePerGroup;
|
||||
Integer projectMax = project.getMaxParticipants();
|
||||
if (projectMax != null && projectMax > 0) {
|
||||
// 使用项目配置的单位容纳人数
|
||||
maxPeoplePerGroup = projectMax;
|
||||
log.debug("项目 '{}' 使用自定义容纳人数: {}", projectName, projectMax);
|
||||
} else if (members.size() <= scheduleConfig.getMaxPeoplePerGroup()) {
|
||||
// 35人以内不拆分
|
||||
maxPeoplePerGroup = members.size();
|
||||
} else {
|
||||
// 超过35人,按每组30-35人拆分
|
||||
maxPeoplePerGroup = scheduleConfig.getTargetPeoplePerGroup();
|
||||
}
|
||||
|
||||
if (members.size() <= maxPeoplePerGroup) {
|
||||
// 人数不多,不需要拆分
|
||||
ScheduleGroupData group = new ScheduleGroupData();
|
||||
group.setGroupName(projectName + " " + categoryName);
|
||||
group.setProjectId(first.getProjectId());
|
||||
group.setProjectType(1);
|
||||
group.setMaxParticipants(maxPeoplePerGroup); // 设置项目单位容纳人数
|
||||
group.setDisplayOrder(displayOrder++);
|
||||
group.setCategory(categoryName); // 设置组别
|
||||
group.setTotalParticipants(members.size());
|
||||
group.setAthletes(members);
|
||||
|
||||
int duration = members.size() * durationPerPerson;
|
||||
group.setEstimatedDuration(duration);
|
||||
|
||||
log.debug("个人项目 '{}': {}人, {}分钟/人, 总计{}分钟",
|
||||
projectName + " " + categoryName, members.size(), durationPerPerson, duration);
|
||||
|
||||
groups.add(group);
|
||||
} else {
|
||||
// 人数太多,需要拆分成多个小组
|
||||
int totalPeople = members.size();
|
||||
int numSubGroups = (int) Math.ceil((double) totalPeople / maxPeoplePerGroup);
|
||||
|
||||
log.info("个人项目 '{}' 有{}人,将拆分成{}个小组(每组最多{}人)",
|
||||
projectName + " " + categoryName, totalPeople, numSubGroups, maxPeoplePerGroup);
|
||||
|
||||
for (int i = 0; i < numSubGroups; i++) {
|
||||
int startIdx = i * maxPeoplePerGroup;
|
||||
int endIdx = Math.min(startIdx + maxPeoplePerGroup, totalPeople);
|
||||
List<MartialAthlete> subGroupMembers = members.subList(startIdx, endIdx);
|
||||
|
||||
ScheduleGroupData group = new ScheduleGroupData();
|
||||
group.setGroupName(projectName + " " + categoryName + " 第" + (i + 1) + "组");
|
||||
group.setProjectId(first.getProjectId());
|
||||
group.setProjectType(1);
|
||||
group.setMaxParticipants(maxPeoplePerGroup); // 设置项目单位容纳人数
|
||||
group.setDisplayOrder(displayOrder++);
|
||||
group.setCategory(categoryName); // 设置组别
|
||||
group.setTotalParticipants(subGroupMembers.size());
|
||||
group.setAthletes(new ArrayList<>(subGroupMembers));
|
||||
|
||||
int duration = subGroupMembers.size() * durationPerPerson;
|
||||
group.setEstimatedDuration(duration);
|
||||
|
||||
log.debug(" 拆分子组 '第{}组': {}人, 总计{}分钟",
|
||||
i + 1, subGroupMembers.size(), duration);
|
||||
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
return participantGroupingService.autoGroup(athletes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证时间窗口容量是否足够容纳所有分组
|
||||
*/
|
||||
@@ -918,154 +689,4 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
||||
}
|
||||
}
|
||||
|
||||
private static class ScheduleGroupData {
|
||||
private String groupName;
|
||||
private Long projectId;
|
||||
private Integer projectType;
|
||||
private Integer displayOrder;
|
||||
private Integer totalParticipants;
|
||||
private Integer totalTeams;
|
||||
private Integer estimatedDuration;
|
||||
private Integer maxParticipants; // 项目单位容纳人数
|
||||
private String category; // 组别(男子/女子/混合)
|
||||
private List<MartialAthlete> athletes;
|
||||
|
||||
// 分配结果
|
||||
private Long assignedVenueId;
|
||||
private String assignedVenueName;
|
||||
private LocalDate assignedDate;
|
||||
private String assignedTimePeriod;
|
||||
private String assignedTimeSlot;
|
||||
private Integer assignedTimeSlotIndex; // 时间段索引
|
||||
|
||||
// Getters and Setters
|
||||
public String getGroupName() {
|
||||
return groupName;
|
||||
}
|
||||
|
||||
public void setGroupName(String groupName) {
|
||||
this.groupName = groupName;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Integer getProjectType() {
|
||||
return projectType;
|
||||
}
|
||||
|
||||
public void setProjectType(Integer projectType) {
|
||||
this.projectType = projectType;
|
||||
}
|
||||
|
||||
public Integer getDisplayOrder() {
|
||||
return displayOrder;
|
||||
}
|
||||
|
||||
public void setDisplayOrder(Integer displayOrder) {
|
||||
this.displayOrder = displayOrder;
|
||||
}
|
||||
|
||||
public Integer getTotalParticipants() {
|
||||
return totalParticipants;
|
||||
}
|
||||
|
||||
public void setTotalParticipants(Integer totalParticipants) {
|
||||
this.totalParticipants = totalParticipants;
|
||||
}
|
||||
|
||||
public Integer getTotalTeams() {
|
||||
return totalTeams;
|
||||
}
|
||||
|
||||
public void setTotalTeams(Integer totalTeams) {
|
||||
this.totalTeams = totalTeams;
|
||||
}
|
||||
|
||||
public Integer getEstimatedDuration() {
|
||||
return estimatedDuration;
|
||||
}
|
||||
|
||||
public void setEstimatedDuration(Integer estimatedDuration) {
|
||||
this.estimatedDuration = estimatedDuration;
|
||||
}
|
||||
|
||||
public Integer getMaxParticipants() {
|
||||
return maxParticipants;
|
||||
}
|
||||
|
||||
public void setMaxParticipants(Integer maxParticipants) {
|
||||
this.maxParticipants = maxParticipants;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public List<MartialAthlete> getAthletes() {
|
||||
return athletes;
|
||||
}
|
||||
|
||||
public void setAthletes(List<MartialAthlete> athletes) {
|
||||
this.athletes = athletes;
|
||||
}
|
||||
|
||||
public Long getAssignedVenueId() {
|
||||
return assignedVenueId;
|
||||
}
|
||||
|
||||
public void setAssignedVenueId(Long assignedVenueId) {
|
||||
this.assignedVenueId = assignedVenueId;
|
||||
}
|
||||
|
||||
public String getAssignedVenueName() {
|
||||
return assignedVenueName;
|
||||
}
|
||||
|
||||
public void setAssignedVenueName(String assignedVenueName) {
|
||||
this.assignedVenueName = assignedVenueName;
|
||||
}
|
||||
|
||||
public LocalDate getAssignedDate() {
|
||||
return assignedDate;
|
||||
}
|
||||
|
||||
public void setAssignedDate(LocalDate assignedDate) {
|
||||
this.assignedDate = assignedDate;
|
||||
}
|
||||
|
||||
public String getAssignedTimePeriod() {
|
||||
return assignedTimePeriod;
|
||||
}
|
||||
|
||||
public void setAssignedTimePeriod(String assignedTimePeriod) {
|
||||
this.assignedTimePeriod = assignedTimePeriod;
|
||||
}
|
||||
|
||||
public String getAssignedTimeSlot() {
|
||||
return assignedTimeSlot;
|
||||
}
|
||||
|
||||
public void setAssignedTimeSlot(String assignedTimeSlot) {
|
||||
this.assignedTimeSlot = assignedTimeSlot;
|
||||
}
|
||||
|
||||
public Integer getAssignedTimeSlotIndex() {
|
||||
return assignedTimeSlotIndex;
|
||||
}
|
||||
|
||||
public void setAssignedTimeSlotIndex(Integer assignedTimeSlotIndex) {
|
||||
this.assignedTimeSlotIndex = assignedTimeSlotIndex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
package org.springblade.modules.martial.service.schedule.grouping;
|
||||
|
||||
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.pojo.entity.MartialAthlete;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialProject;
|
||||
import org.springblade.modules.martial.service.schedule.model.ScheduleGroupData;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Participant grouping service
|
||||
* Groups athletes by project and category for schedule arrangement
|
||||
*
|
||||
* @author Refactored from MartialScheduleArrangeServiceImpl
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ParticipantGroupingService {
|
||||
|
||||
private final MartialProjectMapper projectMapper;
|
||||
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;
|
||||
|
||||
Map<Long, MartialProject> projectMap = loadProjectMap(athletes);
|
||||
List<MartialAthlete> teamAthletes = filterTeamAthletes(athletes, projectMap);
|
||||
List<MartialAthlete> individualAthletes = filterIndividualAthletes(athletes, projectMap);
|
||||
|
||||
displayOrder = groupTeamAthletes(teamAthletes, projectMap, groups, displayOrder);
|
||||
groupIndividualAthletes(individualAthletes, projectMap, groups, displayOrder);
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
public String getProjectCategoryName(MartialProject project) {
|
||||
if (project == null || project.getCategory() == null) {
|
||||
return "";
|
||||
}
|
||||
switch (project.getCategory()) {
|
||||
case "1": return "男子";
|
||||
case "2": return "女子";
|
||||
case "3": return "混合";
|
||||
case "4": return "团体";
|
||||
default: return project.getCategory();
|
||||
}
|
||||
}
|
||||
|
||||
public List<MartialAthlete> filterByCategory(List<MartialAthlete> athletes, MartialProject project) {
|
||||
if (project == null || project.getCategory() == null) {
|
||||
return athletes;
|
||||
}
|
||||
switch (project.getCategory()) {
|
||||
case "1":
|
||||
return athletes.stream()
|
||||
.filter(a -> a.getGender() == null || a.getGender() == 1)
|
||||
.collect(Collectors.toList());
|
||||
case "2":
|
||||
return athletes.stream()
|
||||
.filter(a -> a.getGender() != null && a.getGender() == 2)
|
||||
.collect(Collectors.toList());
|
||||
default:
|
||||
return athletes;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, MartialProject> loadProjectMap(List<MartialAthlete> athletes) {
|
||||
Set<Long> projectIds = athletes.stream()
|
||||
.map(MartialAthlete::getProjectId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<Long, MartialProject> projectMap = new HashMap<>();
|
||||
for (Long projectId : projectIds) {
|
||||
MartialProject project = projectMapper.selectById(projectId);
|
||||
if (project != null) {
|
||||
projectMap.put(projectId, project);
|
||||
}
|
||||
}
|
||||
return projectMap;
|
||||
}
|
||||
|
||||
private List<MartialAthlete> filterTeamAthletes(List<MartialAthlete> athletes, Map<Long, MartialProject> projectMap) {
|
||||
return athletes.stream()
|
||||
.filter(a -> {
|
||||
MartialProject project = projectMap.get(a.getProjectId());
|
||||
return project != null && (project.getType() == 2 || project.getType() == 3);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<MartialAthlete> filterIndividualAthletes(List<MartialAthlete> athletes, Map<Long, MartialProject> projectMap) {
|
||||
return athletes.stream()
|
||||
.filter(a -> {
|
||||
MartialProject project = projectMap.get(a.getProjectId());
|
||||
return project != null && project.getType() == 1;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
MartialAthlete first = members.get(0);
|
||||
MartialProject project = projectMap.get(first.getProjectId());
|
||||
if (project == null) {
|
||||
log.warn("Project not found, projectId: {}, skipping", first.getProjectId());
|
||||
continue;
|
||||
}
|
||||
|
||||
members = filterByCategory(members, project);
|
||||
if (members.isEmpty()) {
|
||||
log.info("Project '{}' has no athletes after gender filter, skipping", project.getProjectName());
|
||||
continue;
|
||||
}
|
||||
|
||||
long teamCount = members.stream()
|
||||
.map(MartialAthlete::getTeamName)
|
||||
.filter(org -> org != null && !org.isEmpty())
|
||||
.distinct()
|
||||
.count();
|
||||
|
||||
String projectName = project.getProjectName();
|
||||
String categoryName = getProjectCategoryName(project);
|
||||
|
||||
ScheduleGroupData group = ScheduleGroupData.builder()
|
||||
.groupName(projectName + (categoryName.isEmpty() ? "" : " " + categoryName))
|
||||
.projectId(first.getProjectId())
|
||||
.projectType((project.getType() == 2 || project.getType() == 3) ? 2 : 1)
|
||||
.maxParticipants(project.getMaxParticipants())
|
||||
.category(categoryName)
|
||||
.displayOrder(displayOrder++)
|
||||
.totalParticipants(members.size())
|
||||
.totalTeams((int) teamCount)
|
||||
.athletes(members)
|
||||
.build();
|
||||
|
||||
int duration;
|
||||
if (project.getEstimatedDuration() != null && project.getEstimatedDuration() > 0) {
|
||||
duration = (int) teamCount * project.getEstimatedDuration();
|
||||
} else {
|
||||
duration = (int) teamCount * 5 + Math.max(0, (int) teamCount - 1) * 2;
|
||||
}
|
||||
group.setEstimatedDuration(duration);
|
||||
groups.add(group);
|
||||
}
|
||||
return displayOrder;
|
||||
}
|
||||
|
||||
private void groupIndividualAthletes(List<MartialAthlete> individualAthletes, Map<Long, MartialProject> projectMap,
|
||||
List<ScheduleGroupData> groups, int displayOrder) {
|
||||
Map<String, List<MartialAthlete>> individualGroupMap = individualAthletes.stream()
|
||||
.collect(Collectors.groupingBy(a -> String.valueOf(a.getProjectId())));
|
||||
|
||||
for (Map.Entry<String, List<MartialAthlete>> entry : individualGroupMap.entrySet()) {
|
||||
List<MartialAthlete> members = entry.getValue();
|
||||
if (members.isEmpty()) continue;
|
||||
|
||||
MartialAthlete first = members.get(0);
|
||||
MartialProject project = projectMap.get(first.getProjectId());
|
||||
if (project == null) {
|
||||
log.warn("Project not found, projectId: {}, skipping", first.getProjectId());
|
||||
continue;
|
||||
}
|
||||
|
||||
members = filterByCategory(members, project);
|
||||
if (members.isEmpty()) continue;
|
||||
|
||||
String projectName = project.getProjectName();
|
||||
String categoryName = getProjectCategoryName(project);
|
||||
int durationPerPerson = (project.getEstimatedDuration() != null && project.getEstimatedDuration() > 0)
|
||||
? project.getEstimatedDuration() : scheduleConfig.getDefaultDurationPerPerson();
|
||||
|
||||
int maxPeoplePerGroup = calculateMaxPeoplePerGroup(project, members.size());
|
||||
|
||||
if (members.size() <= maxPeoplePerGroup) {
|
||||
ScheduleGroupData group = ScheduleGroupData.builder()
|
||||
.groupName(projectName + " " + categoryName)
|
||||
.projectId(first.getProjectId())
|
||||
.projectType(1)
|
||||
.maxParticipants(maxPeoplePerGroup)
|
||||
.displayOrder(displayOrder++)
|
||||
.category(categoryName)
|
||||
.totalParticipants(members.size())
|
||||
.athletes(members)
|
||||
.estimatedDuration(members.size() * durationPerPerson)
|
||||
.build();
|
||||
groups.add(group);
|
||||
} else {
|
||||
displayOrder = splitIntoSubGroups(members, project, projectName, categoryName,
|
||||
maxPeoplePerGroup, durationPerPerson, groups, displayOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateMaxPeoplePerGroup(MartialProject project, int memberCount) {
|
||||
Integer projectMax = project.getMaxParticipants();
|
||||
if (projectMax != null && projectMax > 0) {
|
||||
return projectMax;
|
||||
} else if (memberCount <= scheduleConfig.getMaxPeoplePerGroup()) {
|
||||
return memberCount;
|
||||
} else {
|
||||
return scheduleConfig.getTargetPeoplePerGroup();
|
||||
}
|
||||
}
|
||||
|
||||
private int splitIntoSubGroups(List<MartialAthlete> members, MartialProject project,
|
||||
String projectName, String categoryName, int maxPeoplePerGroup,
|
||||
int durationPerPerson, List<ScheduleGroupData> groups, int displayOrder) {
|
||||
int totalPeople = members.size();
|
||||
int numSubGroups = (int) Math.ceil((double) totalPeople / maxPeoplePerGroup);
|
||||
|
||||
log.info("Individual project '{}' has {} people, splitting into {} groups (max {} per group)",
|
||||
projectName + " " + categoryName, totalPeople, numSubGroups, maxPeoplePerGroup);
|
||||
|
||||
for (int i = 0; i < numSubGroups; i++) {
|
||||
int startIdx = i * maxPeoplePerGroup;
|
||||
int endIdx = Math.min(startIdx + maxPeoplePerGroup, totalPeople);
|
||||
List<MartialAthlete> subGroupMembers = new ArrayList<>(members.subList(startIdx, endIdx));
|
||||
|
||||
ScheduleGroupData group = ScheduleGroupData.builder()
|
||||
.groupName(projectName + " " + categoryName + " 第" + (i + 1) + "组")
|
||||
.projectId(project.getId())
|
||||
.projectType(1)
|
||||
.maxParticipants(maxPeoplePerGroup)
|
||||
.displayOrder(displayOrder++)
|
||||
.category(categoryName)
|
||||
.totalParticipants(subGroupMembers.size())
|
||||
.athletes(subGroupMembers)
|
||||
.estimatedDuration(subGroupMembers.size() * durationPerPerson)
|
||||
.build();
|
||||
groups.add(group);
|
||||
}
|
||||
return displayOrder;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package org.springblade.modules.martial.service.schedule.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Schedule group data model
|
||||
* Represents a group of participants for schedule arrangement
|
||||
*
|
||||
* @author Refactored from MartialScheduleArrangeServiceImpl
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ScheduleGroupData {
|
||||
|
||||
private String groupName;
|
||||
private Long projectId;
|
||||
private Integer projectType;
|
||||
private Integer displayOrder;
|
||||
private Integer totalParticipants;
|
||||
private Integer totalTeams;
|
||||
private Integer estimatedDuration;
|
||||
private Integer maxParticipants;
|
||||
private String category;
|
||||
private List<MartialAthlete> athletes;
|
||||
|
||||
// Assignment results
|
||||
private Long assignedVenueId;
|
||||
private String assignedVenueName;
|
||||
private LocalDate assignedDate;
|
||||
private String assignedTimePeriod;
|
||||
private String assignedTimeSlot;
|
||||
private Integer assignedTimeSlotIndex;
|
||||
}
|
||||
Reference in New Issue
Block a user