feat: 优化编排分组逻辑
1. 根据项目分组类别(男子/女子/混合)过滤运动员 2. 使用项目的category字段显示分组名称 3. 按team_name统计队伍数量(集体项目) 4. 添加场地容量约束和负载均衡分配 Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
+181
-76
@@ -30,6 +30,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.Period;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -355,6 +356,53 @@ 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 过滤后的运动员列表
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<ScheduleGroupData> autoGroupParticipants(List<MartialAthlete> athletes) {
|
private List<ScheduleGroupData> autoGroupParticipants(List<MartialAthlete> athletes) {
|
||||||
List<ScheduleGroupData> groups = new ArrayList<>();
|
List<ScheduleGroupData> groups = new ArrayList<>();
|
||||||
int displayOrder = 1;
|
int displayOrder = 1;
|
||||||
@@ -391,7 +439,7 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
// 集体项目分组:按"项目ID_组别"分组
|
// 集体项目分组:按"项目ID_组别"分组
|
||||||
Map<String, List<MartialAthlete>> teamGroupMap = teamAthletes.stream()
|
Map<String, List<MartialAthlete>> teamGroupMap = teamAthletes.stream()
|
||||||
.collect(Collectors.groupingBy(a ->
|
.collect(Collectors.groupingBy(a ->
|
||||||
a.getProjectId() + "_" + Func.toStr(a.getCategory(), "未分组")
|
String.valueOf(a.getProjectId())
|
||||||
));
|
));
|
||||||
|
|
||||||
for (Map.Entry<String, List<MartialAthlete>> entry : teamGroupMap.entrySet()) {
|
for (Map.Entry<String, List<MartialAthlete>> entry : teamGroupMap.entrySet()) {
|
||||||
@@ -401,24 +449,33 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
MartialAthlete first = members.get(0);
|
MartialAthlete first = members.get(0);
|
||||||
MartialProject project = projectMap.get(first.getProjectId());
|
MartialProject project = projectMap.get(first.getProjectId());
|
||||||
|
|
||||||
// 统计队伍数(按单位分组)
|
|
||||||
long teamCount = members.stream()
|
|
||||||
.map(MartialAthlete::getOrganization)
|
|
||||||
.filter(org -> org != null && !org.isEmpty())
|
|
||||||
.distinct()
|
|
||||||
.count();
|
|
||||||
|
|
||||||
// 跳过没有项目信息的分组
|
// 跳过没有项目信息的分组
|
||||||
if (project == null) {
|
if (project == null) {
|
||||||
log.warn("项目不存在, projectId: {}, 跳过该分组", first.getProjectId());
|
log.warn("项目不存在, projectId: {}, 跳过该分组", first.getProjectId());
|
||||||
continue;
|
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();
|
ScheduleGroupData group = new ScheduleGroupData();
|
||||||
String projectName = project.getProjectName();
|
String projectName = project.getProjectName();
|
||||||
group.setGroupName(projectName + " " + (first.getCategory() != null ? first.getCategory() : "未分组"));
|
String categoryName = getProjectCategoryName(project);
|
||||||
|
group.setGroupName(projectName + (categoryName.isEmpty() ? "" : " " + categoryName));
|
||||||
group.setProjectId(first.getProjectId());
|
group.setProjectId(first.getProjectId());
|
||||||
group.setProjectType((project.getType() == 2 || project.getType() == 3) ? 2 : 1); // type=2(双人)或type=3(集体)映射为projectType=2(集体)
|
group.setProjectType((project.getType() == 2 || project.getType() == 3) ? 2 : 1); // type=2(双人)或type=3(集体)映射为projectType=2(集体)
|
||||||
|
group.setMaxParticipants(project.getMaxParticipants()); // 设置项目单位容纳人数
|
||||||
group.setDisplayOrder(displayOrder++);
|
group.setDisplayOrder(displayOrder++);
|
||||||
group.setTotalParticipants(members.size());
|
group.setTotalParticipants(members.size());
|
||||||
group.setTotalTeams((int) teamCount);
|
group.setTotalTeams((int) teamCount);
|
||||||
@@ -444,7 +501,7 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
// 个人项目分组:按"项目ID_组别"分组
|
// 个人项目分组:按"项目ID_组别"分组
|
||||||
Map<String, List<MartialAthlete>> individualGroupMap = individualAthletes.stream()
|
Map<String, List<MartialAthlete>> individualGroupMap = individualAthletes.stream()
|
||||||
.collect(Collectors.groupingBy(a ->
|
.collect(Collectors.groupingBy(a ->
|
||||||
a.getProjectId() + "_" + Func.toStr(a.getCategory(), "未分组")
|
String.valueOf(a.getProjectId())
|
||||||
));
|
));
|
||||||
|
|
||||||
for (Map.Entry<String, List<MartialAthlete>> entry : individualGroupMap.entrySet()) {
|
for (Map.Entry<String, List<MartialAthlete>> entry : individualGroupMap.entrySet()) {
|
||||||
@@ -460,8 +517,15 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据项目的分组类别过滤运动员(男子项目只保留男性,女子项目只保留女性)
|
||||||
|
members = filterAthletesByProjectCategory(members, project);
|
||||||
|
if (members.isEmpty()) {
|
||||||
|
log.info("项目 '{}' 过滤后无符合性别要求的运动员,跳过", project.getProjectName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
String projectName = project.getProjectName();
|
String projectName = project.getProjectName();
|
||||||
String categoryName = first.getCategory() != null ? first.getCategory() : "未分组";
|
String categoryName = getProjectCategoryName(project);
|
||||||
|
|
||||||
// 计算单人时长
|
// 计算单人时长
|
||||||
int durationPerPerson = scheduleConfig.getDefaultDurationPerPerson();
|
int durationPerPerson = scheduleConfig.getDefaultDurationPerPerson();
|
||||||
@@ -491,6 +555,7 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
group.setGroupName(projectName + " " + categoryName);
|
group.setGroupName(projectName + " " + categoryName);
|
||||||
group.setProjectId(first.getProjectId());
|
group.setProjectId(first.getProjectId());
|
||||||
group.setProjectType(1);
|
group.setProjectType(1);
|
||||||
|
group.setMaxParticipants(maxPeoplePerGroup); // 设置项目单位容纳人数
|
||||||
group.setDisplayOrder(displayOrder++);
|
group.setDisplayOrder(displayOrder++);
|
||||||
group.setTotalParticipants(members.size());
|
group.setTotalParticipants(members.size());
|
||||||
group.setAthletes(members);
|
group.setAthletes(members);
|
||||||
@@ -519,6 +584,7 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
group.setGroupName(projectName + " " + categoryName + " 第" + (i + 1) + "组");
|
group.setGroupName(projectName + " " + categoryName + " 第" + (i + 1) + "组");
|
||||||
group.setProjectId(first.getProjectId());
|
group.setProjectId(first.getProjectId());
|
||||||
group.setProjectType(1);
|
group.setProjectType(1);
|
||||||
|
group.setMaxParticipants(maxPeoplePerGroup); // 设置项目单位容纳人数
|
||||||
group.setDisplayOrder(displayOrder++);
|
group.setDisplayOrder(displayOrder++);
|
||||||
group.setTotalParticipants(subGroupMembers.size());
|
group.setTotalParticipants(subGroupMembers.size());
|
||||||
group.setAthletes(new ArrayList<>(subGroupMembers));
|
group.setAthletes(new ArrayList<>(subGroupMembers));
|
||||||
@@ -580,26 +646,36 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
log.info("=== 开始分配场地和时间段 ===");
|
log.info("=== 开始分配场地和时间段 ===");
|
||||||
log.info("场地数量: {}, 时间段数量: {}, 分组数量: {}", venues.size(), timeSlots.size(), groups.size());
|
log.info("场地数量: {}, 时间段数量: {}, 分组数量: {}", venues.size(), timeSlots.size(), groups.size());
|
||||||
|
|
||||||
// 为每个场地创建时间轴(记录当前已用时间)
|
// 打印场地容量信息
|
||||||
// key = venueId + "_" + timeSlotIndex, value = 当前已用分钟数
|
|
||||||
Map<String, Integer> venueTimeUsed = new HashMap<>();
|
|
||||||
Map<String, TimeSlot> venueTimeSlotMap = new HashMap<>();
|
|
||||||
|
|
||||||
int timeSlotIndex = 0;
|
|
||||||
for (TimeSlot timeSlot : timeSlots) {
|
|
||||||
for (MartialVenue venue : venues) {
|
for (MartialVenue venue : venues) {
|
||||||
String key = venue.getId() + "_" + timeSlotIndex;
|
log.info("场地: {}, 容纳人数: {}", venue.getVenueName(), venue.getCapacity());
|
||||||
venueTimeUsed.put(key, 0);
|
|
||||||
venueTimeSlotMap.put(key, timeSlot);
|
|
||||||
}
|
|
||||||
timeSlotIndex++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按项目ID分组
|
// 每个场地的已用时间(跨所有时段累计)
|
||||||
|
Map<Long, Integer> venueTotalUsed = new LinkedHashMap<>();
|
||||||
|
for (MartialVenue venue : venues) {
|
||||||
|
venueTotalUsed.put(venue.getId(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每个时段每个场地的已用时间
|
||||||
|
Map<String, Map<Long, Integer>> slotVenueUsed = new LinkedHashMap<>();
|
||||||
|
Map<String, TimeSlot> slotMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for (TimeSlot ts : timeSlots) {
|
||||||
|
String key = ts.getDate() + "_" + ts.getPeriod();
|
||||||
|
if (!slotVenueUsed.containsKey(key)) {
|
||||||
|
slotVenueUsed.put(key, new LinkedHashMap<>());
|
||||||
|
slotMap.put(key, ts);
|
||||||
|
for (MartialVenue venue : venues) {
|
||||||
|
slotVenueUsed.get(key).put(venue.getId(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按项目ID分组并计算总时长
|
||||||
Map<Long, List<ScheduleGroupData>> groupsByProject = groups.stream()
|
Map<Long, List<ScheduleGroupData>> groupsByProject = groups.stream()
|
||||||
.collect(Collectors.groupingBy(ScheduleGroupData::getProjectId));
|
.collect(Collectors.groupingBy(ScheduleGroupData::getProjectId));
|
||||||
|
|
||||||
// 计算每个项目的总时长
|
|
||||||
Map<Long, Integer> projectTotalDuration = new HashMap<>();
|
Map<Long, Integer> projectTotalDuration = new HashMap<>();
|
||||||
groupsByProject.forEach((projectId, projectGroups) -> {
|
groupsByProject.forEach((projectId, projectGroups) -> {
|
||||||
int total = projectGroups.stream().mapToInt(g -> g.getEstimatedDuration() != null ? g.getEstimatedDuration() : 0).sum();
|
int total = projectGroups.stream().mapToInt(g -> g.getEstimatedDuration() != null ? g.getEstimatedDuration() : 0).sum();
|
||||||
@@ -624,92 +700,112 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
.collect(Collectors.joining(", ")));
|
.collect(Collectors.joining(", ")));
|
||||||
|
|
||||||
int assignedCount = 0;
|
int assignedCount = 0;
|
||||||
Map<Long, String> projectVenueSlotMap = new HashMap<>(); // 记录项目分配的场地+时段
|
|
||||||
|
|
||||||
for (Long projectId : sortedProjectIds) {
|
for (Long projectId : sortedProjectIds) {
|
||||||
List<ScheduleGroupData> projectGroups = groupsByProject.get(projectId);
|
List<ScheduleGroupData> projectGroups = groupsByProject.get(projectId);
|
||||||
int projectDuration = projectTotalDuration.get(projectId);
|
int projectDuration = projectTotalDuration.get(projectId);
|
||||||
String projectName = projectGroups.get(0).getGroupName().split(" ")[0];
|
String projectName = projectGroups.get(0).getGroupName().split(" ")[0];
|
||||||
|
|
||||||
log.info("开始分配项目 '{}': {}个分组, 总时长{}分钟", projectName, projectGroups.size(), projectDuration);
|
// 获取项目的单位容纳人数(每组最大人数)
|
||||||
|
int projectMaxParticipants = projectGroups.get(0).getMaxParticipants() != null
|
||||||
|
? projectGroups.get(0).getMaxParticipants() : 1;
|
||||||
|
|
||||||
// 找到能容纳该项目的最佳场地+时段(优先选择已用时间最少的)
|
log.info("开始分配项目 '{}': {}个分组, 总时长{}分钟, 单位容纳人数={}",
|
||||||
String bestKey = null;
|
projectName, projectGroups.size(), projectDuration, projectMaxParticipants);
|
||||||
int minUsed = Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
for (Map.Entry<String, Integer> entry : venueTimeUsed.entrySet()) {
|
// 第一步:筛选出容量足够的场地
|
||||||
String key = entry.getKey();
|
List<MartialVenue> eligibleVenues = venues.stream()
|
||||||
int used = entry.getValue();
|
.filter(v -> {
|
||||||
TimeSlot ts = venueTimeSlotMap.get(key);
|
Integer capacity = v.getCapacity();
|
||||||
|
// 如果场地没有设置容量,默认允许
|
||||||
|
if (capacity == null || capacity <= 0) return true;
|
||||||
|
// 场地容量必须 >= 项目单位容纳人数
|
||||||
|
return capacity >= projectMaxParticipants;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// 检查剩余容量是否足够
|
if (eligibleVenues.isEmpty()) {
|
||||||
if (used + projectDuration <= ts.getCapacity()) {
|
log.warn("项目 '{}' 需要容纳{}人的场地,但没有符合条件的场地!使用第一个场地",
|
||||||
if (used < minUsed) {
|
projectName, projectMaxParticipants);
|
||||||
minUsed = used;
|
eligibleVenues = venues;
|
||||||
bestKey = key;
|
} else {
|
||||||
}
|
log.info("项目 '{}' 可用场地: {}", projectName,
|
||||||
}
|
eligibleVenues.stream().map(MartialVenue::getVenueName).collect(Collectors.joining(", ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有单个时段能容纳,选择剩余容量最大的
|
// 第二步:在符合条件的场地中,选择总负载最小的
|
||||||
if (bestKey == null) {
|
Long bestVenueId = null;
|
||||||
|
int minTotalUsed = Integer.MAX_VALUE;
|
||||||
|
for (MartialVenue venue : eligibleVenues) {
|
||||||
|
int used = venueTotalUsed.get(venue.getId());
|
||||||
|
if (used < minTotalUsed) {
|
||||||
|
minTotalUsed = used;
|
||||||
|
bestVenueId = venue.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第三步:在选定的场地中,找第一个能容纳该项目的时段
|
||||||
|
String bestSlotKey = null;
|
||||||
|
for (String slotKey : slotVenueUsed.keySet()) {
|
||||||
|
TimeSlot ts = slotMap.get(slotKey);
|
||||||
|
int used = slotVenueUsed.get(slotKey).get(bestVenueId);
|
||||||
|
int remain = ts.getCapacity() - used;
|
||||||
|
|
||||||
|
if (remain >= projectDuration) {
|
||||||
|
bestSlotKey = slotKey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有单个时段能容纳,选择剩余容量最大的时段
|
||||||
|
if (bestSlotKey == null) {
|
||||||
int maxRemain = -1;
|
int maxRemain = -1;
|
||||||
for (Map.Entry<String, Integer> entry : venueTimeUsed.entrySet()) {
|
for (String slotKey : slotVenueUsed.keySet()) {
|
||||||
String key = entry.getKey();
|
TimeSlot ts = slotMap.get(slotKey);
|
||||||
int used = entry.getValue();
|
int used = slotVenueUsed.get(slotKey).get(bestVenueId);
|
||||||
TimeSlot ts = venueTimeSlotMap.get(key);
|
|
||||||
int remain = ts.getCapacity() - used;
|
int remain = ts.getCapacity() - used;
|
||||||
if (remain > maxRemain) {
|
if (remain > maxRemain) {
|
||||||
maxRemain = remain;
|
maxRemain = remain;
|
||||||
bestKey = key;
|
bestSlotKey = slotKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.warn("项目 '{}' 总时长{}分钟可能需要跨时段", projectName, projectDuration);
|
log.warn("项目 '{}' 总时长{}分钟,在场地上没有单个时段能完全容纳", projectName, projectDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析场地ID和时段索引
|
TimeSlot targetSlot = slotMap.get(bestSlotKey);
|
||||||
String[] parts = bestKey.split("_");
|
int slotIndex = timeSlots.indexOf(targetSlot);
|
||||||
Long venueId = Long.parseLong(parts[0]);
|
final Long venueId = bestVenueId;
|
||||||
int slotIndex = Integer.parseInt(parts[1]);
|
|
||||||
TimeSlot timeSlot = venueTimeSlotMap.get(bestKey);
|
|
||||||
String venueName = venues.stream().filter(v -> v.getId().equals(venueId)).findFirst().map(MartialVenue::getVenueName).orElse("未知场地");
|
String venueName = venues.stream().filter(v -> v.getId().equals(venueId)).findFirst().map(MartialVenue::getVenueName).orElse("未知场地");
|
||||||
|
|
||||||
log.info("项目 '{}' 分配到: 场地={}, 时段={}, 当前已用={}分钟", projectName, venueName, timeSlot.getStartTime(), venueTimeUsed.get(bestKey));
|
log.info("项目 '{}' 分配到: 场地={}, 日期={}, 时段={}",
|
||||||
|
projectName, venueName, targetSlot.getDate(), targetSlot.getStartTime());
|
||||||
|
|
||||||
// 分配该项目的所有分组到同一场地+时段
|
// 分配该项目的所有分组到同一场地
|
||||||
for (ScheduleGroupData group : projectGroups) {
|
for (ScheduleGroupData group : projectGroups) {
|
||||||
group.setAssignedVenueId(venueId);
|
group.setAssignedVenueId(venueId);
|
||||||
group.setAssignedVenueName(venueName);
|
group.setAssignedVenueName(venueName);
|
||||||
group.setAssignedDate(timeSlot.getDate());
|
group.setAssignedDate(targetSlot.getDate());
|
||||||
group.setAssignedTimeSlot(timeSlot.getStartTime());
|
group.setAssignedTimeSlot(targetSlot.getStartTime());
|
||||||
group.setAssignedTimeSlotIndex(slotIndex);
|
group.setAssignedTimeSlotIndex(slotIndex);
|
||||||
group.setAssignedTimePeriod(timeSlot.getPeriod());
|
group.setAssignedTimePeriod(targetSlot.getPeriod());
|
||||||
assignedCount++;
|
assignedCount++;
|
||||||
|
|
||||||
log.info(" 分组 '{}' 时长={}分钟", group.getGroupName(), group.getEstimatedDuration());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新已用时间
|
// 更新已用时间
|
||||||
venueTimeUsed.put(bestKey, venueTimeUsed.get(bestKey) + projectDuration);
|
slotVenueUsed.get(bestSlotKey).put(venueId, slotVenueUsed.get(bestSlotKey).get(venueId) + projectDuration);
|
||||||
projectVenueSlotMap.put(projectId, bestKey);
|
venueTotalUsed.put(venueId, venueTotalUsed.get(venueId) + projectDuration);
|
||||||
|
|
||||||
log.info(" 场地 {} 时段 {} 已用时间更新为 {} 分钟", venueName, timeSlot.getStartTime(), venueTimeUsed.get(bestKey));
|
log.info(" 场地 {} 总负载更新为 {} 分钟", venueName, venueTotalUsed.get(venueId));
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("=== 分配完成: {}/{} 个分组成功分配 ===", assignedCount, groups.size());
|
log.info("=== 分配完成: {}/{} 个分组成功分配 ===", assignedCount, groups.size());
|
||||||
|
|
||||||
// 输出各场地时段的使用情况
|
// 输出使用统计
|
||||||
log.info("=== 各场地时段使用统计 ===");
|
log.info("=== 各场地总负载 ===");
|
||||||
for (Map.Entry<String, Integer> entry : venueTimeUsed.entrySet()) {
|
for (Map.Entry<Long, Integer> entry : venueTotalUsed.entrySet()) {
|
||||||
if (entry.getValue() > 0) {
|
final Long vid = entry.getKey();
|
||||||
String[] parts = entry.getKey().split("_");
|
String vname = venues.stream().filter(v -> v.getId().equals(vid)).findFirst().map(MartialVenue::getVenueName).orElse("未知");
|
||||||
Long venueId = Long.parseLong(parts[0]);
|
log.info("场地={}, 总负载={}分钟", vname, entry.getValue());
|
||||||
String venueName = venues.stream().filter(v -> v.getId().equals(venueId)).findFirst().map(MartialVenue::getVenueName).orElse("未知");
|
|
||||||
TimeSlot ts = venueTimeSlotMap.get(entry.getKey());
|
|
||||||
log.info("场地={}, 日期={}, 时段={}, 已用={}/{}分钟 ({}%)",
|
|
||||||
venueName, ts.getDate(), ts.getStartTime(), entry.getValue(), ts.getCapacity(),
|
|
||||||
(int)(entry.getValue() * 100.0 / ts.getCapacity()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -858,6 +954,7 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
private Integer totalParticipants;
|
private Integer totalParticipants;
|
||||||
private Integer totalTeams;
|
private Integer totalTeams;
|
||||||
private Integer estimatedDuration;
|
private Integer estimatedDuration;
|
||||||
|
private Integer maxParticipants; // 项目单位容纳人数
|
||||||
private List<MartialAthlete> athletes;
|
private List<MartialAthlete> athletes;
|
||||||
|
|
||||||
// 分配结果
|
// 分配结果
|
||||||
@@ -925,6 +1022,14 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
|||||||
this.estimatedDuration = estimatedDuration;
|
this.estimatedDuration = estimatedDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getMaxParticipants() {
|
||||||
|
return maxParticipants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxParticipants(Integer maxParticipants) {
|
||||||
|
this.maxParticipants = maxParticipants;
|
||||||
|
}
|
||||||
|
|
||||||
public List<MartialAthlete> getAthletes() {
|
public List<MartialAthlete> getAthletes() {
|
||||||
return athletes;
|
return athletes;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user