diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormHygieneCheckController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormHygieneCheckController.java index f3f4c994..a16131a9 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormHygieneCheckController.java +++ b/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 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> getViolations( + @NotNull(message = "卫生检查ID不能为空") @PathVariable Long hygieneCheckId) { + List violations = xgDormHygieneCheckService + .getViolationsByHygieneCheckId(hygieneCheckId); + return R.ok(violations); + } + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgHygieneCheckViolation.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgHygieneCheckViolation.java index b98fb144..0a7d8d39 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgHygieneCheckViolation.java +++ b/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; + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgViolationDataBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgViolationDataBo.java index 37949e26..7ab4228b 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgViolationDataBo.java +++ b/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 */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgHygieneCheckViolationVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgHygieneCheckViolationVo.java index d8818bdd..cd6a9dc6 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgHygieneCheckViolationVo.java +++ b/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; + /** * 创建者 */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgHygieneCheckViolationMapper.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgHygieneCheckViolationMapper.java index efc9d024..8c07c95a 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgHygieneCheckViolationMapper.java +++ b/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 { + extends BaseMapperPlus { + + /** + * 根据卫生检查ID查询违纪记录(包含详情) + * + * @param hygieneCheckId 卫生检查ID + * @return 违纪记录列表(包含详情) + */ + List selectViolationsByHygieneCheckId(@Param("hygieneCheckId") Long hygieneCheckId); } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormHygieneCheckService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormHygieneCheckService.java index 030827f9..9af2b759 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormHygieneCheckService.java +++ b/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 getViolationsByHygieneCheckId(Long hygieneCheckId); + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java index 7837ad2d..e9e0ce43 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java +++ b/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 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 newViolationData) { + try { + // 1. 查询数据库中现有的违纪记录 + List existingViolations = getViolationsByHygieneCheckId(hygieneCheckId); + log.info("数据库中现有违纪记录数量: {}", existingViolations.size()); + + // 2. 收集现有记录的违纪记录ID(disciplineRecordId) + Set existingDisciplineIds = existingViolations.stream() + .map(XgHygieneCheckViolationVo::getDisciplineRecordId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + log.info("现有违纪记录ID集合: {}", existingDisciplineIds); + + // 3. 收集新数据中的违纪记录ID(优先使用disciplineRecordId字段,兼容旧版本的id字段) + Set 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 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 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 getViolationsByHygieneCheckId(Long hygieneCheckId) { + if (hygieneCheckId == null) { + log.warn("卫生检查ID为空,无法查询违纪记录"); + return List.of(); + } + + try { + // 使用自定义查询方法,关联查询违纪详情 + List 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(); + } + } + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormHygieneCheck/XgHygieneCheckViolationMapper.xml b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormHygieneCheck/XgHygieneCheckViolationMapper.xml new file mode 100644 index 00000000..d2a96137 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormHygieneCheck/XgHygieneCheckViolationMapper.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + + + diff --git a/docs/开发文档/API/宿舍卫生检查管理API.md b/docs/开发文档/API/宿舍卫生检查管理API.md index 15961142..05074370 100644 --- a/docs/开发文档/API/宿舍卫生检查管理API.md +++ b/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` \ No newline at end of file +- `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` 时: +- ✅ 系统识别为更新操作,直接更新现有违纪记录 +- ✅ 不再执行逻辑删除 + 新增的错误流程 +- ✅ 数据库中不会产生重复的违纪记录 \ No newline at end of file diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue index 5a8de4db..0da9cb36 100644 --- a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue @@ -47,6 +47,30 @@ + + + + + + + +
+ + 类型说明:{{ selectedDisciplineTypeRemark }} +
+
+
- - - - - - - - - - - - -
- + + +
+ +
将文件拖到此处,或点击上传
+
+ + +
@@ -126,51 +187,108 @@ -
- - - - +
+
+
+
+
+

{{ student.studentName }}

+ 男 + 女 +
+

{{ student.studentNo }}

+
+
+ {{ student.bedCode }} +
+
- - - +
+

班级:{{ student.className }}

+

专业:{{ student.majorName }}

+
- - - +
+ + +
+ + +
+
+ +
+ + + + +
+
+ +
+ + + + +
+
+
+
- - - - - - - - - - + +
+
+ 违纪情况 + 录入违纪 +
+ +
+
+
+ {{ violation.disciplineTypeName }} + 扣{{ violation.scoreDeduction }}分 + (已撤销) +
+
+ + 编辑 + + + 删除 + +
+
+
+ +
+ 暂无违纪记录 +
+
+
@@ -179,53 +297,6 @@
- - - - - -
- - - - - - - - - - - - - - - - -
- - - -
- -
-
- @@ -794,18 +1156,193 @@ watch( gap: 8px; } -.students-table-wrapper { - max-height: 400px; - overflow: auto; +.students-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 16px; +} + +.student-card { border: 1px solid #e4e7ed; + border-radius: 8px; + padding: 16px; + background: #fff; + transition: all 0.3s; +} + +.student-card:hover { + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); +} + +.student-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 12px; +} + +.student-name-with-gender { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} + +.student-basic-info h4 { + margin: 0; + color: #303133; + font-size: 16px; +} + +.student-basic-info p { + margin: 0 0 8px 0; + color: #666; + font-size: 14px; +} + +.student-details { + margin-bottom: 16px; + font-size: 13px; + color: #666; +} + +.student-details p { + margin: 4px 0; +} + +.student-score-section { + margin-bottom: 16px; +} + +.score-item { + margin-bottom: 8px; +} + +.score-item label { + display: block; + font-size: 12px; + color: #666; + margin-bottom: 4px; +} + +/* 确保个人扣分输入框与下拉框高度一致 */ +.score-item .el-input, +.score-item .el-select { + height: 32px !important; +} + +/* 输入框样式统一 */ +.score-item .el-input .el-input__wrapper { + height: 32px !important; + min-height: 32px !important; + box-sizing: border-box !important; + padding: 1px 11px !important; +} + +.score-item .el-input .el-input__inner { + height: 30px !important; + line-height: 30px !important; + padding: 0 !important; + border: none !important; + background: transparent !important; +} + +/* 下拉框样式统一 */ +.score-item .el-select .el-select__wrapper { + height: 32px !important; + min-height: 32px !important; + box-sizing: border-box !important; + padding: 1px 11px !important; +} + +.score-item .el-select .el-select__selection { + height: 30px !important; + line-height: 30px !important; + padding: 0 !important; +} + +.score-item .el-select .el-select__selected-item { + height: 30px !important; + line-height: 30px !important; + padding: 0 !important; +} + +.score-item .el-select .el-select__placeholder { + height: 30px !important; + line-height: 30px !important; + padding: 0 !important; +} + +.violation-section { + border-top: 1px solid #f0f0f0; + padding-top: 12px; +} + +.violation-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.violation-header span { + font-size: 14px; + font-weight: 600; + color: #303133; +} + +.violations-list { + max-height: 150px; + overflow-y: auto; +} + +.violation-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; + margin-bottom: 8px; + background: #fdf6ec; + border: 1px solid #faecd8; border-radius: 4px; } -.violations-table-wrapper { - max-height: 400px; - overflow: auto; - border: 1px solid #e4e7ed; - border-radius: 4px; +.violation-item[data-status='2'] { + background: #f5f5f5; + border-color: #e4e7ed; +} + +.violation-info { + flex: 1; +} + +.violation-type { + font-size: 13px; + color: #e6a23c; + font-weight: 500; +} + +.violation-score { + font-size: 12px; + color: #f56c6c; + margin-left: 8px; +} + +.violation-status { + font-size: 12px; + color: #909399; + margin-left: 8px; +} + +.violation-actions { + display: flex; + gap: 4px; +} + +.no-violations { + text-align: center; + padding: 12px; + color: #909399; } .female-tag { @@ -814,12 +1351,245 @@ watch( border-color: rgba(255, 105, 180, 0.1) !important; } +/* 附件上传容器样式 */ .attachment-upload-container { width: 100%; + max-height: calc(100vh - 650px); + min-height: 200px; + overflow-y: auto; + padding-right: 8px; + border: 1px solid #e4e7ed; + border-radius: 6px; + background: #fafafa; +} + +/* 附件上传区域内容样式 */ +.attachment-upload-container .upload-demo { + padding: 12px; + min-height: 180px; +} + +.attachment-upload-container .upload-demo .el-upload { + border-radius: 6px; + cursor: pointer; + position: relative; + overflow: hidden; + transition: all 0.3s; +} + +.attachment-upload-container .upload-demo .el-upload:hover { + border-color: #409eff; +} + +.attachment-upload-container .upload-demo .el-upload__tip { + font-size: 12px; + color: #000000; + margin-top: 7px; +} + +/* 调整拖拽上传图标与文字在同一行、居中对齐 */ +.attachment-upload-container .upload-demo .el-upload-dragger1 { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 15px; + padding: 0px !important; +} + +.attachment-upload-container .upload-demo .el-upload-dragger1 .el-icon--upload { + margin: 0; + padding: 0px !important; +} + +/* 文件卡片占位封面样式 */ +.attachment-upload-container .upload-demo .upload-file-placeholder { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #909399; + font-size: 14px; +} + +.attachment-upload-container .upload-demo .upload-file-placeholder .el-icon { + font-size: 28px; + margin-bottom: 4px; +} + +.attachment-upload-container .upload-demo .upload-file-placeholder .file-ext { + font-size: 12px; + text-transform: uppercase; + text-align: center; +} + +/* 让附件卡片一行展示 5 个 */ +.attachment-upload-container :deep(.el-upload-list--picture-card) { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 9px; + margin-top: 12px; + padding-bottom: 16px; +} + +/* 确保卡片宽度自适应 */ +.attachment-upload-container :deep(.el-upload-list--picture-card) .el-upload-list__item { + margin: 0; + height: 180px !important; + width: 100% !important; +} + +:deep(.el-upload-dragger) { + border: 0px !important; + padding: 0px !important; +} + +:deep(.el-upload-dragger) .el-upload-dragger1 { + border: 0px !important; + padding: 0px !important; +} + +/* 文件卡片样式 */ +.upload-file-card { + position: relative; + width: 100%; + height: 100%; + border-radius: 6px; + overflow: hidden; + background: #fff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transition: all 0.3s; +} + +.upload-file-card:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.upload-file-thumb { + position: relative; + width: 100%; + height: 110px; + background: #f5f5f5; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.upload-thumb-image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.upload-file-loading { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 10; +} + +.loading-text { + margin-top: 8px; + font-size: 12px; + color: #606266; +} + +.upload-file-name { + padding: 6px 8px; + font-size: 11px; + color: #303133; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + background: #fafafa; + border-top: 1px solid #f0f0f0; + line-height: 1.2; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + word-break: break-all; + max-width: 100%; +} + +.upload-file-card-actions { + display: flex; + justify-content: center; + align-items: center; + padding: 6px 8px; + background: #fafafa; + border-top: 1px solid #f0f0f0; + transition: all 0.3s; + height: 38px; +} + +.upload-file-card-actions .el-button-group { + background: transparent; + border-radius: 4px; + padding: 0; +} + +.upload-file-card-actions .el-button { + padding: 4px 8px; + border: 1px solid #e0e0e0; + background: #fff; + font-size: 12px; + margin: 0 2px; + border-radius: 3px; + transition: all 0.3s; +} + +.upload-file-card-actions .el-button:hover { + background: #f5f5f5; + border-color: #409eff; +} + +.upload-file-card-actions .el-button.is-disabled { + opacity: 0.5; + cursor: not-allowed; + background: #f5f5f5; } .loading-container { padding: 20px; text-align: center; } + +.discipline-form-item { + margin-bottom: 0; +} + +.discipline-type-remark { + display: flex; + align-items: center; + margin-top: 0; + margin-left: -120px; + padding-left: 120px; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 10px; + border-radius: 4px; +} + +.remark-icon { + margin-right: 6px; + color: #909399; + font-size: 14px; +} + +.remark-text { + font-size: 12px; + color: #606266; + line-height: 1.4; +} diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/components/ViolationRecordDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/components/ViolationRecordDialog.vue index d88085c4..50019509 100644 --- a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/components/ViolationRecordDialog.vue +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/components/ViolationRecordDialog.vue @@ -7,7 +7,7 @@ {{ studentInfo?.studentName }} ({{ studentInfo?.studentNo }}) {{ studentInfo?.className }} - {{ studentInfo?.bedName }} + {{ studentInfo?.bedCode }}
@@ -167,7 +167,7 @@