zlwu 1 anno fa
parent
commit
7b53c004ab

+ 15 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormBedStudentController.java

@@ -20,6 +20,7 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.xg.domain.vo.XgDormBedStudentVo;
 import org.dromara.xg.domain.bo.XgDormBedStudentBo;
 import org.dromara.xg.domain.bo.XgDormBedBatchAllocateBo;
+import org.dromara.xg.domain.bo.XgDormBedBatchAllocateToClassBo;
 import org.dromara.xg.service.IXgDormBedStudentService;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.xg.domain.bo.XgDormBedBatchRecallBo;
@@ -117,6 +118,20 @@ public class XgDormBedStudentController extends BaseController {
         return toAjax(xgDormBedStudentService.batchAllocateBedsToCollege(bo));
     }
 
+    /**
+     * 批量分配床位给班级
+     *
+     * @param bo 批量分配给班级参数
+     */
+    @SaCheckPermission("dormBedStudent:dormBedStudent:add")
+    @Log(title = "批量分配床位给班级", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping("/batchAllocateToClass")
+    public R<Void> batchAllocateBedsToClass(
+            @Validated(AddGroup.class) @RequestBody XgDormBedBatchAllocateToClassBo bo) {
+        return toAjax(xgDormBedStudentService.batchAllocateBedsToClass(bo));
+    }
+
     /**
      * 批量收回宿舍床位
      */

+ 49 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormBedBatchAllocateToClassBo.java

@@ -0,0 +1,49 @@
+package org.dromara.xg.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.util.List;
+
+/**
+ * 床位批量分配给班级业务对象 xg_dorm_bed_batch_allocate_to_class
+ *
+ * @author zlwu
+ * @date 2025-01-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class XgDormBedBatchAllocateToClassBo extends BaseEntity {
+
+    /**
+     * 床位ID列表
+     */
+    @NotEmpty(message = "床位ID列表不能为空", groups = { AddGroup.class })
+    private List<Long> bedIds;
+
+    /**
+     * 班级ID列表
+     */
+    @NotEmpty(message = "班级ID列表不能为空", groups = { AddGroup.class })
+    private List<Long> classIds;
+
+    /**
+     * 班级人数列表(与classIds一一对应)
+     */
+    private List<Integer> classStudentCounts;
+
+    /**
+     * 楼栋ID
+     */
+    private Long buildingId;
+
+    /**
+     * 楼栋类型(1-男生楼,2-女生楼)
+     */
+    private String buildingType;
+
+}

+ 68 - 60
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java

