zlwu 1 год назад
Родитель
Сommit
718ab4cb38

+ 27 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormHygieneCheckController.java

@@ -23,6 +23,7 @@ import org.dromara.xg.domain.bo.XgDormHygieneCheckBo;
 import org.dromara.xg.domain.bo.XgDormHygieneCheckCompleteBo;
 import org.dromara.xg.service.IXgDormHygieneCheckService;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo;
 
 /**
  * 宿舍卫生检查
@@ -175,4 +176,30 @@ public class XgDormHygieneCheckController extends BaseController {
         return toAjax(xgDormHygieneCheckService.saveCompleteData(bo));
     }
 
+    /**
+     * 更新完整的宿舍卫生检查数据(包含学生评分和违纪记录)
+     */
+    @SaCheckPermission("dormHygieneCheck:dormHygieneCheck:edit")
+    @Log(title = "宿舍卫生检查", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping("/updateCompleteData")
+    public R<Void> updateCompleteData(@Validated(EditGroup.class) @RequestBody XgDormHygieneCheckCompleteBo bo) {
+        return toAjax(xgDormHygieneCheckService.updateCompleteData(bo));
+    }
+
+    /**
+     * 根据卫生检查ID查询相关的违纪记录
+     *
+     * @param hygieneCheckId 卫生检查ID
+     * @return 违纪记录列表
+     */
+    @SaCheckPermission("dormHygieneCheck:dormHygieneCheck:query")
+    @GetMapping("/getViolations/{hygieneCheckId}")
+    public R<List<XgHygieneCheckViolationVo>> getViolations(
+            @NotNull(message = "卫生检查ID不能为空") @PathVariable Long hygieneCheckId) {
+        List<XgHygieneCheckViolationVo> violations = xgDormHygieneCheckService
+                .getViolationsByHygieneCheckId(hygieneCheckId);
+        return R.ok(violations);
+    }
+
 }

+ 6 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgHygieneCheckViolation.java

@@ -74,4 +74,10 @@ public class XgHygieneCheckViolation extends TenantEntity {
      */
     private String status;
 
+    /**
+     * 删除标志(0代表存在 1代表删除)
+     */
+    @TableLogic(value = "0", delval = "1")
+    private Long deleted;
+
 }

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

