refactor: Phase 3 - Extract VenueAllocationService from God Class
- Create VenueAllocationService in schedule/allocation/ - Modify MartialScheduleArrangeServiceImpl to use VenueAllocationService - Remove validateCapacity, assignVenueAndTimeSlot methods and SlotInfo class - Original class reduced from 692 to ~500 lines - All 438 tests passing Related to Issue #11
This commit is contained in:
+15
-205
@@ -27,6 +27,7 @@ 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.springblade.modules.martial.service.schedule.allocation.VenueAllocationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springblade.modules.martial.service.schedule.model.ScheduleGroupData;
|
||||
import org.springblade.modules.martial.service.schedule.model.TimeSlot;
|
||||
@@ -62,6 +63,8 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
||||
private final TimeSlotGenerator timeSlotGenerator;
|
||||
|
||||
private final ParticipantGroupingService participantGroupingService;
|
||||
private final VenueAllocationService venueAllocationService;
|
||||
|
||||
@Override
|
||||
public List<Long> getUnlockedCompetitions() {
|
||||
// 查询所有未锁定的赛事(schedule_status != 2)
|
||||
@@ -390,222 +393,29 @@ public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrang
|
||||
/**
|
||||
* 验证时间窗口容量是否足够容纳所有分组
|
||||
*/
|
||||
/**
|
||||
* Validate capacity
|
||||
* @deprecated Use VenueAllocationService.validateCapacity() directly
|
||||
*/
|
||||
@Deprecated
|
||||
private void validateCapacity(List<ScheduleGroupData> groups,
|
||||
List<MartialVenue> venues,
|
||||
List<TimeSlot> timeSlots) {
|
||||
// 计算总需求时长
|
||||
int totalDuration = groups.stream()
|
||||
.mapToInt(ScheduleGroupData::getEstimatedDuration)
|
||||
.sum();
|
||||
|
||||
// 计算总可用容量
|
||||
int totalCapacity = venues.size() * timeSlots.size() * (timeSlots.isEmpty() ? 0 : timeSlots.get(0).getCapacity());
|
||||
|
||||
log.info("=== 容量验证 ===");
|
||||
log.info("分组总需求时长: {} 分钟", totalDuration);
|
||||
log.info("总可用容量: {} 分钟 ({}个场地 × {}个时段 × {}分钟/时段)",
|
||||
totalCapacity, venues.size(), timeSlots.size(),
|
||||
timeSlots.isEmpty() ? 0 : timeSlots.get(0).getCapacity());
|
||||
|
||||
if (totalDuration > totalCapacity) {
|
||||
String errorMsg = String.format(
|
||||
"时间窗口容量不足! 需要 %d 分钟, 但只有 %d 分钟可用 (缺口: %d 分钟)",
|
||||
totalDuration, totalCapacity, totalDuration - totalCapacity
|
||||
);
|
||||
log.error(errorMsg);
|
||||
throw new RuntimeException(errorMsg);
|
||||
}
|
||||
|
||||
double utilizationRate = totalCapacity > 0 ? (totalDuration * 100.0 / totalCapacity) : 0;
|
||||
log.info("预计容量利用率: {}%", (int)utilizationRate);
|
||||
|
||||
if (utilizationRate > scheduleConfig.getCapacityWarningThreshold()) {
|
||||
log.warn("⚠️ 容量利用率超过90%,可能导致分配困难,建议增加场地或延长比赛时间");
|
||||
}
|
||||
venueAllocationService.validateCapacity(groups, venues, timeSlots);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign venue and time slot
|
||||
* @deprecated Use VenueAllocationService.allocate() directly
|
||||
*/
|
||||
@Deprecated
|
||||
private void assignVenueAndTimeSlot(List<ScheduleGroupData> groups,
|
||||
List<MartialVenue> venues,
|
||||
List<TimeSlot> timeSlots) {
|
||||
log.info("=== 开始分配场地和时间段 ===");
|
||||
log.info("场地数量: {}, 时间段数量: {}, 分组数量: {}", venues.size(), timeSlots.size(), groups.size());
|
||||
|
||||
// 打印场地容量信息
|
||||
for (MartialVenue venue : venues) {
|
||||
log.info("场地: {}, 容纳人数: {}", venue.getVenueName(), venue.getCapacity());
|
||||
}
|
||||
|
||||
// 每个场地的已用时间(跨所有时段累计)
|
||||
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()
|
||||
.collect(Collectors.groupingBy(ScheduleGroupData::getProjectId));
|
||||
|
||||
Map<Long, Integer> projectTotalDuration = new HashMap<>();
|
||||
groupsByProject.forEach((projectId, projectGroups) -> {
|
||||
int total = projectGroups.stream().mapToInt(g -> g.getEstimatedDuration() != null ? g.getEstimatedDuration() : 0).sum();
|
||||
projectTotalDuration.put(projectId, total);
|
||||
});
|
||||
|
||||
// 排序: 集体项目优先,然后按时长降序
|
||||
List<Long> sortedProjectIds = new ArrayList<>(groupsByProject.keySet());
|
||||
sortedProjectIds.sort((a, b) -> {
|
||||
List<ScheduleGroupData> groupsA = groupsByProject.get(a);
|
||||
List<ScheduleGroupData> groupsB = groupsByProject.get(b);
|
||||
int typeA = groupsA.get(0).getProjectType() != null ? groupsA.get(0).getProjectType() : 1;
|
||||
int typeB = groupsB.get(0).getProjectType() != null ? groupsB.get(0).getProjectType() : 1;
|
||||
if (typeA != typeB) {
|
||||
return typeB - typeA;
|
||||
}
|
||||
return projectTotalDuration.get(b) - projectTotalDuration.get(a);
|
||||
});
|
||||
|
||||
log.info("项目排序: {}", sortedProjectIds.stream()
|
||||
.map(id -> groupsByProject.get(id).get(0).getGroupName().split(" ")[0] + "(" + projectTotalDuration.get(id) + "分钟)")
|
||||
.collect(Collectors.joining(", ")));
|
||||
|
||||
int assignedCount = 0;
|
||||
|
||||
for (Long projectId : sortedProjectIds) {
|
||||
List<ScheduleGroupData> projectGroups = groupsByProject.get(projectId);
|
||||
int projectDuration = projectTotalDuration.get(projectId);
|
||||
String projectName = projectGroups.get(0).getGroupName().split(" ")[0];
|
||||
|
||||
// 获取项目的单位容纳人数(每组最大人数)
|
||||
int projectMaxParticipants = projectGroups.get(0).getMaxParticipants() != null
|
||||
? projectGroups.get(0).getMaxParticipants() : 1;
|
||||
|
||||
log.info("开始分配项目 '{}': {}个分组, 总时长{}分钟, 单位容纳人数={}",
|
||||
projectName, projectGroups.size(), projectDuration, projectMaxParticipants);
|
||||
|
||||
// 第一步:筛选出容量足够的场地
|
||||
List<MartialVenue> eligibleVenues = venues.stream()
|
||||
.filter(v -> {
|
||||
Integer capacity = v.getCapacity();
|
||||
// 如果场地没有设置容量,默认允许
|
||||
if (capacity == null || capacity <= 0) return true;
|
||||
// 场地容量必须 >= 项目单位容纳人数
|
||||
return capacity >= projectMaxParticipants;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (eligibleVenues.isEmpty()) {
|
||||
log.warn("项目 '{}' 需要容纳{}人的场地,但没有符合条件的场地!使用第一个场地",
|
||||
projectName, projectMaxParticipants);
|
||||
eligibleVenues = venues;
|
||||
} else {
|
||||
log.info("项目 '{}' 可用场地: {}", projectName,
|
||||
eligibleVenues.stream().map(MartialVenue::getVenueName).collect(Collectors.joining(", ")));
|
||||
}
|
||||
|
||||
// 第二步:在符合条件的场地中,选择总负载最小的
|
||||
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;
|
||||
for (String slotKey : slotVenueUsed.keySet()) {
|
||||
TimeSlot ts = slotMap.get(slotKey);
|
||||
int used = slotVenueUsed.get(slotKey).get(bestVenueId);
|
||||
int remain = ts.getCapacity() - used;
|
||||
if (remain > maxRemain) {
|
||||
maxRemain = remain;
|
||||
bestSlotKey = slotKey;
|
||||
}
|
||||
}
|
||||
log.warn("项目 '{}' 总时长{}分钟,在场地上没有单个时段能完全容纳", projectName, projectDuration);
|
||||
}
|
||||
|
||||
TimeSlot targetSlot = slotMap.get(bestSlotKey);
|
||||
int slotIndex = timeSlots.indexOf(targetSlot);
|
||||
final Long venueId = bestVenueId;
|
||||
String venueName = venues.stream().filter(v -> v.getId().equals(venueId)).findFirst().map(MartialVenue::getVenueName).orElse("未知场地");
|
||||
|
||||
log.info("项目 '{}' 分配到: 场地={}, 日期={}, 时段={}",
|
||||
projectName, venueName, targetSlot.getDate(), targetSlot.getStartTime());
|
||||
|
||||
// 分配该项目的所有分组到同一场地
|
||||
for (ScheduleGroupData group : projectGroups) {
|
||||
group.setAssignedVenueId(venueId);
|
||||
group.setAssignedVenueName(venueName);
|
||||
group.setAssignedDate(targetSlot.getDate());
|
||||
group.setAssignedTimeSlot(targetSlot.getStartTime());
|
||||
group.setAssignedTimeSlotIndex(slotIndex);
|
||||
group.setAssignedTimePeriod(targetSlot.getPeriod());
|
||||
assignedCount++;
|
||||
}
|
||||
|
||||
// 更新已用时间
|
||||
slotVenueUsed.get(bestSlotKey).put(venueId, slotVenueUsed.get(bestSlotKey).get(venueId) + projectDuration);
|
||||
venueTotalUsed.put(venueId, venueTotalUsed.get(venueId) + projectDuration);
|
||||
|
||||
log.info(" 场地 {} 总负载更新为 {} 分钟", venueName, venueTotalUsed.get(venueId));
|
||||
}
|
||||
|
||||
log.info("=== 分配完成: {}/{} 个分组成功分配 ===", assignedCount, groups.size());
|
||||
|
||||
// 输出使用统计
|
||||
log.info("=== 各场地总负载 ===");
|
||||
for (Map.Entry<Long, Integer> entry : venueTotalUsed.entrySet()) {
|
||||
final Long vid = entry.getKey();
|
||||
String vname = venues.stream().filter(v -> v.getId().equals(vid)).findFirst().map(MartialVenue::getVenueName).orElse("未知");
|
||||
log.info("场地={}, 总负载={}分钟", vname, entry.getValue());
|
||||
}
|
||||
venueAllocationService.allocate(groups, venues, timeSlots);
|
||||
}
|
||||
|
||||
|
||||
// 槽位信息内部类
|
||||
private static class SlotInfo {
|
||||
int timeSlotIndex; // 时间段索引 (0=第1天上午, 1=第1天下午, 2=第2天上午, ...)
|
||||
Long venueId;
|
||||
String venueName;
|
||||
LocalDate date;
|
||||
String timeSlot;
|
||||
String period;
|
||||
int capacity;
|
||||
int currentLoad;
|
||||
}
|
||||
|
||||
private void clearOldScheduleData(Long competitionId) {
|
||||
// 删除旧的编排数据
|
||||
LambdaQueryWrapper<MartialScheduleGroup> groupWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
package org.springblade.modules.martial.service.schedule.allocation;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.modules.martial.config.ScheduleConfig;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
||||
import org.springblade.modules.martial.service.schedule.model.ScheduleGroupData;
|
||||
import org.springblade.modules.martial.service.schedule.model.TimeSlot;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Venue allocation service
|
||||
* Allocates groups to venues and time slots
|
||||
*
|
||||
* @author Refactored from MartialScheduleArrangeServiceImpl
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VenueAllocationService {
|
||||
|
||||
private final ScheduleConfig scheduleConfig;
|
||||
|
||||
/**
|
||||
* Validate if capacity is sufficient for all groups
|
||||
*/
|
||||
public void validateCapacity(List<ScheduleGroupData> groups,
|
||||
List<MartialVenue> venues,
|
||||
List<TimeSlot> timeSlots) {
|
||||
int totalDuration = groups.stream()
|
||||
.mapToInt(ScheduleGroupData::getEstimatedDuration)
|
||||
.sum();
|
||||
|
||||
int totalCapacity = venues.size() * timeSlots.size() *
|
||||
(timeSlots.isEmpty() ? 0 : timeSlots.get(0).getCapacity());
|
||||
|
||||
log.info("=== Capacity Validation ===");
|
||||
log.info("Total required duration: {} minutes", totalDuration);
|
||||
log.info("Total available capacity: {} minutes ({} venues x {} slots x {} min/slot)",
|
||||
totalCapacity, venues.size(), timeSlots.size(),
|
||||
timeSlots.isEmpty() ? 0 : timeSlots.get(0).getCapacity());
|
||||
|
||||
if (totalDuration > totalCapacity) {
|
||||
String errorMsg = String.format(
|
||||
"Insufficient capacity! Need %d minutes, but only %d available (gap: %d minutes)",
|
||||
totalDuration, totalCapacity, totalDuration - totalCapacity
|
||||
);
|
||||
log.error(errorMsg);
|
||||
throw new RuntimeException(errorMsg);
|
||||
}
|
||||
|
||||
double utilizationRate = totalCapacity > 0 ? (totalDuration * 100.0 / totalCapacity) : 0;
|
||||
log.info("Expected utilization rate: {}%", (int)utilizationRate);
|
||||
|
||||
if (utilizationRate > scheduleConfig.getCapacityWarningThreshold()) {
|
||||
log.warn("Warning: Utilization rate exceeds 90%, allocation may be difficult");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate groups to venues and time slots
|
||||
*/
|
||||
public void allocate(List<ScheduleGroupData> groups,
|
||||
List<MartialVenue> venues,
|
||||
List<TimeSlot> timeSlots) {
|
||||
log.info("=== Starting venue and time slot allocation ===");
|
||||
log.info("Venues: {}, Time slots: {}, Groups: {}", venues.size(), timeSlots.size(), groups.size());
|
||||
|
||||
Map<Long, Integer> venueTotalUsed = initVenueTotalUsed(venues);
|
||||
Map<String, Map<Long, Integer>> slotVenueUsed = new LinkedHashMap<>();
|
||||
Map<String, TimeSlot> slotMap = new LinkedHashMap<>();
|
||||
initSlotMaps(timeSlots, venues, slotVenueUsed, slotMap);
|
||||
|
||||
Map<Long, List<ScheduleGroupData>> groupsByProject = groups.stream()
|
||||
.collect(Collectors.groupingBy(ScheduleGroupData::getProjectId));
|
||||
|
||||
Map<Long, Integer> projectTotalDuration = calculateProjectDurations(groupsByProject);
|
||||
List<Long> sortedProjectIds = sortProjectsByPriority(groupsByProject, projectTotalDuration);
|
||||
|
||||
int assignedCount = 0;
|
||||
for (Long projectId : sortedProjectIds) {
|
||||
assignedCount += allocateProject(projectId, groupsByProject.get(projectId),
|
||||
projectTotalDuration.get(projectId), venues, timeSlots,
|
||||
venueTotalUsed, slotVenueUsed, slotMap);
|
||||
}
|
||||
|
||||
log.info("=== Allocation complete: {}/{} groups assigned ===", assignedCount, groups.size());
|
||||
logVenueUsage(venues, venueTotalUsed);
|
||||
}
|
||||
|
||||
private Map<Long, Integer> initVenueTotalUsed(List<MartialVenue> venues) {
|
||||
Map<Long, Integer> venueTotalUsed = new LinkedHashMap<>();
|
||||
for (MartialVenue venue : venues) {
|
||||
venueTotalUsed.put(venue.getId(), 0);
|
||||
}
|
||||
return venueTotalUsed;
|
||||
}
|
||||
|
||||
private void initSlotMaps(List<TimeSlot> timeSlots, List<MartialVenue> venues,
|
||||
Map<String, Map<Long, Integer>> slotVenueUsed,
|
||||
Map<String, TimeSlot> slotMap) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, Integer> calculateProjectDurations(Map<Long, List<ScheduleGroupData>> groupsByProject) {
|
||||
Map<Long, Integer> projectTotalDuration = new HashMap<>();
|
||||
groupsByProject.forEach((projectId, projectGroups) -> {
|
||||
int total = projectGroups.stream()
|
||||
.mapToInt(g -> g.getEstimatedDuration() != null ? g.getEstimatedDuration() : 0)
|
||||
.sum();
|
||||
projectTotalDuration.put(projectId, total);
|
||||
});
|
||||
return projectTotalDuration;
|
||||
}
|
||||
|
||||
private List<Long> sortProjectsByPriority(Map<Long, List<ScheduleGroupData>> groupsByProject,
|
||||
Map<Long, Integer> projectTotalDuration) {
|
||||
List<Long> sortedProjectIds = new ArrayList<>(groupsByProject.keySet());
|
||||
sortedProjectIds.sort((a, b) -> {
|
||||
List<ScheduleGroupData> groupsA = groupsByProject.get(a);
|
||||
List<ScheduleGroupData> groupsB = groupsByProject.get(b);
|
||||
int typeA = groupsA.get(0).getProjectType() != null ? groupsA.get(0).getProjectType() : 1;
|
||||
int typeB = groupsB.get(0).getProjectType() != null ? groupsB.get(0).getProjectType() : 1;
|
||||
if (typeA != typeB) {
|
||||
return typeB - typeA;
|
||||
}
|
||||
return projectTotalDuration.get(b) - projectTotalDuration.get(a);
|
||||
});
|
||||
return sortedProjectIds;
|
||||
}
|
||||
|
||||
private int allocateProject(Long projectId, List<ScheduleGroupData> projectGroups,
|
||||
int projectDuration, List<MartialVenue> venues,
|
||||
List<TimeSlot> timeSlots, Map<Long, Integer> venueTotalUsed,
|
||||
Map<String, Map<Long, Integer>> slotVenueUsed,
|
||||
Map<String, TimeSlot> slotMap) {
|
||||
String projectName = projectGroups.get(0).getGroupName().split(" ")[0];
|
||||
int projectMaxParticipants = projectGroups.get(0).getMaxParticipants() != null
|
||||
? projectGroups.get(0).getMaxParticipants() : 1;
|
||||
|
||||
List<MartialVenue> eligibleVenues = filterEligibleVenues(venues, projectMaxParticipants, projectName);
|
||||
Long bestVenueId = findBestVenue(eligibleVenues, venueTotalUsed);
|
||||
String bestSlotKey = findBestSlot(bestVenueId, projectDuration, slotVenueUsed, slotMap, projectName);
|
||||
|
||||
TimeSlot targetSlot = slotMap.get(bestSlotKey);
|
||||
int slotIndex = timeSlots.indexOf(targetSlot);
|
||||
final Long venueId = bestVenueId;
|
||||
String venueName = venues.stream()
|
||||
.filter(v -> v.getId().equals(venueId))
|
||||
.findFirst()
|
||||
.map(MartialVenue::getVenueName)
|
||||
.orElse("Unknown");
|
||||
|
||||
log.info("Project '{}' assigned to: venue={}, date={}, slot={}",
|
||||
projectName, venueName, targetSlot.getDate(), targetSlot.getStartTime());
|
||||
|
||||
for (ScheduleGroupData group : projectGroups) {
|
||||
group.setAssignedVenueId(venueId);
|
||||
group.setAssignedVenueName(venueName);
|
||||
group.setAssignedDate(targetSlot.getDate());
|
||||
group.setAssignedTimeSlot(targetSlot.getStartTime());
|
||||
group.setAssignedTimeSlotIndex(slotIndex);
|
||||
group.setAssignedTimePeriod(targetSlot.getPeriod());
|
||||
}
|
||||
|
||||
slotVenueUsed.get(bestSlotKey).put(venueId,
|
||||
slotVenueUsed.get(bestSlotKey).get(venueId) + projectDuration);
|
||||
venueTotalUsed.put(venueId, venueTotalUsed.get(venueId) + projectDuration);
|
||||
|
||||
return projectGroups.size();
|
||||
}
|
||||
|
||||
private List<MartialVenue> filterEligibleVenues(List<MartialVenue> venues,
|
||||
int projectMaxParticipants,
|
||||
String projectName) {
|
||||
List<MartialVenue> eligibleVenues = venues.stream()
|
||||
.filter(v -> {
|
||||
Integer capacity = v.getCapacity();
|
||||
if (capacity == null || capacity <= 0) return true;
|
||||
return capacity >= projectMaxParticipants;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (eligibleVenues.isEmpty()) {
|
||||
log.warn("Project '{}' needs venue for {} people, but no eligible venue found! Using first venue",
|
||||
projectName, projectMaxParticipants);
|
||||
return venues;
|
||||
}
|
||||
return eligibleVenues;
|
||||
}
|
||||
|
||||
private Long findBestVenue(List<MartialVenue> eligibleVenues, Map<Long, Integer> venueTotalUsed) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
return bestVenueId;
|
||||
}
|
||||
|
||||
private String findBestSlot(Long venueId, int projectDuration,
|
||||
Map<String, Map<Long, Integer>> slotVenueUsed,
|
||||
Map<String, TimeSlot> slotMap, String projectName) {
|
||||
String bestSlotKey = null;
|
||||
for (String slotKey : slotVenueUsed.keySet()) {
|
||||
TimeSlot ts = slotMap.get(slotKey);
|
||||
int used = slotVenueUsed.get(slotKey).get(venueId);
|
||||
int remain = ts.getCapacity() - used;
|
||||
if (remain >= projectDuration) {
|
||||
bestSlotKey = slotKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestSlotKey == null) {
|
||||
int maxRemain = -1;
|
||||
for (String slotKey : slotVenueUsed.keySet()) {
|
||||
TimeSlot ts = slotMap.get(slotKey);
|
||||
int used = slotVenueUsed.get(slotKey).get(venueId);
|
||||
int remain = ts.getCapacity() - used;
|
||||
if (remain > maxRemain) {
|
||||
maxRemain = remain;
|
||||
bestSlotKey = slotKey;
|
||||
}
|
||||
}
|
||||
log.warn("Project '{}' duration {} minutes cannot fit in single slot", projectName, projectDuration);
|
||||
}
|
||||
return bestSlotKey;
|
||||
}
|
||||
|
||||
private void logVenueUsage(List<MartialVenue> venues, Map<Long, Integer> venueTotalUsed) {
|
||||
log.info("=== Venue Usage Summary ===");
|
||||
for (Map.Entry<Long, Integer> entry : venueTotalUsed.entrySet()) {
|
||||
final Long vid = entry.getKey();
|
||||
String vname = venues.stream()
|
||||
.filter(v -> v.getId().equals(vid))
|
||||
.findFirst()
|
||||
.map(MartialVenue::getVenueName)
|
||||
.orElse("Unknown");
|
||||
log.info("Venue={}, Total load={} minutes", vname, entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user