@@ -19,71 +19,79 @@ import java.util.Map;
  */
 public interface XgStudentMsgMapper extends BaseMapperPlus<XgStudentMsg, XgStudentMsgVo> {
 
-        /**
-         * 根据组织ID列表分页查询学生信息 用于导出
-         *
-         * @param page         分页参数
-         * @param queryWrapper 查询条件
-         * @param deptIds      组织ID列表
-         * @return 学生信息分页结果
-         */
-        List<XgStudentMsgExportVo> queryPageListExportByDeptId(
-                        @Param("ew") LambdaQueryWrapper<XgStudentMsg> queryWrapper,
-                        @Param("deptIds") List<Long> deptIds);
+    /**
+     * 根据组织ID列表分页查询学生信息 用于导出
+     *
+     * @param page         分页参数
+     * @param queryWrapper 查询条件
+     * @param deptIds      组织ID列表
+     * @return 学生信息分页结果
+     */
+    List<XgStudentMsgExportVo> queryPageListExportByDeptId(
+            @Param("ew") LambdaQueryWrapper<XgStudentMsg> queryWrapper,
+            @Param("deptIds") List<Long> deptIds);
 
-        /**
-         * 根据组织ID列表分页查询学生信息
-         *
-         * @param page         分页参数
-         * @param queryWrapper 查询条件
-         * @param deptIds      组织ID列表
-         * @return 学生信息分页结果
-         */
-        Page<XgStudentMsgVo> selectPageByDeptIds(@Param("page") Page<XgStudentMsgVo> page,
-                        @Param("ew") LambdaQueryWrapper<XgStudentMsg> queryWrapper,
-                        @Param("deptIds") List<Long> deptIds);
+    /**
+     * 根据组织ID列表分页查询学生信息
+     *
+     * @param page         分页参数
+     * @param queryWrapper 查询条件
+     * @param deptIds      组织ID列表
+     * @return 学生信息分页结果
+     */
+    Page<XgStudentMsgVo> selectPageByDeptIds(@Param("page") Page<XgStudentMsgVo> page,
+            @Param("ew") LambdaQueryWrapper<XgStudentMsg> queryWrapper,
+            @Param("deptIds") List<Long> deptIds);
 
-        /**
-         * 根据班级ID和姓名查询学生信息列表
-         *
-         * @param classId 班级ID
-         * @param name    学生姓名(模糊查询)
-         * @return 学生信息列表
-         */
-        List<XgStudentMsgVo> selectStudentsByClassId(@Param("classId") String classId, @Param("name") String name);
+    /**
+     * 根据班级ID和姓名查询学生信息列表
+     *
+     * @param classId 班级ID
+     * @param name    学生姓名(模糊查询)
+     * @return 学生信息列表
+     */
+    List<XgStudentMsgVo> selectStudentsByClassId(@Param("classId") String classId, @Param("name") String name);
 
-        /**
-         * 根据学生ID查询班主任ID
-         *
-         * @param studentId 学生ID
-         * @return 班主任ID
-         */
-        Long selectClassTeacherIdByStudentId(@Param("studentId") Long studentId);
+    /**
+     * 根据学生ID查询班主任ID
+     *
+     * @param studentId 学生ID
+     * @return 班主任ID
+     */
+    Long selectClassTeacherIdByStudentId(@Param("studentId") Long studentId);
 
-        /**
-         * 根据学生ID查询学院ID
-         *
-         * @param studentId 学生ID
-         * @return 学院ID
-         */
-        Long selectCollegeIdByStudentId(@Param("studentId") Long studentId);
+    /**
+     * 根据学生ID查询学院ID
+     *
+     * @param studentId 学生ID
+     * @return 学院ID
+     */
+    Long selectCollegeIdByStudentId(@Param("studentId") Long studentId);
 
-        /**
-         * 根据学院ID和角色Key查询具备该角色的用户列表
-         *
-         * @param collegeId 学院ID
-         * @param roleKey   角色Key
-         * @return 用户ID列表
-         */
-        List<Long> selectUserIdsByCollegeIdAndRoleKey(@Param("collegeId") Long collegeId,
-                        @Param("roleKey") String roleKey);
+    /**
+     * 根据学院ID和角色Key查询具备该角色的用户列表
+     *
+     * @param collegeId 学院ID
+     * @param roleKey   角色Key
+     * @return 用户ID列表
+     */
+    List<Long> selectUserIdsByCollegeIdAndRoleKey(@Param("collegeId") Long collegeId,
+            @Param("roleKey") String roleKey);
 
-        /**
-         * 查询二级学院下的学院、专业、班级学生统计信息
-         *
-         * @param secondaryCollegeId 二级学院ID
-         * @return 学生统计信息列表
-         */
-        List<Map<String, Object>> getCollegeStudentStats(@Param("secondaryCollegeId") Long secondaryCollegeId);
+    /**
+     * 查询二级学院下的学院、专业、班级学生统计信息
+     *
+     * @param secondaryCollegeId 二级学院ID
+     * @return 学生统计信息列表
+     */
+    List<Map<String, Object>> getCollegeStudentStats(@Param("secondaryCollegeId") Long secondaryCollegeId);
+
+    /**
+     * 查询二级学院下的学院、专业、班级学生统计信息(按床位分配统计)
+     *
+     * @param secondaryCollegeId 二级学院ID
+     * @return 学生统计信息列表
+     */
+    List<Map<String, Object>> getCollegeStudentStatsByClass(@Param("secondaryCollegeId") Long secondaryCollegeId);
 
 }