@@ -17,6 +17,16 @@ import java.math.BigDecimal;
 @Data
 public class XgViolationDataBo {
 
+    /**
+     * 违纪记录ID(用于更新操作,新增时为null)
+     */
+    private Long id;
+
+    /**
+     * 违纪记录ID,关联xskh_discipline_record.id(用于更新违纪记录)
+     */
+    private Long disciplineRecordId;
+
     /**
      * 学生ID,关联xg_student_msg.id
      */

+ 29 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgHygieneCheckViolationVo.java

@@ -5,6 +5,7 @@ import io.github.linpeilie.annotations.AutoMapper;
 import lombok.Data;
 import java.math.BigDecimal;
 import java.util.Date;
+import java.time.LocalDate;
 import com.fasterxml.jackson.annotation.JsonFormat;
 
 import java.io.Serial;
@@ -72,6 +73,34 @@ public class XgHygieneCheckViolationVo {
      */
     private String status;
 
+    // ============ 新增违纪详情字段 ============
+
+    /**
+     * 违纪处分日期
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate disciplineDate;
+
+    /**
+     * 违纪处分原因
+     */
+    private String disciplineReason;
+
+    /**
+     * 违纪处分内容
+     */
+    private String disciplineContent;
+
+    /**
+     * 扣分分值
+     */
+    private BigDecimal scoreDeduction;
+
+    /**
+     * 附件OSS ID列表,逗号分隔
+     */
+    private String attachmentOssIds;
+
     /**
      * 创建者
      */

+ 11 - 1
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgHygieneCheckViolationMapper.java

@@ -3,6 +3,8 @@ package org.dromara.xg.mapper;
 import org.dromara.xg.domain.XgHygieneCheckViolation;
 import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
 
 /**
  * 宿舍卫生检查违纪记录Mapper接口
@@ -11,6 +13,14 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
  * @date 2025-01-29
  */
 public interface XgHygieneCheckViolationMapper
-        extends BaseMapperPlus<XgHygieneCheckViolation, XgHygieneCheckViolationVo> {
+                extends BaseMapperPlus<XgHygieneCheckViolation, XgHygieneCheckViolationVo> {
+
+        /**
+         * 根据卫生检查ID查询违纪记录(包含详情)
+         *
+         * @param hygieneCheckId 卫生检查ID
+         * @return 违纪记录列表(包含详情)
+         */
+        List<XgHygieneCheckViolationVo> selectViolationsByHygieneCheckId(@Param("hygieneCheckId") Long hygieneCheckId);
 
 }

+ 17 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormHygieneCheckService.java

@@ -2,6 +2,7 @@ package org.dromara.xg.service;
 
 import org.dromara.xg.domain.vo.DormHygieneCheckExportVo;
 import org.dromara.xg.domain.vo.XgDormHygieneCheckVo;
+import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo;
 import org.dromara.xg.domain.bo.XgDormHygieneCheckBo;
 import org.dromara.xg.domain.bo.XgDormHygieneCheckCompleteBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -119,4 +120,20 @@ public interface IXgDormHygieneCheckService {
      */
     Boolean saveCompleteData(XgDormHygieneCheckCompleteBo bo);
 
+    /**
+     * 更新完整的宿舍卫生检查数据(包含学生评分和违纪记录)
+     *
+     * @param bo 完整的宿舍卫生检查数据
+     * @return 是否更新成功
+     */
+    Boolean updateCompleteData(XgDormHygieneCheckCompleteBo bo);
+
+    /**
+     * 根据卫生检查ID查询相关的违纪记录
+     *
+     * @param hygieneCheckId 卫生检查ID
+     * @return 违纪记录列表
+     */
+    List<XgHygieneCheckViolationVo> getViolationsByHygieneCheckId(Long hygieneCheckId);
+
 }

+ 351 - 1
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java

@@ -25,6 +25,7 @@ import org.dromara.xg.mapper.XgDormHygieneStudentScoreMapper;
 import org.dromara.xg.service.IXgDormHygieneCheckService;
 import org.dromara.xg.service.IXgStudentMsgService;
 import org.dromara.xg.service.IXgHygieneCheckViolationService;
+import org.dromara.xg.mapper.XgHygieneCheckViolationMapper;
 import org.dromara.system.service.ISysDictDataService;
 import org.springframework.context.ApplicationContext;
 
@@ -32,6 +33,13 @@ import java.util.List;
 import java.util.Map;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo;
+import org.dromara.xg.domain.XgHygieneCheckViolation;
+import java.util.Collections;
 
 /**
  * 宿舍卫生检查Service业务层处理
@@ -49,6 +57,7 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
     private final IXgStudentMsgService xgStudentMsgService;
     private final ISysDictDataService sysDictDataService;
     private final IXgHygieneCheckViolationService violationService;
+    private final XgHygieneCheckViolationMapper violationMapper;
     private final ApplicationContext applicationContext;
 
     /**
@@ -708,7 +717,8 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
                         }
                     } catch (Exception e) {
                         log.error("处理违纪数据失败,学生ID: {}, 错误: {}", violationBo.getStudentId(), e.getMessage(), e);
-                        // 不影响主流程,继续处理下一个
+                        throw new RuntimeException(
+                                "处理违纪数据失败,学生ID: " + violationBo.getStudentId() + ", 错误: " + e.getMessage(), e);
                     }
                 }
             }
@@ -947,4 +957,344 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
         }
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateCompleteData(XgDormHygieneCheckCompleteBo bo) {
+        try {
+            // 1. 更新主表数据
+            XgDormHygieneCheck update = MapstructUtils.convert(bo, XgDormHygieneCheck.class);
+            validEntityBeforeSave(update);
+            boolean flag = baseMapper.updateById(update) > 0;
+            log.info("更新宿舍卫生检查主表数据: {}", update);
+
+            if (!flag) {
+                log.error("更新宿舍卫生检查主表数据失败");
+                return false;
+            }
+
+            Long hygieneCheckId = update.getId();
+
+            // 2. 更新学生评分数据 - 先删除旧数据,再插入新数据
+            LambdaQueryWrapper<XgDormHygieneStudentScore> scoreWrapper = Wrappers.lambdaQuery();
+            scoreWrapper.eq(XgDormHygieneStudentScore::getHygieneCheckId, hygieneCheckId);
+            studentScoreMapper.delete(scoreWrapper);
+            log.info("删除旧的学生评分数据,检查ID: {}", hygieneCheckId);
+
+            if (bo.getStudentScores() != null && !bo.getStudentScores().isEmpty()) {
+                for (XgDormHygieneStudentScoreBo studentScoreBo : bo.getStudentScores()) {
+                    XgDormHygieneStudentScore studentScore = MapstructUtils.convert(studentScoreBo,
+                            XgDormHygieneStudentScore.class);
+                    studentScore.setHygieneCheckId(hygieneCheckId);
+                    studentScore.setId(null); // 确保是新插入
+
+                    // 根据学生ID查询学生完整信息,补充学院、专业、班级信息
+                    if (studentScoreBo.getStudentId() != null) {
+                        try {
+                            var studentInfo = xgStudentMsgService.queryById(studentScoreBo.getStudentId());
+                            if (studentInfo != null) {
+                                // 补充学院信息
+                                if (studentScore.getCollegeId() == null
+                                        && StringUtils.isNotBlank(studentInfo.getCollegeId())) {
+                                    try {
+                                        studentScore.setCollegeId(Long.valueOf(studentInfo.getCollegeId()));
+                                    } catch (NumberFormatException e) {
+                                        log.warn("学院ID转换失败,学生ID: {}, 学院ID: {}", studentScoreBo.getStudentId(),
+                                                studentInfo.getCollegeId());
+                                    }
+                                }
+                                if (StringUtils.isBlank(studentScore.getCollegeName())) {
+                                    studentScore.setCollegeName(studentInfo.getCollegeName());
+                                }
+
+                                // 补充专业信息
+                                if (studentScore.getMajorId() == null
+                                        && StringUtils.isNotBlank(studentInfo.getMajorId())) {
+                                    try {
+                                        studentScore.setMajorId(Long.valueOf(studentInfo.getMajorId()));
+                                    } catch (NumberFormatException e) {
+                                        log.warn("专业ID转换失败,学生ID: {}, 专业ID: {}", studentScoreBo.getStudentId(),
+                                                studentInfo.getMajorId());
+                                    }
+                                }
+                                if (StringUtils.isBlank(studentScore.getMajorName())) {
+                                    studentScore.setMajorName(studentInfo.getMajorName());
+                                }
+
+                                // 补充班级信息
+                                if (studentScore.getClassId() == null
+                                        && StringUtils.isNotBlank(studentInfo.getClassId())) {
+                                    try {
+                                        studentScore.setClassId(Long.valueOf(studentInfo.getClassId()));
+                                    } catch (NumberFormatException e) {
+                                        log.warn("班级ID转换失败,学生ID: {}, 班级ID: {}", studentScoreBo.getStudentId(),
+                                                studentInfo.getClassId());
+                                    }
+                                }
+                                if (StringUtils.isBlank(studentScore.getClassName())) {
+                                    studentScore.setClassName(studentInfo.getClassName());
+                                }
+
+                                log.info("补充学生信息 - 学生ID: {}, 学院: {}, 专业: {}, 班级: {}",
+                                        studentScoreBo.getStudentId(),
+                                        studentInfo.getCollegeName(),
+                                        studentInfo.getMajorName(),
+                                        studentInfo.getClassName());
+                            }
+                        } catch (Exception e) {
+                            log.warn("查询学生信息失败,学生ID: {}, 错误: {}", studentScoreBo.getStudentId(), e.getMessage());
+                        }
+                    }
+
+                    log.info("保存学生评分数据: {}", studentScore);
+                    studentScoreMapper.insert(studentScore);
+                }
+            }
+
+            // 3. 处理违纪记录数据 - 完整的增删改逻辑
+            handleViolationRecords(hygieneCheckId, bo.getViolationData());
+
+            log.info("完整更新宿舍卫生检查数据成功,检查ID: {}", hygieneCheckId);
+            return true;
+
+        } catch (Exception e) {
+            log.error("更新宿舍卫生检查数据失败", e);
+            throw new RuntimeException("更新宿舍卫生检查数据失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 处理违纪记录数据 - 完整的增删改逻辑
+     *
+     * @param hygieneCheckId   卫生检查ID
+     * @param newViolationData 新的违纪记录数据列表
+     */
+    private void handleViolationRecords(Long hygieneCheckId, List<XgViolationDataBo> newViolationData) {
+        try {
+            // 1. 查询数据库中现有的违纪记录
+            List<XgHygieneCheckViolationVo> existingViolations = getViolationsByHygieneCheckId(hygieneCheckId);
+            log.info("数据库中现有违纪记录数量: {}", existingViolations.size());
+
+            // 2. 收集现有记录的违纪记录ID(disciplineRecordId)
+            Set<Long> existingDisciplineIds = existingViolations.stream()
+                    .map(XgHygieneCheckViolationVo::getDisciplineRecordId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+            log.info("现有违纪记录ID集合: {}", existingDisciplineIds);
+
+            // 3. 收集新数据中的违纪记录ID(优先使用disciplineRecordId字段,兼容旧版本的id字段)
+            Set<Long> newDisciplineIds = new HashSet<>();
+            if (newViolationData != null) {
+                newDisciplineIds = newViolationData.stream()
+                        .map(violationBo -> {
+                            // 优先使用disciplineRecordId字段,如果为空则使用id字段(向后兼容)
+                            return violationBo.getDisciplineRecordId() != null
+                                    ? violationBo.getDisciplineRecordId()
+                                    : violationBo.getId();
+                        })
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toSet());
+            }
+            log.info("新数据中的违纪记录ID集合: {}", newDisciplineIds);
+
+            // 4. 找出需要删除的记录(在数据库中存在,但不在新数据中)
+            Set<Long> toDeleteIds = new HashSet<>(existingDisciplineIds);
+            toDeleteIds.removeAll(newDisciplineIds);
+            log.info("需要删除的违纪记录ID集合: {}", toDeleteIds);
+
+            // 5. 删除不再存在的记录
+            for (Long deleteId : toDeleteIds) {
+                deleteViolationRecord(hygieneCheckId, deleteId);
+            }
+
+            // 6. 处理新数据中的记录(新增或更新)
+            if (newViolationData != null && !newViolationData.isEmpty()) {
+                for (XgViolationDataBo violationBo : newViolationData) {
+                    try {
+                        log.info("处理违纪记录数据: {}", violationBo);
+
+                        // 如果是已存在的违纪记录(有违纪记录ID且在现有记录中),更新统一表中的记录
+                        Long currentDisciplineRecordId = violationBo.getDisciplineRecordId() != null
+                                ? violationBo.getDisciplineRecordId()
+                                : violationBo.getId();
+                        if (currentDisciplineRecordId != null
+                                && existingDisciplineIds.contains(currentDisciplineRecordId)) {
+                            log.info("更新现有违纪记录,违纪记录ID: {}", currentDisciplineRecordId);
+                            updateDisciplineRecord(violationBo);
+                        } else {
+                            // 新增违纪记录
+                            log.info("新增违纪记录,学生ID: {}", violationBo.getStudentId());
+                            Long disciplineRecordId = createDisciplineRecord(violationBo);
+
+                            if (disciplineRecordId != null && violationService != null) {
+                                XgHygieneCheckViolationBo relationBo = new XgHygieneCheckViolationBo();
+                                relationBo.setHygieneCheckId(hygieneCheckId);
+                                relationBo.setDisciplineRecordId(disciplineRecordId);
+                                relationBo.setStudentId(violationBo.getStudentId());
+                                relationBo.setStudentNo(violationBo.getStudentNo());
+                                relationBo.setStudentName(violationBo.getStudentName());
+                                relationBo.setDisciplineTypeId(violationBo.getDisciplineTypeId());
+                                relationBo.setDisciplineTypeName(violationBo.getDisciplineTypeName());
+                                relationBo.setViolationSource("1"); // 1-宿舍卫生检查
+                                relationBo.setStatus("1"); // 1-生效
+
+                                boolean relationResult = violationService.insertByBo(relationBo);
+                                if (!relationResult) {
+                                    log.warn("创建卫生检查违纪关联记录失败,学生ID: {}", violationBo.getStudentId());
+                                } else {
+                                    log.info("创建卫生检查违纪关联记录成功,学生ID: {},违纪记录ID: {}",
+                                            violationBo.getStudentId(), disciplineRecordId);
+                                }
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.error("处理违纪数据失败,学生ID: {}, 错误: {}", violationBo.getStudentId(), e.getMessage(), e);
+                        throw new RuntimeException(
+                                "处理违纪数据失败,学生ID: " + violationBo.getStudentId() + ", 错误: " + e.getMessage(), e);
+                    }
+                }
+            }
+
+            log.info("违纪记录处理完成,卫生检查ID: {}", hygieneCheckId);
+
+        } catch (Exception e) {
+            log.error("处理违纪记录数据失败,卫生检查ID: {}, 错误: {}", hygieneCheckId, e.getMessage(), e);
+            throw new RuntimeException("处理违纪记录数据失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 删除违纪记录(同时删除关联表和统一表中的记录)
+     *
+     * @param hygieneCheckId     卫生检查ID
+     * @param disciplineRecordId 违纪记录ID
+     */
+    private void deleteViolationRecord(Long hygieneCheckId, Long disciplineRecordId) {
+        try {
+            log.info("开始逻辑删除违纪记录,卫生检查ID: {}, 违纪记录ID: {}", hygieneCheckId, disciplineRecordId);
+
+            // 1. 逻辑删除关联表中的记录 (xg_hygiene_check_violation)
+            if (violationMapper != null) {
+                LambdaQueryWrapper<XgHygieneCheckViolation> wrapper = Wrappers.lambdaQuery();
+                wrapper.eq(XgHygieneCheckViolation::getHygieneCheckId, hygieneCheckId)
+                        .eq(XgHygieneCheckViolation::getDisciplineRecordId, disciplineRecordId);
+
+                // 使用逻辑删除
+                int deleteCount = violationMapper.delete(wrapper);
+                if (deleteCount > 0) {
+                    log.info("逻辑删除卫生检查违纪关联记录成功,违纪记录ID: {}, 删除数量: {}", disciplineRecordId, deleteCount);
+                } else {
+                    log.warn("逻辑删除卫生检查违纪关联记录失败,违纪记录ID: {}", disciplineRecordId);
+                }
+            }
+
+            // 2. 逻辑删除统一表中的记录 (xskh_discipline_record)
+            deleteDisciplineRecord(disciplineRecordId);
+
+        } catch (Exception e) {
+            log.error("逻辑删除违纪记录失败,违纪记录ID: {}, 错误: {}", disciplineRecordId, e.getMessage(), e);
+            throw new RuntimeException("逻辑删除违纪记录失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 逻辑删除统一表中的违纪记录
+     *
+     * @param disciplineRecordId 违纪记录ID
+     */
+    private void deleteDisciplineRecord(Long disciplineRecordId) {
+        try {
+            // 通过接口类型动态获取IXskhDisciplineRecordService,避免硬依赖
+            Class<?> serviceInterface = Class.forName("org.dromara.xskh.service.IXskhDisciplineRecordService");
+            Object disciplineRecordService = applicationContext.getBean(serviceInterface);
+
+            if (disciplineRecordService != null) {
+                // 使用deleteWithValidByIds方法进行逻辑删除
+                var deleteMethod = disciplineRecordService.getClass().getMethod("deleteWithValidByIds",
+                        Collection.class, Boolean.class);
+                Boolean result = (Boolean) deleteMethod.invoke(disciplineRecordService,
+                        Collections.singletonList(disciplineRecordId), false);
+
+                if (result) {
+                    log.info("逻辑删除统一违纪记录成功,违纪记录ID: {}", disciplineRecordId);
+                } else {
+                    log.error("逻辑删除统一违纪记录失败,违纪记录ID: {}", disciplineRecordId);
+                }
+            } else {
+                log.error("未找到违纪记录服务,无法删除统一违纪记录,违纪记录ID: {}", disciplineRecordId);
+            }
+
+        } catch (ClassNotFoundException e) {
+            log.warn("学生考核管理模块(xskh)未启用,跳过统一违纪记录删除");
+        } catch (Exception e) {
+            log.error("逻辑删除统一违纪记录失败,违纪记录ID: {}, 错误: {}", disciplineRecordId, e.getMessage(), e);
+            throw new RuntimeException("逻辑删除统一违纪记录失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 更新学生违纪记录
+     *
+     * @param violationBo 违纪数据
+     */
+    private void updateDisciplineRecord(XgViolationDataBo violationBo) {
+        try {
+            // 通过接口类型动态获取IXskhDisciplineRecordService,避免硬依赖
+            Class<?> serviceInterface = Class.forName("org.dromara.xskh.service.IXskhDisciplineRecordService");
+            Object disciplineRecordService = applicationContext.getBean(serviceInterface);
+
+            if (disciplineRecordService != null) {
+                // 创建XskhDisciplineRecordBo对象
+                Object disciplineRecordBo = createDisciplineRecordBo(violationBo);
+                if (disciplineRecordBo != null) {
+                    // 设置ID用于更新
+                    setFieldValue(disciplineRecordBo, "id", violationBo.getId());
+
+                    // 反射调用updateByBo方法
+                    Class<?> disciplineRecordBoClass = Class
+                            .forName("org.dromara.xskh.domain.bo.XskhDisciplineRecordBo");
+                    var updateMethod = disciplineRecordService.getClass().getMethod("updateByBo",
+                            disciplineRecordBoClass);
+                    Boolean result = (Boolean) updateMethod.invoke(disciplineRecordService, disciplineRecordBo);
+
+                    if (result) {
+                        log.info("更新学生违纪记录成功,学生ID: {}, 违纪记录ID: {}",
+                                violationBo.getStudentId(), violationBo.getId());
+                    } else {
+                        log.warn("更新学生违纪记录失败,学生ID: {}", violationBo.getStudentId());
+                    }
+                }
+            }
+        } catch (ClassNotFoundException e) {
+            log.warn("学生考核管理模块(xskh)未启用,跳过违纪记录更新");
+        } catch (Exception e) {
+            log.error("更新学生违纪记录失败: {}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 根据卫生检查ID查询相关的违纪记录(包含详情)
+     *
+     * @param hygieneCheckId 卫生检查ID
+     * @return 违纪记录列表(包含详情)
+     */
+    @Override
+    public List<XgHygieneCheckViolationVo> getViolationsByHygieneCheckId(Long hygieneCheckId) {
+        if (hygieneCheckId == null) {
+            log.warn("卫生检查ID为空,无法查询违纪记录");
+            return List.of();
+        }
+
+        try {
+            // 使用自定义查询方法,关联查询违纪详情
+            List<XgHygieneCheckViolationVo> violations = violationMapper
+                    .selectViolationsByHygieneCheckId(hygieneCheckId);
+
+            log.info("卫生检查ID: {} 查询到 {} 条违纪记录(包含详情)", hygieneCheckId, violations.size());
+            return violations;
+        } catch (Exception e) {
+            log.error("查询卫生检查违纪记录失败,检查ID: {}, 错误: {}", hygieneCheckId, e.getMessage(), e);
+            return List.of();
+        }
+    }
+
 }

+ 78 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormHygieneCheck/XgHygieneCheckViolationMapper.xml

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.xg.mapper.XgHygieneCheckViolationMapper">
+
+    <resultMap type="org.dromara.xg.domain.vo.XgHygieneCheckViolationVo" id="XgHygieneCheckViolationResult">
+        <result property="id" column="id" />
+        <result property="hygieneCheckId" column="hygiene_check_id" />
+        <result property="disciplineRecordId" column="discipline_record_id" />
+        <result property="studentId" column="student_id" />
+        <result property="studentNo" column="student_no" />
+        <result property="studentName" column="student_name" />
+        <result property="disciplineTypeId" column="discipline_type_id" />
+        <result property="disciplineTypeName" column="discipline_type_name" />
+        <result property="violationSource" column="violation_source" />
+        <result property="status" column="status" />
+        <!-- 违纪详情字段 -->
+        <result property="disciplineDate" column="discipline_date" />
+        <result property="disciplineReason" column="discipline_reason" />
+        <result property="disciplineContent" column="discipline_content" />
+        <result property="scoreDeduction" column="score_deduction" />
+        <result property="attachmentOssIds" column="attachment_oss_ids" />
+        <result property="createBy" column="create_by" />
+        <result property="createTime" column="create_time" />
+        <result property="updateBy" column="update_by" />
+        <result property="updateTime" column="update_time" />
+    </resultMap>
+
+    <sql id="selectXgHygieneCheckViolationVo">
+        SELECT
+            hcv.id,
+            hcv.hygiene_check_id,
+            hcv.discipline_record_id,
+            hcv.student_id,
+            hcv.student_no,
+            hcv.student_name,
+            hcv.discipline_type_id,
+            hcv.discipline_type_name,
+            hcv.violation_source,
+            hcv.status,
+            hcv.create_by,
+            hcv.create_time,
+            hcv.update_by,
+            hcv.update_time,
+            -- 关联违纪详情
+            dr.discipline_date,
+            dr.discipline_reason,
+            dr.discipline_content,
+            dr.score_deduction,
+            dr.attachment_oss_ids
+        FROM xg_hygiene_check_violation hcv
+        LEFT JOIN xskh_discipline_record dr ON hcv.discipline_record_id = dr.id AND dr.deleted = 0
+    </sql>
+
+    <select id="selectVoList" parameterType="com.baomidou.mybatisplus.core.conditions.Wrapper" resultMap="XgHygieneCheckViolationResult">
+        <include refid="selectXgHygieneCheckViolationVo"/>
+        <where>
+            hcv.deleted = 0
+            ${ew.customSqlSegment}
+        </where>
+        ORDER BY hcv.create_time DESC
+    </select>
+
+    <select id="selectVoById" parameterType="Long" resultMap="XgHygieneCheckViolationResult">
+        <include refid="selectXgHygieneCheckViolationVo"/>
+        WHERE hcv.id = #{id} AND hcv.deleted = 0
+    </select>
+
+    <!-- 根据卫生检查ID查询违纪记录(包含详情) -->
+    <select id="selectViolationsByHygieneCheckId" parameterType="Long" resultMap="XgHygieneCheckViolationResult">
+        <include refid="selectXgHygieneCheckViolationVo"/>
+        WHERE hcv.hygiene_check_id = #{hygieneCheckId}
+            AND hcv.deleted = 0
+        ORDER BY hcv.create_time DESC
+    </select>
+
+</mapper>

+ 114 - 1
docs/开发文档/API/宿舍卫生检查管理API.md

@@ -412,4 +412,117 @@ lqw.orderByAsc(XgDormRoom::getSort); // 第二优先级:按排序号升序
 
 #### 涉及文件
 - `web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckDialog.vue`
-- `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java` 
+- `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java`
+
+---
+
+## 违纪记录更新修复 (2025-01-03)
+
+### 问题描述
+
+在调用 `updateCompleteData` 接口更新宿舍卫生检查数据时,即使违纪记录入参中包含有效的 `disciplineRecordId`,系统仍然执行了"逻辑删除旧记录 + 新增新记录"的操作,而不是直接更新现有记录。
+
+### 根本原因
+
+**数据字段映射错误**:
+- `XgViolationDataBo` 中的 `id` 字段被误认为是违纪记录ID (`disciplineRecordId`)
+- 实际上 `id` 字段是关联表 `xg_hygiene_check_violation` 的主键ID
+- 真正的违纪记录ID应该通过 `disciplineRecordId` 字段传递
+
+### 修复方案
+
+#### 1. 扩展 XgViolationDataBo 字段
+
+**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgViolationDataBo.java`
+
+```java
+/**
+ * 违纪记录ID(用于更新操作,新增时为null)
+ */
+private Long id;
+
+/**
+ * 违纪记录ID,关联xskh_discipline_record.id(用于更新违纪记录)
+ */
+private Long disciplineRecordId;
+```
+
+#### 2. 修复 handleViolationRecords 方法逻辑
+
+**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java`
+
+**修复前**:
+```java
+newDisciplineIds = newViolationData.stream()
+        .map(XgViolationDataBo::getId) // 错误:使用关联表ID
+        .filter(Objects::nonNull)
+        .collect(Collectors.toSet());
+```
+
+**修复后**:
+```java
+newDisciplineIds = newViolationData.stream()
+        .map(violationBo -> {
+            // 优先使用disciplineRecordId字段,如果为空则使用id字段(向后兼容)
+            return violationBo.getDisciplineRecordId() != null 
+                    ? violationBo.getDisciplineRecordId() 
+                    : violationBo.getId();
+        })
+        .filter(Objects::nonNull)
+        .collect(Collectors.toSet());
+```
+
+#### 3. 修复判断逻辑
+
+**修复前**:
+```java
+if (violationBo.getId() != null && existingDisciplineIds.contains(violationBo.getId())) {
+    // 更新逻辑
+}
+```
+
+**修复后**:
+```java
+Long currentDisciplineRecordId = violationBo.getDisciplineRecordId() != null 
+        ? violationBo.getDisciplineRecordId() 
+        : violationBo.getId();
+if (currentDisciplineRecordId != null && existingDisciplineIds.contains(currentDisciplineRecordId)) {
+    // 更新逻辑
+}
+```
+
+### 正确的入参格式
+
+```json
+{
+  "violationData": [
+    {
+      "id": "1964301377652428802",           // 关联表ID (可选)
+      "disciplineRecordId": "1964301377522405377", // 违纪记录ID (用于更新判断)
+      "studentId": "1949114038914949121",
+      "studentNo": "2025DQ21121",
+      "studentName": "梁文清",
+      "disciplineTypeId": "1963793291531026434",
+      "disciplineTypeName": "地面未打扫",
+      "disciplineReason": "未按要求打扫地面",
+      "disciplineContent": "地面有明显垃圾",
+      "scoreDeduction": "2.00",
+      // ... 其他字段
+    }
+  ]
+}
+```
+
+### 修复效果
+
+1. **准确识别更新操作**:系统能正确识别哪些违纪记录需要更新,哪些需要新增
+2. **避免重复数据**:不再错误地删除现有记录并新增重复记录
+3. **向后兼容**:保持对旧版本数据格式的兼容性
+4. **数据完整性**:确保违纪记录的历史数据完整性
+
+### 测试验证
+
+使用修复后的接口,当入参包含 `disciplineRecordId` 时:
+- ✅ 系统识别为更新操作,直接更新现有违纪记录
+- ✅ 不再执行逻辑删除 + 新增的错误流程
+- ✅ 数据库中不会产生重复的违纪记录 

Разница между файлами не показана из-за своего большого размера
+ 557 - 249
web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue


+ 94 - 14
web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/components/ViolationRecordDialog.vue

@@ -7,7 +7,7 @@
           <span class="student-name">{{ studentInfo?.studentName }}</span>
           <span class="student-detail">({{ studentInfo?.studentNo }})</span>
           <span class="student-detail">{{ studentInfo?.className }}</span>
-          <span class="student-detail">{{ studentInfo?.bedName }}</span>
+          <span class="student-detail">{{ studentInfo?.bedCode }}</span>
         </div>
       </el-form-item>
 
@@ -167,7 +167,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed, watch, onMounted } from 'vue';
+import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue';
 import { ElMessage } from 'element-plus';
 import { Plus, InfoFilled, UploadFilled, Document, Loading, Download, Delete } from '@element-plus/icons-vue';
 // import ImagePreview from '@/components/ImagePreview/index.vue';
@@ -367,7 +367,42 @@ const initForm = () => {
 
   if (props.violationData) {
     console.log('违纪记录:编辑模式,violationData:', props.violationData);
-    Object.assign(form, props.violationData);
+
+    // 处理违纪数据回显
+    const violationDataCopy = { ...props.violationData };
+
+    // 确保disciplineTypeId是字符串类型(Element Plus select组件需要)
+    if (violationDataCopy.disciplineTypeId !== null && violationDataCopy.disciplineTypeId !== undefined) {
+      violationDataCopy.disciplineTypeId = violationDataCopy.disciplineTypeId.toString();
+    }
+
+    // 确保scoreDeduction是数值类型
+    if (violationDataCopy.scoreDeduction !== null && violationDataCopy.scoreDeduction !== undefined) {
+      violationDataCopy.scoreDeduction = parseFloat(violationDataCopy.scoreDeduction) || 0;
+    }
+
+    // 格式化日期(确保格式正确)
+    if (violationDataCopy.disciplineDate) {
+      // 如果是完整的日期时间,只取日期部分
+      const dateStr = violationDataCopy.disciplineDate.toString();
+      if (dateStr.includes(' ')) {
+        violationDataCopy.disciplineDate = dateStr.split(' ')[0];
+      } else if (dateStr.includes('T')) {
+        violationDataCopy.disciplineDate = dateStr.split('T')[0];
+      }
+    }
+
+    Object.assign(form, violationDataCopy);
+
+    console.log('违纪记录:表单数据已更新:', form);
+
+    // 如果有违纪类型ID,触发违纪类型变化事件以设置扣分范围
+    if (form.disciplineTypeId) {
+      // 使用 nextTick 确保违纪类型列表已加载
+      nextTick(() => {
+        handleDisciplineTypeChange(form.disciplineTypeId);
+      });
+    }
 
     // 如果是编辑模式,处理附件数据
     if (props.violationData.attachmentOssIds) {
@@ -680,24 +715,56 @@ const handleConfirm = async () => {
 // 关闭弹窗
 const handleClose = () => {
   emit('close');
-  resetForm();
+  // 只重置违纪相关字段,保留学生基本信息
+  Object.assign(form, {
+    disciplineTypeId: '',
+    disciplineTypeName: '',
+    disciplineDate: new Date().toISOString().split('T')[0],
+    disciplineReason: '',
+    disciplineContent: '',
+    scoreDeduction: 0,
+    attachmentOssIds: ''
+  });
+
+  attachmentList.value = [];
+  currentTypeMinScore.value = 0;
+  currentTypeMaxScore.value = 100;
 };
 
 // 重置表单
 const resetForm = () => {
+  // 保留学生基本信息(如果有的话)
+  const preserveStudentInfo = props.studentInfo
+    ? {
+        studentId: props.studentInfo.studentId || '',
+        studentNo: props.studentInfo.studentNo || '',
+        studentName: props.studentInfo.studentName || '',
+        collegeId: props.studentInfo.collegeId || '',
+        collegeName: props.studentInfo.collegeName || '',
+        majorId: props.studentInfo.majorId || '',
+        majorName: props.studentInfo.majorName || '',
+        classId: props.studentInfo.classId || '',
+        className: props.studentInfo.className || '',
+        bedCode: props.studentInfo.bedCode || '' // 保留床位信息
+      }
+    : {
+        studentId: '',
+        studentNo: '',
+        studentName: '',
+        collegeId: '',
+        collegeName: '',
+        majorId: '',
+        majorName: '',
+        classId: '',
+        className: '',
+        bedCode: ''
+      };
+
   Object.assign(form, {
-    studentId: '',
-    studentNo: '',
-    studentName: '',
-    collegeId: '',
-    collegeName: '',
-    majorId: '',
-    majorName: '',
-    classId: '',
-    className: '',
+    ...preserveStudentInfo,
     disciplineTypeId: '',
     disciplineTypeName: '',
-    disciplineDate: '',
+    disciplineDate: new Date().toISOString().split('T')[0], // 默认今天
     disciplineReason: '',
     disciplineContent: '',
     scoreDeduction: 0,
@@ -730,6 +797,19 @@ watch(
   }
 );
 
+// 监听违纪数据变化(新增)
+watch(
+  () => props.violationData,
+  (newViolationData) => {
+    // 当违纪数据发生变化时,重新初始化表单
+    if (newViolationData && props.modelValue) {
+      console.log('违纪记录:监听到violationData变化,重新初始化表单:', newViolationData);
+      initForm();
+    }
+  },
+  { immediate: true, deep: true }
+);
+
 // 组件挂载
 onMounted(() => {
   getAllViolationTypes();

+ 28 - 23
web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue

@@ -161,15 +161,14 @@
       @update:visible="(value) => (mainDialog.visible = value)"
     />
 
-    <!-- 单个宿舍房间检查弹窗组件(修改使用) -->
-    <DormHygieneCheckSingleDialog
-      :visible="singleDialog.visible"
-      :title="singleDialog.title"
-      :room-info="currentEditData?.roomInfo"
-      :check-info="currentEditData"
-      @submit="handleSingleDialogSubmit"
-      @close="handleSingleDialogClose"
-      @update:visible="(value) => (singleDialog.visible = value)"
+    <!-- 编辑宿舍卫生检查弹窗组件(修改使用) -->
+    <DormHygieneCheckEditDialog
+      :visible="editDialog.visible"
+      :title="editDialog.title"
+      :form-data="currentEditData"
+      @submit="handleEditDialogSubmit"
+      @close="handleEditDialogClose"
+      @update:visible="(value) => (editDialog.visible = value)"
     />
   </div>
 </template>
@@ -179,6 +178,8 @@ import { ref, reactive, toRefs, onMounted, getCurrentInstance, ComponentInternal
 import ElMessage from 'element-plus/es/components/message/index';
 import ElMessageBox from 'element-plus/es/components/message-box/index';
 import ElLoading from 'element-plus/es/components/loading/index';
+// @ts-expect-error: request util import
+import request from '@/utils/request';
 import {
   listDormHygieneCheck,
   getDormHygieneCheck,
@@ -194,7 +195,7 @@ import {
   DormHygieneCheckCompleteForm
 } from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types';
 import DormHygieneCheckMainDialog from './DormHygieneCheckMainDialog.vue';
-import DormHygieneCheckSingleDialog from './DormHygieneCheckSingleDialog.vue';
+import DormHygieneCheckEditDialog from './DormHygieneCheckEditDialog.vue';
 
 // 类型定义
 interface DialogOption {
@@ -229,8 +230,8 @@ const mainDialog = reactive<DialogOption>({
   title: ''
 });
 
-// 单个房间检查弹窗(修改)
-const singleDialog = reactive<DialogOption>({
+// 编辑宿舍卫生检查弹窗(修改)
+const editDialog = reactive<DialogOption>({
   visible: false,
   title: ''
 });
@@ -326,9 +327,9 @@ const handleMainDialogClose = () => {
   reset();
 };
 
-/** 单个房间检查弹窗关闭事件处理 */
-const handleSingleDialogClose = () => {
-  singleDialog.visible = false;
+/** 编辑弹窗关闭事件处理 */
+const handleEditDialogClose = () => {
+  editDialog.visible = false;
   currentEditData.value = null;
 };
 
@@ -346,13 +347,17 @@ const handleMainDialogSubmit = async (formData: any) => {
   }
 };
 
-/** 单个房间检查弹窗提交事件处理 */
-const handleSingleDialogSubmit = async (formData: any) => {
+/** 编辑弹窗提交事件处理 */
+const handleEditDialogSubmit = async (formData: any) => {
   try {
-    // 单个房间检查弹窗提交的是修改数据
-    await updateDormHygieneCheck(formData);
+    // 编辑弹窗提交的是修改数据,调用完整更新接口
+    await request({
+      url: '/dormHygieneCheck/dormHygieneCheck/updateCompleteData',
+      method: 'put',
+      data: formData
+    });
     ElMessage.success('操作成功');
-    singleDialog.visible = false;
+    editDialog.visible = false;
     await getList();
   } catch (error) {
     console.error('提交失败:', error);
@@ -400,8 +405,8 @@ const handleAdd = () => {
 
 /** 修改按钮操作 */
 const handleUpdate = async (row?: DormHygieneCheckVO) => {
-  singleDialog.visible = true;
-  singleDialog.title = '修改宿舍卫生检查';
+  editDialog.visible = true;
+  editDialog.title = '修改宿舍卫生检查';
 
   const loadingInstance = ElLoading.service({
     target: '.el-dialog__body',
@@ -416,7 +421,7 @@ const handleUpdate = async (row?: DormHygieneCheckVO) => {
   } catch (error) {
     console.error('加载编辑数据失败:', error);
     ElMessage.error('加载数据失败,请重试');
-    singleDialog.visible = false;
+    editDialog.visible = false;
   } finally {
     loadingInstance.close();
   }

Некоторые файлы не были показаны из-за большого количества измененных файлов