|
|
@@ -45,6 +45,9 @@ import org.dromara.xg.mapper.XgSysDeptMapper;
|
|
|
import org.dromara.xg.domain.XgSysDept;
|
|
|
import java.util.stream.Collectors;
|
|
|
import java.util.LinkedHashMap;
|
|
|
+import org.dromara.xg.domain.bo.XgDormBedBatchAllocateToClassBo;
|
|
|
+import org.dromara.xg.mapper.XgClassMsgMapper;
|
|
|
+import org.dromara.xg.domain.XgClassMsg;
|
|
|
|
|
|
/**
|
|
|
* 床位学生分配关系Service业务层处理
|
|
|
@@ -65,6 +68,7 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
|
|
|
private final XgDormCampusMapper xgDormCampusMapper;
|
|
|
private final XgDormAreaMapper xgDormAreaMapper;
|
|
|
private final XgSysDeptMapper xgSysDeptMapper;
|
|
|
+ private final XgClassMsgMapper xgClassMsgMapper;
|
|
|
|
|
|
/**
|
|
|
* 查询床位学生分配关系
|
|
|
@@ -504,6 +508,307 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 批量分配床位给班级
|
|
|
+ *
|
|
|
+ * @param bo 批量分配给班级请求参数
|
|
|
+ * @return 是否分配成功
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public Boolean batchAllocateBedsToClass(XgDormBedBatchAllocateToClassBo bo) {
|
|
|
+ try {
|
|
|
+ // 1. 获取床位信息
|
|
|
+ List<XgDormBed> beds = xgDormBedMapper.selectBatchIds(bo.getBedIds());
|
|
|
+
|
|
|
+ // 2. 校验床位是否已分配给班级(检查xg_dorm_bed_student表中的classId)
|
|
|
+ LambdaQueryWrapper<XgDormBedStudent> classCheckWrapper = new LambdaQueryWrapper<>();
|
|
|
+ classCheckWrapper.in(XgDormBedStudent::getBedId, bo.getBedIds())
|
|
|
+ .isNotNull(XgDormBedStudent::getClassId)
|
|
|
+ .eq(XgDormBedStudent::getStatus, "0"); // 正常状态,未退宿
|
|
|
+
|
|
|
+ List<XgDormBedStudent> existingClassAllocations = baseMapper.selectList(classCheckWrapper);
|
|
|
+
|
|
|
+ if (!existingClassAllocations.isEmpty()) {
|
|
|
+ // 找出已分配的床位名称
|
|
|
+ Set<Long> allocatedBedIds = existingClassAllocations.stream()
|
|
|
+ .map(XgDormBedStudent::getBedId)
|
|
|
+ .collect(java.util.stream.Collectors.toSet());
|
|
|
+
|
|
|
+ List<String> allocatedBedNames = beds.stream()
|
|
|
+ .filter(bed -> allocatedBedIds.contains(bed.getId()))
|
|
|
+ .map(XgDormBed::getBedName)
|
|
|
+ .collect(java.util.stream.Collectors.toList());
|
|
|
+
|
|
|
+ throw new RuntimeException("以下床位已分配给班级,无法重复分配:" + String.join("、", allocatedBedNames));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 检查床位是否已分配给学院(必须先分配给学院才能分配给班级)
|
|
|
+ LambdaQueryWrapper<XgDormBedStudent> collegeCheckWrapper = new LambdaQueryWrapper<>();
|
|
|
+ collegeCheckWrapper.in(XgDormBedStudent::getBedId, bo.getBedIds())
|
|
|
+ .isNotNull(XgDormBedStudent::getCollegeId)
|
|
|
+ .isNull(XgDormBedStudent::getClassId) // 只分配了学院,还没分配班级
|
|
|
+ .eq(XgDormBedStudent::getStatus, "0"); // 正常状态,未退宿
|
|
|
+
|
|
|
+ List<XgDormBedStudent> existingCollegeAllocations = baseMapper.selectList(collegeCheckWrapper);
|
|
|
+
|
|
|
+ if (existingCollegeAllocations.isEmpty()) {
|
|
|
+ throw new RuntimeException("选择的床位尚未分配给学院,请先分配给学院后再分配给班级");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 验证所有床位都已分配给学院
|
|
|
+ Set<Long> collegeAllocatedBedIds = existingCollegeAllocations.stream()
|
|
|
+ .map(XgDormBedStudent::getBedId)
|
|
|
+ .collect(java.util.stream.Collectors.toSet());
|
|
|
+
|
|
|
+ List<Long> unallocatedBedIds = bo.getBedIds().stream()
|
|
|
+ .filter(bedId -> !collegeAllocatedBedIds.contains(bedId))
|
|
|
+ .collect(java.util.stream.Collectors.toList());
|
|
|
+
|
|
|
+ if (!unallocatedBedIds.isEmpty()) {
|
|
|
+ List<String> unallocatedBedNames = beds.stream()
|
|
|
+ .filter(bed -> unallocatedBedIds.contains(bed.getId()))
|
|
|
+ .map(XgDormBed::getBedName)
|
|
|
+ .collect(java.util.stream.Collectors.toList());
|
|
|
+
|
|
|
+ throw new RuntimeException("以下床位尚未分配给学院,无法分配给班级:" + String.join("、", unallocatedBedNames));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 获取楼栋信息,用于获取校区ID和宿舍区ID
|
|
|
+ XgDormBuilding building = xgDormBuildingMapper.selectById(bo.getBuildingId());
|
|
|
+ if (building == null) {
|
|
|
+ throw new RuntimeException("楼栋信息不存在,buildingId: " + bo.getBuildingId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 查询班级信息,获取专业ID和学院ID
|
|
|
+ List<XgClassMsg> classes = xgClassMsgMapper.selectBatchIds(bo.getClassIds());
|
|
|
+ if (classes.size() != bo.getClassIds().size()) {
|
|
|
+ throw new RuntimeException("部分班级信息不存在,请检查班级ID");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 建立班级ID到班级信息的映射
|
|
|
+ Map<Long, XgClassMsg> classMap = classes.stream()
|
|
|
+ .collect(java.util.stream.Collectors.toMap(XgClassMsg::getId, cls -> cls));
|
|
|
+
|
|
|
+ // 7. 计算每个班级分配的床位数量
|
|
|
+ Date now = new Date();
|
|
|
+ Long loginUserId = LoginHelper.getUserId();
|
|
|
+ int totalAvailableBeds = collegeAllocatedBedIds.size();
|
|
|
+
|
|
|
+ // 如果没有提供班级人数信息,则平均分配
|
|
|
+ List<Integer> classCounts = bo.getClassStudentCounts();
|
|
|
+ if (classCounts == null || classCounts.isEmpty()) {
|
|
|
+ // 平均分配:每个班级分配的床位数
|
|
|
+ int bedsPerClass = totalAvailableBeds / bo.getClassIds().size();
|
|
|
+ int remainingBeds = totalAvailableBeds % bo.getClassIds().size();
|
|
|
+
|
|
|
+ classCounts = new ArrayList<>();
|
|
|
+ for (int i = 0; i < bo.getClassIds().size(); i++) {
|
|
|
+ int count = bedsPerClass + (i < remainingBeds ? 1 : 0);
|
|
|
+ classCounts.add(count);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证班级人数总和不能超过可用床位总数
|
|
|
+ int totalStudents = classCounts.stream().mapToInt(Integer::intValue).sum();
|
|
|
+ if (totalStudents > totalAvailableBeds) {
|
|
|
+ throw new RuntimeException(
|
|
|
+ "班级人数总和(" + totalStudents + ")超过了可用床位总数(" + totalAvailableBeds + "),请调整分配数量");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("床位分配验证通过:可用床位数{},分配学生数{},剩余床位数{}",
|
|
|
+ totalAvailableBeds, totalStudents, totalAvailableBeds - totalStudents);
|
|
|
+
|
|
|
+ // 8. 获取房间信息,建立roomId到roomCode的映射
|
|
|
+ Set<Long> roomIds = beds.stream().map(XgDormBed::getRoomId).collect(java.util.stream.Collectors.toSet());
|
|
|
+ List<XgDormRoom> rooms = xgDormRoomMapper.selectBatchIds(roomIds);
|
|
|
+ Map<Long, String> roomCodeMap = rooms.stream()
|
|
|
+ .collect(java.util.stream.Collectors.toMap(XgDormRoom::getId, XgDormRoom::getRoomCode));
|
|
|
+
|
|
|
+ // 9. 将床位按房间顺序排列,实现房间优先分配
|
|
|
+ // 获取已分配给学院的床位详细信息
|
|
|
+ List<XgDormBed> availableBeds = beds.stream()
|
|
|
+ .filter(bed -> collegeAllocatedBedIds.contains(bed.getId()))
|
|
|
+ .collect(java.util.stream.Collectors.toList());
|
|
|
+
|
|
|
+ // 按房间编码排序床位,确保同一房间的床位连续
|
|
|
+ List<XgDormBed> sortedBeds = availableBeds.stream()
|
|
|
+ .sorted((b1, b2) -> {
|
|
|
+ // 首先按房间编码排序
|
|
|
+ String roomCode1 = roomCodeMap.get(b1.getRoomId());
|
|
|
+ String roomCode2 = roomCodeMap.get(b2.getRoomId());
|
|
|
+ int roomCompare = roomCode1.compareTo(roomCode2);
|
|
|
+ if (roomCompare != 0) {
|
|
|
+ return roomCompare;
|
|
|
+ }
|
|
|
+ // 同一房间内按床位编码排序
|
|
|
+ return b1.getBedCode().compareTo(b2.getBedCode());
|
|
|
+ })
|
|
|
+ .collect(java.util.stream.Collectors.toList());
|
|
|
+
|
|
|
+ // 10. 按房间分组床位,实现房间优先分配策略
|
|
|
+ Map<Long, List<XgDormBed>> bedsByRoom = sortedBeds.stream()
|
|
|
+ .collect(java.util.stream.Collectors.groupingBy(XgDormBed::getRoomId));
|
|
|
+
|
|
|
+ // 获取排序后的房间ID列表
|
|
|
+ List<Long> sortedRoomIds = new ArrayList<>(bedsByRoom.keySet());
|
|
|
+ sortedRoomIds.sort((r1, r2) -> roomCodeMap.get(r1).compareTo(roomCodeMap.get(r2)));
|
|
|
+
|
|
|
+ // 11. 房间优先分配算法(四轮分配策略)
|
|
|
+ // 房间使用状态跟踪
|
|
|
+ int[] roomUsage = new int[sortedRoomIds.size()]; // 记录每个房间已使用床位数
|
|
|
+ Map<Integer, Long> roomClassMap = new java.util.HashMap<>(); // 记录房间主要分配给哪个班级
|
|
|
+
|
|
|
+ // 按班级顺序,采用房间优先分配策略
|
|
|
+ for (int classIndex = 0; classIndex < bo.getClassIds().size(); classIndex++) {
|
|
|
+ Long classId = bo.getClassIds().get(classIndex);
|
|
|
+ XgClassMsg classInfo = classMap.get(classId);
|
|
|
+ int remainingStudents = classCounts.get(classIndex);
|
|
|
+
|
|
|
+ log.info("开始为班级{}分配{}个床位,采用房间优先策略", classInfo.getClassName(), remainingStudents);
|
|
|
+
|
|
|
+ // 第一轮:优先分配完整房间
|
|
|
+ for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) {
|
|
|
+ Long roomId = sortedRoomIds.get(roomIndex);
|
|
|
+ List<XgDormBed> roomBeds = bedsByRoom.get(roomId);
|
|
|
+ int roomCapacity = roomBeds.size();
|
|
|
+
|
|
|
+ // 如果房间空闲且学生数足够分配整个房间
|
|
|
+ if (roomUsage[roomIndex] == 0 && remainingStudents >= roomCapacity) {
|
|
|
+ // 分配整个房间给当前班级
|
|
|
+ roomUsage[roomIndex] = roomCapacity;
|
|
|
+ roomClassMap.put(roomIndex, classId);
|
|
|
+ remainingStudents -= roomCapacity;
|
|
|
+
|
|
|
+ log.info("为班级{}分配完整房间{},房间编码{},床位数{}",
|
|
|
+ classInfo.getClassName(), roomId, roomCodeMap.get(roomId), roomCapacity);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 第二轮:处理剩余学生(优先分配到已有该班级学生的房间)
|
|
|
+ for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) {
|
|
|
+ Long roomId = sortedRoomIds.get(roomIndex);
|
|
|
+ List<XgDormBed> roomBeds = bedsByRoom.get(roomId);
|
|
|
+ int availableBedsCount = roomBeds.size() - roomUsage[roomIndex];
|
|
|
+
|
|
|
+ // 优先分配到已有该班级学生的房间
|
|
|
+ if (availableBedsCount > 0 && classId.equals(roomClassMap.get(roomIndex))) {
|
|
|
+ int bedsToAllocate = Math.min(remainingStudents, availableBedsCount);
|
|
|
+ roomUsage[roomIndex] += bedsToAllocate;
|
|
|
+ remainingStudents -= bedsToAllocate;
|
|
|
+
|
|
|
+ log.info("为班级{}在已分配房间{}补充{}个床位",
|
|
|
+ classInfo.getClassName(), roomId, bedsToAllocate);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 第三轮:如果还有剩余学生,分配到空闲房间(避免混住)
|
|
|
+ for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) {
|
|
|
+ Long roomId = sortedRoomIds.get(roomIndex);
|
|
|
+ List<XgDormBed> roomBeds = bedsByRoom.get(roomId);
|
|
|
+ int availableBedsCount = roomBeds.size() - roomUsage[roomIndex];
|
|
|
+
|
|
|
+ // 分配到完全空闲的房间
|
|
|
+ if (availableBedsCount > 0 && roomClassMap.get(roomIndex) == null) {
|
|
|
+ int bedsToAllocate = Math.min(remainingStudents, availableBedsCount);
|
|
|
+ roomUsage[roomIndex] += bedsToAllocate;
|
|
|
+ roomClassMap.put(roomIndex, classId);
|
|
|
+ remainingStudents -= bedsToAllocate;
|
|
|
+
|
|
|
+ log.info("为班级{}分配空闲房间{}的{}个床位",
|
|
|
+ classInfo.getClassName(), roomId, bedsToAllocate);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 第四轮:确保所有学生都能分配到床位(允许混住)
|
|
|
+ for (int roomIndex = 0; roomIndex < sortedRoomIds.size() && remainingStudents > 0; roomIndex++) {
|
|
|
+ Long roomId = sortedRoomIds.get(roomIndex);
|
|
|
+ List<XgDormBed> roomBeds = bedsByRoom.get(roomId);
|
|
|
+ int availableBedsCount = roomBeds.size() - roomUsage[roomIndex];
|
|
|
+
|
|
|
+ // 分配到任何有空闲床位的房间,即使会产生混住
|
|
|
+ if (availableBedsCount > 0) {
|
|
|
+ int bedsToAllocate = Math.min(remainingStudents, availableBedsCount);
|
|
|
+ roomUsage[roomIndex] += bedsToAllocate;
|
|
|
+ remainingStudents -= bedsToAllocate;
|
|
|
+
|
|
|
+ Long existingClassId = roomClassMap.get(roomIndex);
|
|
|
+ if (existingClassId != null && !existingClassId.equals(classId)) {
|
|
|
+ XgClassMsg existingClass = classMap.get(existingClassId);
|
|
|
+ log.warn("房间{}产生混住:班级{}和班级{}",
|
|
|
+ roomCodeMap.get(roomId), existingClass.getClassName(), classInfo.getClassName());
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("为班级{}分配房间{}的{}个床位(可能混住)",
|
|
|
+ classInfo.getClassName(), roomId, bedsToAllocate);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否还有未分配的学生
|
|
|
+ if (remainingStudents > 0) {
|
|
|
+ throw new RuntimeException(
|
|
|
+ "班级" + classInfo.getClassName() + "还有" + remainingStudents + "个学生未能分配床位,床位资源不足");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("班级{}床位分配完成", classInfo.getClassName());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 12. 批量更新床位学生记录,补充班级和专业信息
|
|
|
+ for (int i = 0; i < bo.getClassIds().size(); i++) {
|
|
|
+ Long classId = bo.getClassIds().get(i);
|
|
|
+ XgClassMsg classInfo = classMap.get(classId);
|
|
|
+
|
|
|
+ // 计算应该分配给该班级的床位数量
|
|
|
+ int classStudentCount = classCounts.get(i);
|
|
|
+
|
|
|
+ // 获取该班级应该分配的床位ID列表(按分配算法确定的顺序)
|
|
|
+ List<Long> classBedIds = new ArrayList<>();
|
|
|
+ int allocatedCount = 0;
|
|
|
+
|
|
|
+ // 按房间分配顺序找出分配给该班级的床位
|
|
|
+ for (int roomIndex = 0; roomIndex < sortedRoomIds.size()
|
|
|
+ && allocatedCount < classStudentCount; roomIndex++) {
|
|
|
+ if (classId.equals(roomClassMap.get(roomIndex))) {
|
|
|
+ Long roomId = sortedRoomIds.get(roomIndex);
|
|
|
+ List<XgDormBed> roomBeds = bedsByRoom.get(roomId);
|
|
|
+
|
|
|
+ // 计算这个房间分配给该班级的床位数
|
|
|
+ int roomBedsForThisClass = Math.min(classStudentCount - allocatedCount, roomBeds.size());
|
|
|
+
|
|
|
+ for (int j = 0; j < roomBedsForThisClass; j++) {
|
|
|
+ classBedIds.add(roomBeds.get(j).getId());
|
|
|
+ allocatedCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!classBedIds.isEmpty()) {
|
|
|
+ // 批量更新该班级分配的床位记录
|
|
|
+ LambdaUpdateWrapper<XgDormBedStudent> updateWrapper = Wrappers.lambdaUpdate();
|
|
|
+ updateWrapper.in(XgDormBedStudent::getBedId, classBedIds)
|
|
|
+ .eq(XgDormBedStudent::getStatus, "0") // 正常状态
|
|
|
+ .isNull(XgDormBedStudent::getClassId) // 只更新还没分配班级的记录
|
|
|
+ .isNotNull(XgDormBedStudent::getCollegeId) // 已分配给学院
|
|
|
+ .set(XgDormBedStudent::getMajorId, classInfo.getMajorId())
|
|
|
+ .set(XgDormBedStudent::getClassId, classInfo.getId())
|
|
|
+ .set(XgDormBedStudent::getUpdateTime, now)
|
|
|
+ .set(XgDormBedStudent::getUpdateBy, loginUserId);
|
|
|
+
|
|
|
+ int updateCount = baseMapper.update(null, updateWrapper);
|
|
|
+ log.info("为班级{}({})更新了{}条床位分配记录", classInfo.getClassName(), classId, updateCount);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("成功更新{}个床位分配给{}个班级,总可用床位:{}",
|
|
|
+ bo.getBedIds().size(), bo.getClassIds().size(), totalAvailableBeds);
|
|
|
+ return true;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量分配床位给班级失败", e);
|
|
|
+ throw new RuntimeException("床位分配失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取当前用户二级学院的宿舍楼树形结构
|
|
|
* 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择
|