+ 9 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormBedStudentService.java

@@ -3,6 +3,7 @@ package org.dromara.xg.service;
 import org.dromara.xg.domain.vo.XgDormBedStudentVo;
 import org.dromara.xg.domain.bo.XgDormBedStudentBo;
 import org.dromara.xg.domain.bo.XgDormBedBatchAllocateBo;
+import org.dromara.xg.domain.bo.XgDormBedBatchAllocateToClassBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
 
@@ -79,6 +80,14 @@ public interface IXgDormBedStudentService {
     Boolean batchAllocateBedsToCollege(
             XgDormBedBatchAllocateBo bo);
 
+    /**
+     * 批量分配床位给班级
+     *
+     * @param bo 批量分配给班级请求参数
+     * @return 是否分配成功
+     */
+    Boolean batchAllocateBedsToClass(XgDormBedBatchAllocateToClassBo bo);
+
     /**
      * 批量收回宿舍床位
      *

+ 305 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java

@@ -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());
+        }
+    }
+
     /**
      * 获取当前用户二级学院的宿舍楼树形结构
      * 校区-宿舍区-宿舍楼三级结构,校区和宿舍区不可选,只有宿舍楼可以选择

+ 150 - 2
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java

@@ -1321,6 +1321,154 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService {
         return result;
     }
 
+    /**
+     * 获取学生信息树(二级学院下的学院、专业、班级统计信息)- 按班级床位分配统计
+     *
+     * @param secondaryCollegeId 二级学院ID,默认101
+     * @return 学生信息树
+     */
+    public List<XgStudentTreeVo> getStudentTreeByClass(Long secondaryCollegeId) {
+        log.info("开始构建学生信息树(按床位分配统计),二级学院ID: {}", secondaryCollegeId);
+
+        List<XgStudentTreeVo> result = new ArrayList<>();
+
+        try {
+            // 1. 查询二级学院下的所有学院信息,并统计男女生人数(使用新的按班级床位分配统计方法)
+            List<Map<String, Object>> collegeStats = baseMapper.getCollegeStudentStatsByClass(secondaryCollegeId);
+            log.info("查询到 {} 个学院", collegeStats.size());
+
+            // 按学院分组
+            Map<String, List<Map<String, Object>>> collegeMap = collegeStats.stream()
+                    .collect(Collectors.groupingBy(item -> {
+                        Object collegeIdObj = item.get("college_id");
+                        Long collegeId = collegeIdObj instanceof Long ? (Long) collegeIdObj
+                                : Long.valueOf(collegeIdObj.toString());
+                        String collegeName = (String) item.get("college_name");
+                        return collegeId + "_" + collegeName;
+                    }));
+
+            // 2. 构建学院节点
+            for (Map.Entry<String, List<Map<String, Object>>> collegeEntry : collegeMap.entrySet()) {
+                String collegeKey = collegeEntry.getKey();
+                List<Map<String, Object>> collegeMajors = collegeEntry.getValue();
+
+                String[] keyParts = collegeKey.split("_");
+                Long collegeId = Long.valueOf(keyParts[0]);
+                String collegeName = keyParts[1];
+
+                // 计算学院级别的统计信息
+                int collegeMaleTotal = 0;
+                int collegeFemaleTotal = 0;
+                int collegeMaleAssigned = 0;
+                int collegeFemaleAssigned = 0;
+
+                for (Map<String, Object> item : collegeMajors) {
+                    collegeMaleTotal += ((Number) item.get("male_total")).intValue();
+                    collegeFemaleTotal += ((Number) item.get("female_total")).intValue();
+                    collegeMaleAssigned += ((Number) item.get("male_assigned")).intValue();
+                    collegeFemaleAssigned += ((Number) item.get("female_assigned")).intValue();
+                }
+
+                XgStudentTreeVo collegeNode = new XgStudentTreeVo();
+                collegeNode.setId(collegeId.toString());
+                collegeNode.setLabel(collegeName);
+                collegeNode.setType("college");
+                collegeNode.setCollegeId(collegeId);
+                collegeNode.setCollegeName(collegeName);
+                collegeNode.setMaleTotal(collegeMaleTotal);
+                collegeNode.setFemaleTotal(collegeFemaleTotal);
+                collegeNode.setMaleAssigned(collegeMaleAssigned);
+                collegeNode.setFemaleAssigned(collegeFemaleAssigned);
+                collegeNode.setChildren(new ArrayList<>());
+
+                // 按专业分组
+                Map<String, List<Map<String, Object>>> majorMap = collegeMajors.stream()
+                        .collect(Collectors.groupingBy(item -> {
+                            Object majorIdObj = item.get("major_id");
+                            Long majorId = majorIdObj instanceof Long ? (Long) majorIdObj
+                                    : Long.valueOf(majorIdObj.toString());
+                            String majorName = (String) item.get("major_name");
+                            return majorId + "_" + majorName;
+                        }));
+
+                // 3. 构建专业节点
+                for (Map.Entry<String, List<Map<String, Object>>> majorEntry : majorMap.entrySet()) {
+                    String majorKey = majorEntry.getKey();
+                    List<Map<String, Object>> majorClasses = majorEntry.getValue();
+
+                    String[] majorKeyParts = majorKey.split("_");
+                    Long majorId = Long.valueOf(majorKeyParts[0]);
+                    String majorName = majorKeyParts[1];
+
+                    // 计算专业级别的统计信息
+                    int majorMaleTotal = 0;
+                    int majorFemaleTotal = 0;
+                    int majorMaleAssigned = 0;
+                    int majorFemaleAssigned = 0;
+
+                    for (Map<String, Object> item : majorClasses) {
+                        majorMaleTotal += ((Number) item.get("male_total")).intValue();
+                        majorFemaleTotal += ((Number) item.get("female_total")).intValue();
+                        majorMaleAssigned += ((Number) item.get("male_assigned")).intValue();
+                        majorFemaleAssigned += ((Number) item.get("female_assigned")).intValue();
+                    }
+
+                    XgStudentTreeVo majorNode = new XgStudentTreeVo();
+                    majorNode.setId(majorId.toString());
+                    majorNode.setLabel(majorName);
+                    majorNode.setType("major");
+                    majorNode.setMajorId(majorId);
+                    majorNode.setMajorName(majorName);
+                    majorNode.setMaleTotal(majorMaleTotal);
+                    majorNode.setFemaleTotal(majorFemaleTotal);
+                    majorNode.setMaleAssigned(majorMaleAssigned);
+                    majorNode.setFemaleAssigned(majorFemaleAssigned);
+                    majorNode.setChildren(new ArrayList<>());
+
+                    // 4. 构建班级节点
+                    for (Map<String, Object> classData : majorClasses) {
+                        Object classIdObj = classData.get("class_id");
+                        Long classId = classIdObj instanceof Long ? (Long) classIdObj
+                                : Long.valueOf(classIdObj.toString());
+                        String className = (String) classData.get("class_name");
+                        String grade = (String) classData.get("grade");
+
+                        int classMaleTotal = ((Number) classData.get("male_total")).intValue();
+                        int classFemaleTotal = ((Number) classData.get("female_total")).intValue();
+                        int classMaleAssigned = ((Number) classData.get("male_assigned")).intValue();
+                        int classFemaleAssigned = ((Number) classData.get("female_assigned")).intValue();
+
+                        XgStudentTreeVo classNode = new XgStudentTreeVo();
+                        classNode.setId(classId.toString());
+                        classNode.setLabel(className);
+                        classNode.setType("class");
+                        classNode.setGrade(grade);
+                        classNode.setClassId(classId);
+                        classNode.setClassName(className);
+                        classNode.setMaleTotal(classMaleTotal);
+                        classNode.setFemaleTotal(classFemaleTotal);
+                        classNode.setMaleAssigned(classMaleAssigned);
+                        classNode.setFemaleAssigned(classFemaleAssigned);
+                        classNode.setChildren(new ArrayList<>());
+
+                        majorNode.getChildren().add(classNode);
+                    }
+
+                    collegeNode.getChildren().add(majorNode);
+                }
+
+                result.add(collegeNode);
+            }
+
+            log.info("成功构建学生信息树,共 {} 个学院", result.size());
+
+        } catch (Exception e) {
+            log.error("构建学生信息树失败", e);
+        }
+
+        return result;
+    }
+
     /**
      * 获取当前用户所在学院的学生信息树
      *
@@ -1338,8 +1486,8 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService {
 
             log.info("获取到当前用户的学院ID: {}", collegeId);
 
-            // 2. 调用现有的getStudentTree方法
-            return getStudentTree(collegeId);
+            // 2. 调用按床位分配统计的方法
+            return getStudentTreeByClass(collegeId);
 
         } catch (Exception e) {
             log.error("获取当前用户学院的学生信息树失败", e);

+ 50 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml

@@ -200,4 +200,54 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ORDER BY c.grade DESC, sm.college_name, sm.major_name, sm.class_name
     </select>
 
+    <!-- 查询二级学院下的学院、专业、班级学生统计信息(按床位分配统计) -->
+    <select id="getCollegeStudentStatsByClass" resultType="java.util.Map">
+        SELECT
+            c.grade,
+            sm.college_id,
+            sm.college_name,
+            sm.major_id,
+            sm.major_name,
+            sm.class_id,
+            sm.class_name,
+            COALESCE(SUM(CASE WHEN sm.gender = '0' AND sm.is_boarder = 1 THEN 1 ELSE 0 END), 0) AS male_total,
+            COALESCE(SUM(CASE WHEN sm.gender = '1' AND sm.is_boarder = 1 THEN 1 ELSE 0 END), 0) AS female_total,
+            COALESCE((
+                SELECT COUNT(DISTINCT dbs.bed_id)
+                FROM xg_dorm_bed_student dbs
+                LEFT JOIN xg_dorm_bed db ON dbs.bed_id = db.id
+                LEFT JOIN xg_dorm_room dr ON db.room_id = dr.id
+                LEFT JOIN xg_dorm_building dbuild ON dr.building_id = dbuild.id
+                WHERE dbs.class_id = sm.class_id
+                  AND dbs.deleted = '0'
+                  AND dbs.status = '0'
+                  AND db.deleted = '0'
+                  AND dbuild.building_type = '1'
+            ), 0) AS male_assigned,
+            COALESCE((
+                SELECT COUNT(DISTINCT dbs.bed_id)
+                FROM xg_dorm_bed_student dbs
+                LEFT JOIN xg_dorm_bed db ON dbs.bed_id = db.id
+                LEFT JOIN xg_dorm_room dr ON db.room_id = dr.id
+                LEFT JOIN xg_dorm_building dbuild ON dr.building_id = dbuild.id
+                WHERE dbs.class_id = sm.class_id
+                  AND dbs.deleted = '0'
+                  AND dbs.status = '0'
+                  AND db.deleted = '0'
+                  AND dbuild.building_type = '2'
+            ), 0) AS female_assigned
+        FROM xg_student_msg sm
+        LEFT JOIN xg_class_msg c ON sm.class_id = c.id
+        LEFT JOIN sys_dept d ON sm.college_id = d.dept_id
+        WHERE sm.deleted = 0
+          AND sm.student_status = '1'
+          AND sm.is_boarder = 1
+          AND (d.parent_id = #{secondaryCollegeId} OR d.dept_id = #{secondaryCollegeId} OR FIND_IN_SET(#{secondaryCollegeId}, d.ancestors))
+          AND sm.college_id IS NOT NULL
+          AND sm.major_id IS NOT NULL
+          AND sm.class_id IS NOT NULL
+        GROUP BY c.grade, sm.college_id, sm.college_name, sm.major_id, sm.major_name, sm.class_id, sm.class_name
+        ORDER BY c.grade DESC, sm.college_name, sm.major_name, sm.class_name
+    </select>
+
 </mapper>

+ 18 - 0
web/src/api/xg/xgss/dormBed/index.ts

@@ -111,3 +111,21 @@ export const batchAllocateBedsToCollege = (data: {
     data: data
   });
 };
+
+/**
+ * 批量分配床位给班级
+ * @param data 批量分配给班级参数
+ */
+export const batchAllocateBedsToClass = (data: {
+  bedIds: (string | number)[];
+  classIds: (string | number)[];
+  classStudentCounts?: number[];
+  buildingId?: string | number;
+  buildingType?: string;
+}) => {
+  return request({
+    url: '/dormBedStudent/dormBedStudent/batchAllocateToClass',
+    method: 'post',
+    data: data
+  });
+};

File diff suppressed because it is too large
+ 350 - 212
web/src/views/xg/xgss/ejxyfp/components/ClassAllocationDialog.vue


+ 12 - 12
web/src/views/xg/xgss/ejxyfp/ejxyfp.vue

@@ -132,7 +132,7 @@
             <template #header>
               <el-row>
                 <el-col :span="12">
-                  <el-button type="primary" icon="check" size="small" @click="handleAddRoom">分配给二级学院</el-button>
+                  <el-button type="primary" icon="check" size="small" @click="handleAddRoom">分配给班级</el-button>
                   <el-button type="warning" icon="RefreshLeft" size="small" @click="handleBatchRecall">批量收回宿舍</el-button>
                 </el-col>
                 <el-col :span="12" style="text-align: right">
@@ -340,11 +340,11 @@
     />
 
     <!-- 学院分配对话框 -->
-    <college-allocation-dialog
-      v-model="collegeAllocationDialog.visible"
-      :title="collegeAllocationDialog.title"
-      :selected-beds="collegeAllocationDialog.selectedBeds"
-      :building-info="collegeAllocationDialog.buildingInfo"
+    <class-allocation-dialog
+      v-model="classAllocationDialog.visible"
+      :title="classAllocationDialog.title"
+      :selected-beds="classAllocationDialog.selectedBeds"
+      :building-info="classAllocationDialog.buildingInfo"
       @success="handleCollegeAllocationSuccess"
     />
   </div>
@@ -368,7 +368,7 @@ import ElMessageBox from 'element-plus/es/components/message-box/index';
 // 引入子组件
 import RoomDialog from './components/RoomDialog.vue';
 import BedDialog from './components/BedDialog.vue';
-import CollegeAllocationDialog from './components/CollegeAllocationDialog.vue';
+import ClassAllocationDialog from './components/ClassAllocationDialog.vue';
 import { batchRecallDormBeds } from '@/api/xg/xgss/dormBedStudent/dormBedStudent';
 
 // const { proxy } = getCurrentInstance();
@@ -449,9 +449,9 @@ const bedDialog = reactive({
 });
 
 // 学院分配对话框
-const collegeAllocationDialog = reactive({
+const classAllocationDialog = reactive({
   visible: false,
-  title: '分配床位给二级学院',
+  title: '分配床位给班级',
   selectedBeds: [] as any[],
   buildingInfo: undefined as DormTreeNode | undefined
 });
@@ -668,9 +668,9 @@ const handleAddRoom = () => {
   }
 
   // 准备分配对话框数据
-  collegeAllocationDialog.selectedBeds = selectedBeds;
-  collegeAllocationDialog.buildingInfo = selectedBuilding.value;
-  collegeAllocationDialog.visible = true;
+  classAllocationDialog.selectedBeds = selectedBeds;
+  classAllocationDialog.buildingInfo = selectedBuilding.value;
+  classAllocationDialog.visible = true;
 };
 
 /** 批量收回宿舍 */

Some files were not shown because too many files changed in this diff