diff --git a/backend/ruoyi-modules/ruoyi-xg/pom.xml b/backend/ruoyi-modules/ruoyi-xg/pom.xml index e41186bf..a2987fb7 100644 --- a/backend/ruoyi-modules/ruoyi-xg/pom.xml +++ b/backend/ruoyi-modules/ruoyi-xg/pom.xml @@ -104,6 +104,13 @@ ruoyi-workflow + + + org.dromara + ruoyi-xskh + ${revision} + + 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 c5e203d0..f3f4c994 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 @@ -164,4 +164,15 @@ public class XgDormHygieneCheckController extends BaseController { return xgDormHygieneCheckService.queryPageListByStudentId(studentId, bo, pageQuery); } + /** + * 保存完整的宿舍卫生检查数据(包含学生评分和违纪记录) + */ + @SaCheckPermission("dormHygieneCheck:dormHygieneCheck:add") + @Log(title = "宿舍卫生检查", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/saveCompleteData") + public R saveCompleteData(@Validated(AddGroup.class) @RequestBody XgDormHygieneCheckCompleteBo bo) { + return toAjax(xgDormHygieneCheckService.saveCompleteData(bo)); + } + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java index e80ba06a..c183d0dd 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java @@ -140,4 +140,14 @@ public class XgDormHygieneCheck extends TenantEntity { */ private String checkRemarks; + /** + * 违纪类型ID,关联xskh_discipline_type.id + */ + private Long disciplineTypeId; + + /** + * 违纪类型名称 + */ + private String disciplineTypeName; + } 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 new file mode 100644 index 00000000..b98fb144 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgHygieneCheckViolation.java @@ -0,0 +1,77 @@ +package org.dromara.xg.domain; + +import org.dromara.common.tenant.core.TenantEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.math.BigDecimal; + +import java.io.Serial; + +/** + * 宿舍卫生检查违纪关联对象 xg_hygiene_check_violation + * + * @author 吴自龙 + * @date 2025-01-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("xg_hygiene_check_violation") +public class XgHygieneCheckViolation extends TenantEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + private Long id; + + /** + * 宿舍卫生检查ID,关联xg_dorm_hygiene_check.id + */ + private Long hygieneCheckId; + + /** + * 违纪记录ID,关联xskh_discipline_record.id + */ + private Long disciplineRecordId; + + /** + * 学生ID,关联xg_student_msg.id + */ + private Long studentId; + + /** + * 学号 + */ + private String studentNo; + + /** + * 学生姓名 + */ + private String studentName; + + /** + * 违纪类型ID,关联xskh_discipline_type.id + */ + private Long disciplineTypeId; + + /** + * 违纪类型名称 + */ + private String disciplineTypeName; + + /** + * 违纪来源(1宿舍卫生检查 2其他) + */ + private String violationSource; + + /** + * 状态(1生效 2已撤销) + */ + private String status; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckBo.java index ca9e945d..5912f3f9 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckBo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckBo.java @@ -147,5 +147,14 @@ public class XgDormHygieneCheckBo extends BaseEntity { */ private String checkRemarks; + /** + * 违纪类型ID,关联xskh_discipline_type.id + */ + private Long disciplineTypeId; + + /** + * 违纪类型名称 + */ + private String disciplineTypeName; } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckCompleteBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckCompleteBo.java index 3a84a6a1..18d2202f 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckCompleteBo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckCompleteBo.java @@ -149,6 +149,16 @@ public class XgDormHygieneCheckCompleteBo { */ private String checkRemarks; + /** + * 违纪类型ID,关联xskh_discipline_type.id + */ + private Long disciplineTypeId; + + /** + * 违纪类型名称 + */ + private String disciplineTypeName; + /** * 学生评分数据列表 */ @@ -156,6 +166,12 @@ public class XgDormHygieneCheckCompleteBo { @NotEmpty(message = "学生评分数据不能为空", groups = { AddGroup.class, EditGroup.class }) private List studentScores; + /** + * 违纪记录数据列表 + */ + @Valid + private List violationData; + // 基础字段(用于VO类映射) private Long createBy; @@ -166,4 +182,4 @@ public class XgDormHygieneCheckCompleteBo { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; -} \ No newline at end of file +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgHygieneCheckViolationBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgHygieneCheckViolationBo.java new file mode 100644 index 00000000..9c466d26 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgHygieneCheckViolationBo.java @@ -0,0 +1,106 @@ +package org.dromara.xg.domain.bo; + +import org.dromara.xg.domain.XgHygieneCheckViolation; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * 宿舍卫生检查违纪关联业务对象 xg_hygiene_check_violation + * + * @author 吴自龙 + * @date 2025-01-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = XgHygieneCheckViolation.class, reverseConvertGenerate = false) +public class XgHygieneCheckViolationBo extends BaseEntity { + + /** + * 主键ID + */ + private Long id; + + /** + * 宿舍卫生检查ID,关联xg_dorm_hygiene_check.id + */ + private Long hygieneCheckId; + + /** + * 违纪记录ID,关联xskh_discipline_record.id + */ + private Long disciplineRecordId; + + /** + * 学生ID,关联xg_student_msg.id + */ + @NotNull(message = "学生ID不能为空") + private Long studentId; + + /** + * 学号 + */ + @NotBlank(message = "学号不能为空") + private String studentNo; + + /** + * 学生姓名 + */ + @NotBlank(message = "学生姓名不能为空") + private String studentName; + + /** + * 违纪类型ID,关联xskh_discipline_type.id + */ + @NotNull(message = "违纪类型ID不能为空") + private Long disciplineTypeId; + + /** + * 违纪类型名称 + */ + @NotBlank(message = "违纪类型名称不能为空") + private String disciplineTypeName; + + /** + * 违纪来源(1宿舍卫生检查 2其他) + */ + private String violationSource; + + /** + * 状态(1生效 2已撤销) + */ + private String status; + + /** + * 分页大小 + */ + @JsonIgnore + private Integer pageSize; + + /** + * 当前页数 + */ + @JsonIgnore + private Integer pageNum; + + /** + * 排序列 + */ + @JsonIgnore + private String orderByColumn; + + /** + * 排序的方向desc或者asc + */ + @JsonIgnore + private String isAsc; + +} 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 new file mode 100644 index 00000000..37949e26 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgViolationDataBo.java @@ -0,0 +1,115 @@ +package org.dromara.xg.domain.bo; + +import lombok.Data; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; + +/** + * 违纪数据业务对象(用于前端传输) + * + * @author 吴自龙 + * @date 2025-01-03 + */ +@Data +public class XgViolationDataBo { + + /** + * 学生ID,关联xg_student_msg.id + */ + @NotNull(message = "学生ID不能为空") + private Long studentId; + + /** + * 学号 + */ + @NotBlank(message = "学号不能为空") + private String studentNo; + + /** + * 学生姓名 + */ + @NotBlank(message = "学生姓名不能为空") + private String studentName; + + /** + * 班级ID + */ + private Long classId; + + /** + * 班级名称 + */ + private String className; + + /** + * 专业ID + */ + private Long majorId; + + /** + * 专业名称 + */ + private String majorName; + + /** + * 学院ID + */ + private Long collegeId; + + /** + * 学院名称 + */ + private String collegeName; + + /** + * 违纪类型ID,关联xskh_discipline_type.id + */ + @NotNull(message = "违纪类型ID不能为空") + private Long disciplineTypeId; + + /** + * 违纪类型名称 + */ + @NotBlank(message = "违纪类型名称不能为空") + private String disciplineTypeName; + + /** + * 违纪日期 + */ + private String disciplineDate; + + /** + * 违纪原因 + */ + @NotBlank(message = "违纪原因不能为空") + private String disciplineReason; + + /** + * 违纪内容 + */ + private String disciplineContent; + + /** + * 扣分分值 + */ + private BigDecimal scoreDeduction; + + /** + * 违纪来源(1宿舍卫生检查 2其他) + */ + private String violationSource; + + /** + * 状态(1生效 2已撤销) + */ + private String status; + + /** + * 附件OSS ID列表,逗号分隔 + */ + private String attachmentOssIds; +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckVo.java index 43ba5278..bd50c81c 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckVo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckVo.java @@ -14,8 +14,6 @@ import java.io.Serial; import java.io.Serializable; import java.util.Date; - - /** * 宿舍卫生检查视图对象 xg_dorm_hygiene_check * @@ -173,5 +171,16 @@ public class XgDormHygieneCheckVo implements Serializable { @ExcelProperty(value = "检查备注") private String checkRemarks; + /** + * 违纪类型ID,关联xskh_discipline_type.id + */ + @ExcelProperty(value = "违纪类型ID") + private Long disciplineTypeId; + + /** + * 违纪类型名称 + */ + @ExcelProperty(value = "违纪类型名称") + private String disciplineTypeName; } 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 new file mode 100644 index 00000000..d8818bdd --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgHygieneCheckViolationVo.java @@ -0,0 +1,97 @@ +package org.dromara.xg.domain.vo; + +import org.dromara.xg.domain.XgHygieneCheckViolation; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import java.math.BigDecimal; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 宿舍卫生检查违纪关联视图对象 xg_hygiene_check_violation + * + * @author 吴自龙 + * @date 2025-01-03 + */ +@Data +@AutoMapper(target = XgHygieneCheckViolation.class) +public class XgHygieneCheckViolationVo { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + private Long id; + + /** + * 宿舍卫生检查ID + */ + private Long hygieneCheckId; + + /** + * 违纪记录ID + */ + private Long disciplineRecordId; + + /** + * 学生ID + */ + private Long studentId; + + /** + * 学号 + */ + private String studentNo; + + /** + * 学生姓名 + */ + private String studentName; + + /** + * 违纪类型ID + */ + private Long disciplineTypeId; + + /** + * 违纪类型名称 + */ + private String disciplineTypeName; + + /** + * 违纪来源 + */ + private String violationSource; + + /** + * 状态 + */ + private String status; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新者 + */ + private Long updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + +} 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 new file mode 100644 index 00000000..efc9d024 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgHygieneCheckViolationMapper.java @@ -0,0 +1,16 @@ +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; + +/** + * 宿舍卫生检查违纪记录Mapper接口 + * + * @author zlwu + * @date 2025-01-29 + */ +public interface XgHygieneCheckViolationMapper + extends BaseMapperPlus { + +} 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 fc056139..030827f9 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 @@ -111,4 +111,12 @@ public interface IXgDormHygieneCheckService { TableDataInfo queryPageListByStudentId(Long studentId, XgDormHygieneCheckBo bo, PageQuery pageQuery); + /** + * 保存完整的宿舍卫生检查数据(包含学生评分和违纪记录) + * + * @param bo 完整的宿舍卫生检查数据 + * @return 是否保存成功 + */ + Boolean saveCompleteData(XgDormHygieneCheckCompleteBo bo); + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgHygieneCheckViolationService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgHygieneCheckViolationService.java new file mode 100644 index 00000000..db954823 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgHygieneCheckViolationService.java @@ -0,0 +1,85 @@ +package org.dromara.xg.service; + +import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo; +import org.dromara.xg.domain.bo.XgHygieneCheckViolationBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 宿舍卫生检查违纪记录Service接口 + * + * @author zlwu + * @date 2025-01-29 + */ +public interface IXgHygieneCheckViolationService { + + /** + * 查询宿舍卫生检查违纪记录 + * + * @param id 主键 + * @return 宿舍卫生检查违纪记录 + */ + XgHygieneCheckViolationVo queryById(Long id); + + /** + * 分页查询宿舍卫生检查违纪记录列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 宿舍卫生检查违纪记录分页列表 + */ + TableDataInfo queryPageList(XgHygieneCheckViolationBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的宿舍卫生检查违纪记录列表 + * + * @param bo 查询条件 + * @return 宿舍卫生检查违纪记录列表 + */ + List queryList(XgHygieneCheckViolationBo bo); + + /** + * 新增宿舍卫生检查违纪记录 + * + * @param bo 宿舍卫生检查违纪记录 + * @return 是否新增成功 + */ + Boolean insertByBo(XgHygieneCheckViolationBo bo); + + /** + * 修改宿舍卫生检查违纪记录 + * + * @param bo 宿舍卫生检查违纪记录 + * @return 是否修改成功 + */ + Boolean updateByBo(XgHygieneCheckViolationBo bo); + + /** + * 校验并批量删除宿舍卫生检查违纪记录信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 批量新增宿舍卫生检查违纪记录 + * + * @param bos 违纪记录列表 + * @return 是否新增成功 + */ + Boolean insertBatch(List bos); + + /** + * 根据卫生检查ID查询违纪记录列表 + * + * @param hygieneCheckId 卫生检查ID + * @return 违纪记录列表 + */ + List queryByHygieneCheckId(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 f74536ad..7837ad2d 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 @@ -14,6 +14,8 @@ import org.springframework.transaction.annotation.Transactional; import org.dromara.xg.domain.bo.XgDormHygieneCheckBo; import org.dromara.xg.domain.bo.XgDormHygieneCheckCompleteBo; import org.dromara.xg.domain.bo.XgDormHygieneStudentScoreBo; +import org.dromara.xg.domain.bo.XgHygieneCheckViolationBo; +import org.dromara.xg.domain.bo.XgViolationDataBo; import org.dromara.xg.domain.vo.DormHygieneCheckExportVo; import org.dromara.xg.domain.vo.XgDormHygieneCheckVo; import org.dromara.xg.domain.XgDormHygieneCheck; @@ -22,11 +24,14 @@ import org.dromara.xg.mapper.XgDormHygieneCheckMapper; 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.system.service.ISysDictDataService; +import org.springframework.context.ApplicationContext; import java.util.List; import java.util.Map; import java.util.Collection; +import java.util.HashMap; /** * 宿舍卫生检查Service业务层处理 @@ -43,6 +48,8 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService private final XgDormHygieneStudentScoreMapper studentScoreMapper; private final IXgStudentMsgService xgStudentMsgService; private final ISysDictDataService sysDictDataService; + private final IXgHygieneCheckViolationService violationService; + private final ApplicationContext applicationContext; /** * 查询宿舍卫生检查 @@ -574,4 +581,370 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService return "1".equals(isResponsiblePerson) ? "是" : "否"; } + /** + * 保存完整的宿舍卫生检查数据(包含学生评分和违纪记录) + * + * @param bo 完整的宿舍卫生检查数据 + * @return 是否保存成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean saveCompleteData(XgDormHygieneCheckCompleteBo bo) { + try { + // 1. 转换并保存主表数据 + XgDormHygieneCheck add = MapstructUtils.convert(bo, XgDormHygieneCheck.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + log.info("保存宿舍卫生检查主表数据: {}", add); + + if (!flag) { + log.error("保存宿舍卫生检查主表数据失败"); + return false; + } + + Long hygieneCheckId = add.getId(); + + // 2. 保存学生评分数据 + if (bo.getStudentScores() != null && !bo.getStudentScores().isEmpty()) { + for (XgDormHygieneStudentScoreBo studentScoreBo : bo.getStudentScores()) { + XgDormHygieneStudentScore studentScore = MapstructUtils.convert(studentScoreBo, + XgDormHygieneStudentScore.class); + studentScore.setHygieneCheckId(hygieneCheckId); + + // 根据学生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. 保存违纪记录数据(兼容架构:优先保存到统一表,失败时保存关联关系) + if (bo.getViolationData() != null && !bo.getViolationData().isEmpty()) { + for (XgViolationDataBo violationBo : bo.getViolationData()) { + try { + log.info("处理违纪记录数据: {}", violationBo); + + // 3.1 尝试创建违纪记录到统一的xskh_discipline_record表 + Long disciplineRecordId = createDisciplineRecord(violationBo); + + // 3.2 仅当违纪记录创建成功时才创建关联记录 + if (disciplineRecordId != 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); + } + } else { + log.warn("违纪记录创建失败,跳过关联记录创建,学生ID: {}", violationBo.getStudentId()); + } + } catch (Exception e) { + log.error("处理违纪数据失败,学生ID: {}, 错误: {}", violationBo.getStudentId(), e.getMessage(), e); + // 不影响主流程,继续处理下一个 + } + } + } + + log.info("完整保存宿舍卫生检查数据成功,检查ID: {}", hygieneCheckId); + return true; + + } catch (Exception e) { + log.error("保存宿舍卫生检查数据失败", e); + throw new RuntimeException("保存宿舍卫生检查数据失败: " + e.getMessage(), e); + } + } + + /** + * 创建学生违纪记录 + * + * @param violationBo 违纪数据 + * @return 违纪记录ID + */ + private Long createDisciplineRecord(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) { + // 反射调用insertByBo方法 - 使用具体的Bo类型而不是Object + Class disciplineRecordBoClass = Class + .forName("org.dromara.xskh.domain.bo.XskhDisciplineRecordBo"); + var insertMethod = disciplineRecordService.getClass().getMethod("insertByBo", + disciplineRecordBoClass); + Boolean result = (Boolean) insertMethod.invoke(disciplineRecordService, disciplineRecordBo); + + if (result) { + // 获取插入后的ID + var field = disciplineRecordBo.getClass().getDeclaredField("id"); + field.setAccessible(true); + Long id = (Long) field.get(disciplineRecordBo); + log.info("创建学生违纪记录成功,学生ID: {}, 违纪记录ID: {}", violationBo.getStudentId(), id); + return id; + } + } + } + } catch (ClassNotFoundException e) { + log.warn("学生考核管理模块(xskh)未启用,跳过违纪记录创建"); + } catch (Exception e) { + log.error("创建学生违纪记录失败: {}", e.getMessage(), e); + } + return null; + } + + /** + * 创建学生违纪记录Bo对象 + * + * @param violationBo 违纪数据 + * @return 学生违纪记录Bo对象 + */ + private Object createDisciplineRecordBo(XgViolationDataBo violationBo) { + try { + // 通过反射创建XskhDisciplineRecordBo对象 + Class disciplineRecordBoClass = Class.forName("org.dromara.xskh.domain.bo.XskhDisciplineRecordBo"); + Object disciplineRecordBo = disciplineRecordBoClass.getDeclaredConstructor().newInstance(); + + // 设置基本字段 + setFieldValue(disciplineRecordBo, "studentId", violationBo.getStudentId()); + setFieldValue(disciplineRecordBo, "studentNo", violationBo.getStudentNo()); + setFieldValue(disciplineRecordBo, "studentName", violationBo.getStudentName()); + + // 修复:确保collegeId、majorId、classId不为null + // 如果ID为空,尝试通过学生ID查询完整信息 + Long collegeId = violationBo.getCollegeId(); + Long majorId = violationBo.getMajorId(); + Long classId = violationBo.getClassId(); + String collegeName = violationBo.getCollegeName(); + String majorName = violationBo.getMajorName(); + String className = violationBo.getClassName(); + + // 如果ID字段为空,尝试从学生信息中获取 + if ((collegeId == null || collegeId == 0) && violationBo.getStudentId() != null) { + try { + // 查询学生详细信息以获取缺失的ID字段 + Map studentDetail = getStudentDetailInfo(violationBo.getStudentId()); + if (studentDetail != null) { + // 修复:安全地获取Long类型的ID字段 + Object collegeIdObj = studentDetail.get("collegeId"); + Object majorIdObj = studentDetail.get("majorId"); + Object classIdObj = studentDetail.get("classId"); + + collegeId = (collegeIdObj instanceof Long) ? (Long) collegeIdObj + : (collegeIdObj != null ? Long.valueOf(collegeIdObj.toString()) : null); + majorId = (majorIdObj instanceof Long) ? (Long) majorIdObj + : (majorIdObj != null ? Long.valueOf(majorIdObj.toString()) : null); + classId = (classIdObj instanceof Long) ? (Long) classIdObj + : (classIdObj != null ? Long.valueOf(classIdObj.toString()) : null); + + // 如果名称为空,也从查询结果中获取 + if (StringUtils.isBlank(collegeName)) { + collegeName = (String) studentDetail.getOrDefault("collegeName", "未知学院"); + } + if (StringUtils.isBlank(majorName)) { + majorName = (String) studentDetail.getOrDefault("majorName", "未知专业"); + } + if (StringUtils.isBlank(className)) { + className = (String) studentDetail.getOrDefault("className", "未知班级"); + } + + log.info("从学生信息中补充ID字段,学生ID: {}, 学院ID: {}, 专业ID: {}, 班级ID: {}", + violationBo.getStudentId(), collegeId, majorId, classId); + } + } catch (Exception e) { + log.warn("查询学生详细信息失败,使用默认值,学生ID: {}, 错误: {}", violationBo.getStudentId(), e.getMessage()); + } + } + + setFieldValue(disciplineRecordBo, "collegeId", collegeId != null ? collegeId : 0L); + setFieldValue(disciplineRecordBo, "collegeName", + StringUtils.isNotBlank(collegeName) ? collegeName : "未知学院"); + setFieldValue(disciplineRecordBo, "majorId", majorId != null ? majorId : 0L); + setFieldValue(disciplineRecordBo, "majorName", + StringUtils.isNotBlank(majorName) ? majorName : "未知专业"); + setFieldValue(disciplineRecordBo, "classId", classId != null ? classId : 0L); + setFieldValue(disciplineRecordBo, "className", + StringUtils.isNotBlank(className) ? className : "未知班级"); + + setFieldValue(disciplineRecordBo, "disciplineTypeId", violationBo.getDisciplineTypeId()); + setFieldValue(disciplineRecordBo, "disciplineTypeName", violationBo.getDisciplineTypeName()); + + // 设置违纪日期 + if (StringUtils.isNotBlank(violationBo.getDisciplineDate())) { + setFieldValue(disciplineRecordBo, "disciplineDate", + java.time.LocalDate.parse(violationBo.getDisciplineDate())); + } else { + setFieldValue(disciplineRecordBo, "disciplineDate", java.time.LocalDate.now()); + } + + // 设置违纪原因和内容 + setFieldValue(disciplineRecordBo, "disciplineReason", violationBo.getDisciplineReason()); + setFieldValue(disciplineRecordBo, "disciplineContent", violationBo.getDisciplineContent()); + + // 设置扣分分值 + if (violationBo.getScoreDeduction() != null) { + setFieldValue(disciplineRecordBo, "scoreDeduction", violationBo.getScoreDeduction()); + } + + // 设置状态和违纪来源 + setFieldValue(disciplineRecordBo, "status", + StringUtils.isNotBlank(violationBo.getStatus()) ? violationBo.getStatus() : "1"); + setFieldValue(disciplineRecordBo, "violationSource", "1"); // 1-宿舍卫生检查 + + // 设置附件信息(如果有) + if (StringUtils.isNotBlank(violationBo.getAttachmentOssIds())) { + setFieldValue(disciplineRecordBo, "attachmentOssIds", violationBo.getAttachmentOssIds()); + } + + log.info("创建学生违纪记录Bo对象成功,学生ID: {}, 违纪类型: {}", + violationBo.getStudentId(), violationBo.getDisciplineTypeName()); + + return disciplineRecordBo; + + } catch (Exception e) { + log.error("创建学生违纪记录Bo对象失败: {}", e.getMessage(), e); + return null; + } + } + + /** + * 通过反射设置对象字段值 + * + * @param target 目标对象 + * @param fieldName 字段名 + * @param value 字段值 + */ + private void setFieldValue(Object target, String fieldName, Object value) { + try { + if (value == null) { + return; + } + + var field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } catch (Exception e) { + log.warn("设置字段值失败,字段名: {}, 值: {}, 错误: {}", fieldName, value, e.getMessage()); + } + } + + /** + * 获取学生详细信息(包含学院、专业、班级ID) + * + * @param studentId 学生ID + * @return 学生详细信息Map + */ + private Map getStudentDetailInfo(Long studentId) { + try { + var studentInfo = xgStudentMsgService.queryById(studentId); + if (studentInfo != null) { + Map detailMap = new HashMap<>(); + + // 修复:安全地将String类型的ID转换为Long类型 + detailMap.put("collegeId", parseStringToLong(studentInfo.getCollegeId())); + detailMap.put("collegeName", studentInfo.getCollegeName()); + detailMap.put("majorId", parseStringToLong(studentInfo.getMajorId())); + detailMap.put("majorName", studentInfo.getMajorName()); + detailMap.put("classId", parseStringToLong(studentInfo.getClassId())); + detailMap.put("className", studentInfo.getClassName()); + + log.debug("获取学生详细信息成功,学生ID: {}, 学院ID: {}, 专业ID: {}, 班级ID: {}", + studentId, detailMap.get("collegeId"), detailMap.get("majorId"), detailMap.get("classId")); + + return detailMap; + } + } catch (Exception e) { + log.warn("查询学生详细信息失败,学生ID: {}", studentId, e); + } + return null; + } + + /** + * 安全地将String类型转换为Long类型 + * + * @param str 字符串值 + * @return Long值,转换失败时返回null + */ + private Long parseStringToLong(String str) { + if (StringUtils.isBlank(str)) { + return null; + } + try { + return Long.valueOf(str.trim()); + } catch (NumberFormatException e) { + log.warn("字符串转Long失败,原值: {}", str); + return null; + } + } + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgHygieneCheckViolationServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgHygieneCheckViolationServiceImpl.java new file mode 100644 index 00000000..8185fb44 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgHygieneCheckViolationServiceImpl.java @@ -0,0 +1,180 @@ +package org.dromara.xg.service.impl; + +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.dromara.xg.domain.bo.XgHygieneCheckViolationBo; +import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo; +import org.dromara.xg.domain.XgHygieneCheckViolation; +import org.dromara.xg.mapper.XgHygieneCheckViolationMapper; +import org.dromara.xg.service.IXgHygieneCheckViolationService; + +import java.util.List; +import java.util.Collection; + +/** + * 宿舍卫生检查违纪记录Service业务层处理 + * + * @author zlwu + * @date 2025-01-29 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class XgHygieneCheckViolationServiceImpl implements IXgHygieneCheckViolationService { + + private final XgHygieneCheckViolationMapper baseMapper; + + /** + * 查询宿舍卫生检查违纪记录 + * + * @param id 主键 + * @return 宿舍卫生检查违纪记录 + */ + @Override + public XgHygieneCheckViolationVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + /** + * 分页查询宿舍卫生检查违纪记录列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 宿舍卫生检查违纪记录分页列表 + */ + @Override + public TableDataInfo queryPageList(XgHygieneCheckViolationBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的宿舍卫生检查违纪记录列表 + * + * @param bo 查询条件 + * @return 宿舍卫生检查违纪记录列表 + */ + @Override + public List queryList(XgHygieneCheckViolationBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(XgHygieneCheckViolationBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getHygieneCheckId() != null, XgHygieneCheckViolation::getHygieneCheckId, bo.getHygieneCheckId()); + lqw.eq(bo.getStudentId() != null, XgHygieneCheckViolation::getStudentId, bo.getStudentId()); + lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), XgHygieneCheckViolation::getStudentNo, bo.getStudentNo()); + lqw.like(StringUtils.isNotBlank(bo.getStudentName()), XgHygieneCheckViolation::getStudentName, + bo.getStudentName()); + lqw.eq(bo.getDisciplineTypeId() != null, XgHygieneCheckViolation::getDisciplineTypeId, + bo.getDisciplineTypeId()); + lqw.like(StringUtils.isNotBlank(bo.getDisciplineTypeName()), XgHygieneCheckViolation::getDisciplineTypeName, + bo.getDisciplineTypeName()); + lqw.eq(StringUtils.isNotBlank(bo.getViolationSource()), XgHygieneCheckViolation::getViolationSource, + bo.getViolationSource()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XgHygieneCheckViolation::getStatus, bo.getStatus()); + return lqw; + } + + /** + * 新增宿舍卫生检查违纪记录 + * + * @param bo 宿舍卫生检查违纪记录 + * @return 是否新增成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean insertByBo(XgHygieneCheckViolationBo bo) { + XgHygieneCheckViolation add = MapstructUtils.convert(bo, XgHygieneCheckViolation.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改宿舍卫生检查违纪记录 + * + * @param bo 宿舍卫生检查违纪记录 + * @return 是否修改成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updateByBo(XgHygieneCheckViolationBo bo) { + XgHygieneCheckViolation update = MapstructUtils.convert(bo, XgHygieneCheckViolation.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + * + * @param entity 实体类数据 + */ + private void validEntityBeforeSave(XgHygieneCheckViolation entity) { + // TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除宿舍卫生检查违纪记录信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 批量新增宿舍卫生检查违纪记录 + * + * @param bos 违纪记录列表 + * @return 是否新增成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean insertBatch(List bos) { + if (bos == null || bos.isEmpty()) { + return true; + } + + List entities = MapstructUtils.convert(bos, XgHygieneCheckViolation.class); + for (XgHygieneCheckViolation entity : entities) { + validEntityBeforeSave(entity); + } + + return baseMapper.insertBatch(entities); + } + + /** + * 根据卫生检查ID查询违纪记录列表 + * + * @param hygieneCheckId 卫生检查ID + * @return 违纪记录列表 + */ + @Override + public List queryByHygieneCheckId(Long hygieneCheckId) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(XgHygieneCheckViolation::getHygieneCheckId, hygieneCheckId); + return baseMapper.selectVoList(lqw); + } +} diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineTypeController.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineTypeController.java index 4a345895..d89f777b 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineTypeController.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/controller/XskhDisciplineTypeController.java @@ -63,8 +63,7 @@ public class XskhDisciplineTypeController extends BaseController { */ @SaCheckPermission("disciplineType:disciplineType:query") @GetMapping("/{id}") - public R getInfo(@NotNull(message = "主键不能为空") - @PathVariable Long id) { + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { return R.ok(xskhDisciplineTypeService.queryById(id)); } @@ -98,8 +97,27 @@ public class XskhDisciplineTypeController extends BaseController { @SaCheckPermission("disciplineType:disciplineType:remove") @Log(title = "学生违纪处分类型", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") - public R remove(@NotEmpty(message = "主键不能为空") - @PathVariable Long[] ids) { + public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { return toAjax(xskhDisciplineTypeService.deleteWithValidByIds(List.of(ids), true)); } + + /** + * 根据区域类型查询学生违纪处分类型列表 + * + * @param areaType 区域类型(1-宿舍区违纪 2-教学区违纪) + * @param status 状态(0-正常 1-停用) + * @return 学生违纪处分类型列表 + */ + @SaCheckPermission("disciplineType:disciplineType:list") + @GetMapping("/listByAreaType") + public R> listByAreaType(@RequestParam String areaType, + @RequestParam(required = false) String status) { + XskhDisciplineTypeBo bo = new XskhDisciplineTypeBo(); + bo.setAreaType(areaType); + if (status != null) { + bo.setStatus(status); + } + List list = xskhDisciplineTypeService.queryList(bo); + return R.ok(list); + } } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhDisciplineType.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhDisciplineType.java index 62342129..c27e10dd 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhDisciplineType.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhDisciplineType.java @@ -42,6 +42,21 @@ public class XskhDisciplineType extends TenantEntity { */ private String typeCategory; + /** + * 区域类型(1-宿舍区违纪 2-教学区违纪) + */ + private String areaType; + + /** + * 最低扣分 + */ + private java.math.BigDecimal minScore; + + /** + * 最高扣分 + */ + private java.math.BigDecimal maxScore; + /** * 类型说明 */ @@ -63,5 +78,4 @@ public class XskhDisciplineType extends TenantEntity { @TableLogic(value = "0", delval = "1") private Long deleted; - } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhDisciplineTypeBo.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhDisciplineTypeBo.java index 763f1ee2..5c862cef 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhDisciplineTypeBo.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhDisciplineTypeBo.java @@ -8,6 +8,7 @@ import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; import lombok.EqualsAndHashCode; import jakarta.validation.constraints.*; +import java.math.BigDecimal; /** * 学生违纪处分类型业务对象 xskh_discipline_type @@ -44,6 +45,28 @@ public class XskhDisciplineTypeBo extends BaseEntity { @NotBlank(message = "类型分类(1-违纪类型 2-处分类型)不能为空", groups = { AddGroup.class, EditGroup.class }) private String typeCategory; + /** + * 区域类型(1-宿舍区违纪 2-教学区违纪) + */ + @NotBlank(message = "区域类型不能为空", groups = { AddGroup.class, EditGroup.class }) + private String areaType; + + /** + * 最低扣分 + */ + @NotNull(message = "最低扣分不能为空", groups = { AddGroup.class, EditGroup.class }) + @DecimalMin(value = "0.00", message = "最低扣分不能小于0", groups = { AddGroup.class, EditGroup.class }) + @DecimalMax(value = "100.00", message = "最低扣分不能大于100", groups = { AddGroup.class, EditGroup.class }) + private java.math.BigDecimal minScore; + + /** + * 最高扣分 + */ + @NotNull(message = "最高扣分不能为空", groups = { AddGroup.class, EditGroup.class }) + @DecimalMin(value = "0.00", message = "最高扣分不能小于0", groups = { AddGroup.class, EditGroup.class }) + @DecimalMax(value = "100.00", message = "最高扣分不能大于100", groups = { AddGroup.class, EditGroup.class }) + private java.math.BigDecimal maxScore; + /** * 类型说明 */ @@ -64,5 +87,4 @@ public class XskhDisciplineTypeBo extends BaseEntity { */ private Long deleted; - } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhDisciplineTypeVo.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhDisciplineTypeVo.java index 426425f9..a3bb97cb 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhDisciplineTypeVo.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhDisciplineTypeVo.java @@ -12,8 +12,6 @@ import java.io.Serial; import java.io.Serializable; import java.util.Date; - - /** * 学生违纪处分类型视图对象 xskh_discipline_type * @@ -53,6 +51,25 @@ public class XskhDisciplineTypeVo implements Serializable { @ExcelDictFormat(dictType = "student_discipline_category") private String typeCategory; + /** + * 区域类型(1-宿舍区违纪 2-教学区违纪) + */ + @ExcelProperty(value = "区域类型", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "discipline_area_type") + private String areaType; + + /** + * 最低扣分 + */ + @ExcelProperty(value = "最低扣分") + private java.math.BigDecimal minScore; + + /** + * 最高扣分 + */ + @ExcelProperty(value = "最高扣分") + private java.math.BigDecimal maxScore; + /** * 类型说明 */ @@ -78,5 +95,4 @@ public class XskhDisciplineTypeVo implements Serializable { @ExcelProperty(value = "删除标志(0-未删除 1-已删除)") private Long deleted; - } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineTypeServiceImpl.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineTypeServiceImpl.java index fa2bf457..5b86731d 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineTypeServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhDisciplineTypeServiceImpl.java @@ -40,7 +40,7 @@ public class XskhDisciplineTypeServiceImpl implements IXskhDisciplineTypeService * @return 学生违纪处分类型 */ @Override - public XskhDisciplineTypeVo queryById(Long id){ + public XskhDisciplineTypeVo queryById(Long id) { return baseMapper.selectVoById(id); } @@ -73,10 +73,11 @@ public class XskhDisciplineTypeServiceImpl implements IXskhDisciplineTypeService private LambdaQueryWrapper buildQueryWrapper(XskhDisciplineTypeBo bo) { Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.orderByAsc(XskhDisciplineType::getId); + lqw.orderByAsc(XskhDisciplineType::getSortOrder, XskhDisciplineType::getId); lqw.eq(StringUtils.isNotBlank(bo.getTypeCode()), XskhDisciplineType::getTypeCode, bo.getTypeCode()); lqw.like(StringUtils.isNotBlank(bo.getTypeName()), XskhDisciplineType::getTypeName, bo.getTypeName()); lqw.eq(StringUtils.isNotBlank(bo.getTypeCategory()), XskhDisciplineType::getTypeCategory, bo.getTypeCategory()); + lqw.eq(StringUtils.isNotBlank(bo.getAreaType()), XskhDisciplineType::getAreaType, bo.getAreaType()); lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XskhDisciplineType::getStatus, bo.getStatus()); lqw.eq(bo.getSortOrder() != null, XskhDisciplineType::getSortOrder, bo.getSortOrder()); lqw.eq(bo.getDeleted() != null, XskhDisciplineType::getDeleted, bo.getDeleted()); @@ -91,7 +92,16 @@ public class XskhDisciplineTypeServiceImpl implements IXskhDisciplineTypeService */ @Override public Boolean insertByBo(XskhDisciplineTypeBo bo) { + // 添加调试日志 + log.info("接收到的BO参数: typeCode={}, typeName={}, areaType={}, minScore={}, maxScore={}", + bo.getTypeCode(), bo.getTypeName(), bo.getAreaType(), bo.getMinScore(), bo.getMaxScore()); + XskhDisciplineType add = MapstructUtils.convert(bo, XskhDisciplineType.class); + + // 添加映射后的调试日志 + log.info("映射后的Entity参数: typeCode={}, typeName={}, areaType={}, minScore={}, maxScore={}", + add.getTypeCode(), add.getTypeName(), add.getAreaType(), add.getMinScore(), add.getMaxScore()); + validEntityBeforeSave(add); boolean flag = baseMapper.insert(add) > 0; if (flag) { @@ -116,8 +126,26 @@ public class XskhDisciplineTypeServiceImpl implements IXskhDisciplineTypeService /** * 保存前的数据校验 */ - private void validEntityBeforeSave(XskhDisciplineType entity){ - //TODO 做一些数据校验,如唯一约束 + private void validEntityBeforeSave(XskhDisciplineType entity) { + // 校验类型编码唯一性 + if (StringUtils.isNotBlank(entity.getTypeCode())) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(XskhDisciplineType::getTypeCode, entity.getTypeCode()); + if (entity.getId() != null) { + lqw.ne(XskhDisciplineType::getId, entity.getId()); + } + boolean exists = baseMapper.exists(lqw); + if (exists) { + throw new RuntimeException("类型编码已存在:" + entity.getTypeCode()); + } + } + + // 校验扣分范围 + if (entity.getMinScore() != null && entity.getMaxScore() != null) { + if (entity.getMinScore().compareTo(entity.getMaxScore()) > 0) { + throw new RuntimeException("最低扣分不能大于最高扣分"); + } + } } /** @@ -129,8 +157,8 @@ public class XskhDisciplineTypeServiceImpl implements IXskhDisciplineTypeService */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + if (isValid) { + // TODO 做一些业务上的校验,判断是否需要校验 } return baseMapper.deleteByIds(ids) > 0; } diff --git a/docs/设计文档/database/hzdyjs_xg0820.sql b/docs/设计文档/database/hzdyjs_xg0820.sql index f8fa8b63..05034694 100644 --- a/docs/设计文档/database/hzdyjs_xg0820.sql +++ b/docs/设计文档/database/hzdyjs_xg0820.sql @@ -4381,6 +4381,8 @@ CREATE TABLE `xg_dorm_hygiene_check` ( `attachment_ids` text COMMENT '附件OSS ID列表,逗号分隔', `check_summary` text COMMENT '检查总结', `improvement_suggestions` text COMMENT '整改建议', + `discipline_type_id` bigint(20) DEFAULT NULL COMMENT '违纪类型ID,关联xskh_discipline_type.id', + `discipline_type_name` varchar(100) DEFAULT NULL COMMENT '违纪类型名称', `check_remarks` varchar(500) DEFAULT NULL COMMENT '检查备注', PRIMARY KEY (`id`) USING BTREE, KEY `idx_tenant_id` (`tenant_id`), @@ -4394,6 +4396,7 @@ CREATE TABLE `xg_dorm_hygiene_check` ( KEY `idx_hygiene_grade` (`hygiene_grade`), KEY `idx_check_type` (`check_type`), KEY `idx_create_time` (`create_time`), + KEY `idx_discipline_type_id` (`discipline_type_id`), KEY `idx_tenant_deleted` (`tenant_id`,`deleted`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍卫生检查表'; diff --git a/docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql b/docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql index 92f54d1b..dc094e82 100644 --- a/docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql +++ b/docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql @@ -12,6 +12,9 @@ CREATE TABLE `xskh_discipline_type` ( `type_name` varchar(100) NOT NULL COMMENT '类型名称', `type_category` char(1) NOT NULL COMMENT '类型分类(1-违纪类型 2-处分类型)', `remark` varchar(500) DEFAULT NULL COMMENT '类型说明', + `area_type` char(1) DEFAULT '1' COMMENT '区域类型(1-宿舍区违纪 2-教学区违纪)' , + `min_score` decimal(5,2) DEFAULT 0.00 COMMENT '最低扣分', + `max_score` decimal(5,2) DEFAULT 100.00 COMMENT '最高扣分', `status` char(1) DEFAULT '0' COMMENT '状态(0-正常 1-停用)', `sort_order` int(4) DEFAULT 0 COMMENT '排序号', `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', @@ -27,6 +30,39 @@ CREATE TABLE `xskh_discipline_type` ( KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='学生违纪处分类型表'; +-- 1. 宿舍卫生检查违纪关联表(关联宿舍检查与违纪记录) +DROP TABLE IF EXISTS `xg_hygiene_check_violation`; +CREATE TABLE `xg_hygiene_check_violation` ( + `id` bigint(20) NOT NULL COMMENT '主键ID', + `tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号', + `hygiene_check_id` bigint(20) NOT NULL COMMENT '宿舍卫生检查ID,关联xg_dorm_hygiene_check.id', + `discipline_record_id` bigint(20) NOT NULL COMMENT '违纪记录ID,关联xskh_discipline_record.id', + `student_id` bigint(20) NOT NULL COMMENT '学生ID,关联xg_student_msg.id', + `student_no` varchar(20) NOT NULL COMMENT '学号', + `student_name` varchar(50) NOT NULL COMMENT '学生姓名', + `discipline_type_id` bigint(20) NOT NULL COMMENT '违纪类型ID,关联xskh_discipline_type.id', + `discipline_type_name` varchar(100) NOT NULL COMMENT '违纪类型名称', + `violation_source` char(1) DEFAULT '1' COMMENT '违纪来源(1宿舍卫生检查 2其他)', + `status` char(1) DEFAULT '1' COMMENT '状态(1生效 2已撤销)', + `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', + `deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0-未删除 1-已删除)', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_tenant_id` (`tenant_id`), + KEY `idx_hygiene_check_id` (`hygiene_check_id`), + KEY `idx_discipline_record_id` (`discipline_record_id`), + KEY `idx_student_id` (`student_id`), + KEY `idx_student_no` (`student_no`), + KEY `idx_discipline_type_id` (`discipline_type_id`), + KEY `idx_violation_source` (`violation_source`), + KEY `idx_status` (`status`), + KEY `idx_deleted` (`deleted`), + KEY `idx_tenant_deleted` (`tenant_id`,`deleted`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍卫生检查违纪关联表'; + -- 2. 学生违纪处分记录表 DROP TABLE IF EXISTS `xskh_discipline_record`; CREATE TABLE `xskh_discipline_record` ( @@ -47,6 +83,7 @@ CREATE TABLE `xskh_discipline_record` ( `discipline_content` varchar(500) DEFAULT NULL COMMENT '违纪处分内容', `score_deduction` decimal(5,2) DEFAULT 0.00 COMMENT '扣分分值', `status` char(1) DEFAULT '1' COMMENT '状态(1-生效 2-已撤销)', + `violation_source` char(1) DEFAULT '2' COMMENT '违纪来源(1-宿舍卫生检查 2-教学区违纪)', `attachment_oss_ids` varchar(1000) DEFAULT NULL COMMENT '附件OSS文件ID,多个用逗号分隔(申请材料、证明文件等)', `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', diff --git a/docs/问题解决方案/学生考核指标indicator_type_name字段问题修复.md b/docs/问题解决方案/学生考核指标indicator_type_name字段问题修复.md deleted file mode 100644 index 40017513..00000000 --- a/docs/问题解决方案/学生考核指标indicator_type_name字段问题修复.md +++ /dev/null @@ -1,211 +0,0 @@ -# 学生考核指标indicator_type_name字段问题修复 - -## 问题描述 - -在新增学生考核指标时,出现数据库插入错误: - -``` -Field 'indicator_type_name' doesn't have a default value -``` - -## 问题分析 - -### 1. 根本原因 -- 数据库表 `xskh_assessment_indicator` 中的 `indicator_type_name` 字段设置为 `NOT NULL` -- 前端传递的数据中只有 `indicatorTypeId`,没有 `indicatorTypeName` -- 后端Service层在插入数据时没有自动设置 `indicatorTypeName` 字段的值 - -### 2. 字段映射问题 -- 前端使用 `indicatorDescription` 字段,但后端数据库字段是 `description` -- 为了保持前后端代码和数据库字段的一致性,统一使用 `description` 字段名 - -## 解决方案 - -### 1. 后端Service层修复 - -**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentIndicatorServiceImpl.java` - -#### 1.1 添加依赖注入 -```java -private final XskhAssessmentIndicatorTypeMapper indicatorTypeMapper; -``` - -#### 1.2 修改insertByBo方法 -```java -@Override -@Transactional(rollbackFor = Exception.class) -public Boolean insertByBo(XskhAssessmentIndicatorBo bo) { - XskhAssessmentIndicator add = MapstructUtils.convert(bo, XskhAssessmentIndicator.class); - - // 根据指标类型ID设置指标类型名称 - if (add.getIndicatorTypeId() != null) { - XskhAssessmentIndicatorType indicatorType = indicatorTypeMapper.selectById(add.getIndicatorTypeId()); - if (indicatorType != null) { - add.setIndicatorTypeName(indicatorType.getTypeName()); - } - } - - validEntityBeforeSave(add); - boolean flag = baseMapper.insert(add) > 0; - if (flag) { - bo.setId(add.getId()); - // 保存关联关系 - saveIndicatorRelations(add.getId(), bo.getDisciplineTypeIds(), bo.getRewardTypeIds()); - } - return flag; -} -``` - -#### 1.3 修改updateByBo方法 -```java -@Override -@Transactional(rollbackFor = Exception.class) -public Boolean updateByBo(XskhAssessmentIndicatorBo bo) { - XskhAssessmentIndicator update = MapstructUtils.convert(bo, XskhAssessmentIndicator.class); - - // 根据指标类型ID设置指标类型名称 - if (update.getIndicatorTypeId() != null) { - XskhAssessmentIndicatorType indicatorType = indicatorTypeMapper.selectById(update.getIndicatorTypeId()); - if (indicatorType != null) { - update.setIndicatorTypeName(indicatorType.getTypeName()); - } - } - - validEntityBeforeSave(update); - boolean flag = baseMapper.updateById(update) > 0; - if (flag) { - // 更新关联关系 - saveIndicatorRelations(update.getId(), bo.getDisciplineTypeIds(), bo.getRewardTypeIds()); - } - return flag; -} -``` - -### 2. 字段映射修复 - -为了保持前后端代码和数据库字段的一致性,统一使用 `description` 字段名: - -#### 2.1 前端类型定义修复 -**文件**: `web/src/api/xskh/assessmentIndicator/types.ts` - -将所有接口中的 `indicatorDescription` 字段改为 `description`: -- `XskhAssessmentIndicatorBo` -- `XskhAssessmentIndicatorVo` -- `XskhAssessmentIndicatorForm` - -#### 2.2 前端表单修复 -**文件**: `web/src/views/xskh/assessmentIndicator/AssessmentIndicatorForm.vue` - -- 表单字段名:`indicatorDescription` → `description` -- 表单验证规则:`indicatorDescription` → `description` -- 初始数据:`indicatorDescription` → `description` - -### 3. Mapper XML修复 - -**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentIndicatorMapper.xml` - -添加缺失的 `createDept` 字段映射: - -```xml - -``` - -### 4. 前端CSS修复 - -**文件**: `web/src/views/xskh/assessmentIndicator/AssessmentIndicatorForm.vue` - -修复CSS语法错误: - -```css -/* 固定多选框高度,防止动态变化 */ -.fixed-height-select :deep(.el-select__tags) { - max-height: 32px; - overflow: hidden; -} - -.fixed-height-select :deep(.el-select__tags-text) { - max-width: 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.fixed-height-select :deep(.el-select__collapse-tags) { - max-width: 100%; -} -``` - -## 测试验证 - -### 1. 创建测试脚本 -**文件**: `test/学生考核指标API测试脚本.py` - -包含以下测试用例: -- 登录认证 -- 新增学生考核指标 -- 查询指标列表 -- 查询指标详情 -- 修改指标 - -### 2. 测试数据 -```json -{ - "indicatorCode": "zbbm01", - "indicatorName": "学生指标01", - "description": "学生指标01指标说明", - "indicatorTypeId": "1958131304973221889", - "maxScore": 2.1, - "status": "0", - "sortOrder": 0, - "disciplineTypeIds": [ - "1958135628856049666", - "1958135753561096193" - ], - "rewardTypeIds": [ - "1958135350199074817", - "1958135400207761409" - ] -} -``` - -## 修复效果 - -### 1. 问题解决 -- ✅ 解决了 `indicator_type_name` 字段缺失默认值的问题 -- ✅ 统一了前后端字段命名,使用 `description` 字段 -- ✅ 修复了CSS语法错误 -- ✅ 完善了Mapper XML字段映射 - -### 2. 功能增强 -- ✅ 自动根据指标类型ID设置指标类型名称 -- ✅ 统一前后端字段命名,提高代码一致性 -- ✅ 完善了错误处理和日志记录 - -### 3. 代码质量 -- ✅ 遵循项目开发规范 -- ✅ 添加了完整的注释说明 -- ✅ 提供了测试脚本验证功能 - -## 注意事项 - -1. **数据库字段约束**: 确保数据库表设计合理,避免不必要的NOT NULL约束 -2. **字段命名一致性**: 前后端字段名应保持一致,避免不必要的映射转换 -3. **自动填充**: 对于关联字段,应该在Service层自动填充,而不是依赖前端传递 -4. **测试验证**: 每次修复后都应该进行完整的测试验证 - -## 相关文件 - -- `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentIndicatorServiceImpl.java` -- `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhAssessmentIndicatorBo.java` -- `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhAssessmentIndicator.java` -- `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentIndicatorVo.java` -- `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentIndicatorMapper.xml` -- `web/src/api/xskh/assessmentIndicator/types.ts` -- `web/src/views/xskh/assessmentIndicator/AssessmentIndicatorForm.vue` -- `test/学生考核指标API测试脚本.py` - ---- - -**修复时间**: 2025-01-03 -**修复人员**: AI Assistant -**验证状态**: 待验证 \ No newline at end of file diff --git a/docs/问题解决方案/学生考核指标类型createDept字段问题修复.md b/docs/问题解决方案/学生考核指标类型createDept字段问题修复.md deleted file mode 100644 index c27ed59a..00000000 --- a/docs/问题解决方案/学生考核指标类型createDept字段问题修复.md +++ /dev/null @@ -1,162 +0,0 @@ -# 学生考核指标类型createDept字段问题修复 - -## 问题描述 - -在 `XskhAssessmentIndicatorTypeController.java` 类的 `add` 方法执行后,数据库中查看数据时发现 `create_dept` 字段(创建人的部门ID)没有插入数据,显示为 NULL。 - -## 问题分析 - -通过代码分析,发现问题的根本原因是: - -### 1. VO类缺少createDept字段 -**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentIndicatorTypeVo.java` - -**问题**: VO类中缺少 `createDept` 字段定义,导致字段映射不完整。 - -### 2. BO类缺少createDept字段 -**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhAssessmentIndicatorTypeBo.java` - -**问题**: BO类中缺少 `createDept` 字段定义,影响数据传输。 - -### 3. Mapper XML缺少字段映射 -**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentIndicatorTypeMapper.xml` - -**问题**: resultMap中缺少 `createDept` 字段的映射配置,导致数据库字段无法正确映射到实体属性。 - -## 解决方案 - -### 1. 修复VO类 -在 `XskhAssessmentIndicatorTypeVo.java` 中添加 `createDept` 字段: - -```java -/** - * 创建部门 - */ -private Long createDept; -``` - -### 2. 修复BO类 -在 `XskhAssessmentIndicatorTypeBo.java` 中添加基础字段: - -```java -/** - * 创建部门 - */ -private Long createDept; - -/** - * 创建者 - */ -private Long createBy; - -/** - * 创建时间 - */ -private Date createTime; - -/** - * 更新者 - */ -private Long updateBy; - -/** - * 更新时间 - */ -private Date updateTime; -``` - -### 3. 修复Mapper XML -在 `XskhAssessmentIndicatorTypeMapper.xml` 的两个resultMap中添加字段映射: - -```xml - -``` - -### 4. 增强Service实现 -在 `XskhAssessmentIndicatorTypeServiceImpl.java` 中添加调试日志和手动设置逻辑: - -```java -// 调试日志:检查当前登录用户信息 -try { - Long userId = LoginHelper.getUserId(); - Long deptId = LoginHelper.getDeptId(); - log.info("🔍 调试信息 - 当前登录用户ID: {}, 部门ID: {}", userId, deptId); - - // 手动设置createDept字段,确保有值 - if (add.getCreateDept() == null && deptId != null) { - add.setCreateDept(deptId); - log.info("✅ 手动设置createDept字段: {}", deptId); - } else { - log.info("⚠️ createDept字段状态: {}", add.getCreateDept()); - } -} catch (Exception e) { - log.warn("⚠️ 获取登录用户信息失败: {}", e.getMessage()); -} -``` - -## 技术原理 - -### MyBatis-Plus字段填充机制 -系统使用 MyBatis-Plus 的 `@TableField(fill = FieldFill.INSERT)` 注解来自动填充字段: - -```java -@TableField(fill = FieldFill.INSERT) -private Long createDept; -``` - -### 字段填充处理器 -`InjectionMetaObjectHandler` 负责在插入时自动填充字段: - -```java -baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId())); -``` - -### 字段映射关系 -- 数据库字段: `create_dept` -- 实体属性: `createDept` -- 映射配置: `` - -## 验证方法 - -### 1. 创建测试脚本 -创建 `test/学生考核指标类型API测试脚本.py` 来验证修复效果。 - -### 2. 测试步骤 -1. 登录系统获取token -2. 调用新增API创建测试数据 -3. 调用查询API检查createDept字段 -4. 验证字段是否正确填充 - -### 3. 预期结果 -- createDept字段应该包含当前登录用户的部门ID -- 不再显示为NULL - -## 预防措施 - -### 1. 规范检查 -- 所有VO类必须包含四个基础字段:`createBy`、`createTime`、`updateBy`、`updateTime` -- 新增 `createDept` 字段作为第五个基础字段 -- Mapper XML必须包含完整的字段映射 - -### 2. 代码模板 -创建标准的代码模板,确保新开发的模块不会遗漏基础字段。 - -### 3. 自动化测试 -建立自动化测试机制,定期检查字段映射的完整性。 - -## 相关文件 - -### 修改的文件 -1. `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentIndicatorTypeVo.java` -2. `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhAssessmentIndicatorTypeBo.java` -3. `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentIndicatorTypeMapper.xml` -4. `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentIndicatorTypeServiceImpl.java` - -### 新增的文件 -1. `test/学生考核指标类型API测试脚本.py` - -## 总结 - -通过修复VO类、BO类、Mapper XML的字段映射问题,并增强Service实现的调试功能,成功解决了 `create_dept` 字段不填充的问题。这个问题的根本原因是字段映射不完整,导致MyBatis-Plus的自动填充机制无法正常工作。 - -修复后的系统能够正确记录创建人的部门信息,为后续的数据分析和权限控制提供了基础支持。 \ No newline at end of file diff --git a/docs/问题解决方案/学生考核时间格式修复报告.md b/docs/问题解决方案/学生考核时间格式修复报告.md deleted file mode 100644 index 660475f8..00000000 --- a/docs/问题解决方案/学生考核时间格式修复报告.md +++ /dev/null @@ -1,218 +0,0 @@ -# 学生考核时间格式修复报告 - -## 📋 问题描述 - -### 问题现象 -在学生考核管理系统中,点击保存按钮调用接口 `http://localhost/dev-api/assessment/approval/approve` 时,返回的数据中 `bonusApprovalList` 和 `bonusList` 的时间字段显示为不标准的格式: - -```json -{ - "bonusApprovalList": [ - { - "createTime": "2025/8/21 上午10:52", - "updateTime": "2025/8/21 上午10:52" - } - ] -} -``` - -### 问题分析 -经过代码分析,发现问题出现在以下两个VO类文件中: - -1. **XskhAssessmentBonusApprovalVo.java** - 附加分审核VO类 -2. **XskhAssessmentSelfScoreVo.java** - 自评得分VO类 - -这两个文件中的 `createTime` 和 `updateTime` 字段被错误地定义为 `String` 类型,但使用了 `@JsonFormat` 注解,导致时间格式不正确。 - -## 🔧 修复方案 - -### 修复文件列表 - -#### 1. XskhAssessmentBonusApprovalVo.java -**文件路径**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentBonusApprovalVo.java` - -**修复内容**: -```java -// 修复前 -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private String createTime; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private String updateTime; - -// 修复后 -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private Date createTime; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private Date updateTime; -``` - -#### 2. XskhAssessmentSelfScoreVo.java -**文件路径**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentSelfScoreVo.java` - -**修复内容**: -```java -// 修复前 -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private String createTime; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private String updateTime; - -// 修复后 -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private Date createTime; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private Date updateTime; -``` - -### 修复原理 - -1. **类型一致性**: 将时间字段类型从 `String` 改为 `Date`,与实体类保持一致 -2. **JSON格式化**: 使用 `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")` 注解确保时间以标准格式输出 -3. **遵循项目规范**: 符合项目后端开发规范中关于VO类基础字段的要求 - -## 📊 修复效果 - -### 修复前 -```json -{ - "bonusApprovalList": [ - { - "createTime": "2025/8/21 上午10:52", - "updateTime": "2025/8/21 上午10:52" - } - ] -} -``` - -### 修复后 -```json -{ - "bonusApprovalList": [ - { - "createTime": "2025-08-21 10:52:40", - "updateTime": "2025-08-21 10:52:40" - } - ] -} -``` - -## 🧪 验证方法 - -### 1. 代码验证 -- ✅ 检查VO类文件中的时间字段类型 -- ✅ 确认 `@JsonFormat` 注解正确使用 -- ✅ 验证字段类型与实体类一致 - -### 2. 功能验证 -创建了验证脚本 `test/学生考核时间格式修复验证.py`,用于: -- 登录系统获取token -- 调用考核审核接口 -- 检查返回数据中的时间格式 -- 验证时间字段是否为标准格式 - -### 3. 测试步骤 -```bash -# 1. 启动后端服务 -cd backend -mvn spring-boot:run - -# 2. 运行验证脚本 -cd test -python 学生考核时间格式修复验证.py -``` - -## 📚 相关规范 - -### 项目后端开发规范 -根据 `.cursor/cursorrules-backend.md` 中的规范: - -#### VO类基础字段规范(强制性) -```java -/** - * 创建者 - */ -private Long createBy; - -/** - * 创建时间 - */ -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private Date createTime; - -/** - * 更新者 - */ -private Long updateBy; - -/** - * 更新时间 - */ -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private Date updateTime; -``` - -#### 字段使用注解规范 -- 时间字段必须使用JSON格式化注解 -- 字段类型必须统一:createBy/updateBy 为 Long,createTime/updateTime 为 Date -- 时间字段必须使用 `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")` 注解 - -## 🔍 预防措施 - -### 1. 代码审查 -- 新增VO类时必须检查基础字段类型 -- 确保时间字段使用正确的类型和注解 - -### 2. 自动化检查 -- 在CI/CD流程中添加代码规范检查 -- 使用静态代码分析工具检查字段类型 - -### 3. 文档更新 -- 更新项目开发规范文档 -- 在团队中推广VO类开发标准 - -## 📝 总结 - -本次修复解决了学生考核管理系统中时间格式显示不正确的问题,主要修复了: - -1. **2个VO类文件**中的时间字段类型错误 -2. **4个时间字段**的类型从String改为Date -3. **确保JSON格式化注解**正确使用 -4. **修复Service实现类**中的类型转换错误 - -### 🔧 修复详情 - -#### 第一阶段:VO类修复 -- `XskhAssessmentBonusApprovalVo.java` - 修复createTime和updateTime字段类型 -- `XskhAssessmentSelfScoreVo.java` - 修复createTime和updateTime字段类型 - -#### 第二阶段:Service实现类修复(优化方案) -- `XskhAssessmentApplicationApprovalServiceImpl.java` - 优化时间字段处理逻辑 -- **根本解决方案**:修改SQL查询使用ResultMap而不是Map类型 -- **移除复杂的手动类型转换**:让MyBatis自动处理类型映射 -- **简化代码**:从50+行手动转换代码简化为1行直接赋值 - -### 🚨 编译错误解决 - -在修复VO类后,编译时出现了类型转换错误: -``` -不兼容的类型: java.lang.String无法转换为java.util.Date -``` - -**解决方案**: -1. **修改SQL查询**:使用ResultMap替代resultType="java.util.Map" -2. **添加ResultMap定义**:`XskhAssessmentSelfScoreWithIndicatorResult` -3. **修改Mapper接口**:返回类型从`List`改为`List` -4. **简化Service代码**:移除复杂的手动类型转换,直接使用MyBatis自动映射 - -修复后,系统返回的时间格式将符合标准格式 `yyyy-MM-dd HH:mm:ss`,提升了数据的一致性和用户体验。 - ---- - -**修复人员**: AI Assistant -**修复时间**: 2025-01-03 -**验证状态**: ✅ 已修复 -**影响范围**: 学生考核管理系统时间显示 \ No newline at end of file diff --git a/docs/问题解决方案/学生考核申请ID精度丢失修复报告.md b/docs/问题解决方案/学生考核申请ID精度丢失修复报告.md deleted file mode 100644 index 300c0c18..00000000 --- a/docs/问题解决方案/学生考核申请ID精度丢失修复报告.md +++ /dev/null @@ -1,246 +0,0 @@ -# 学生考核申请ID精度丢失修复报告 - -## 问题描述 - -### 错误现象 -在学生考核申请界面保存时,ID等字段丢失精度,导致存到数据库都为 `1958180338534527000`,后几位都是 `000`。 - -### 问题分析 -1. **根本原因**: JavaScript 大整数精度丢失问题 - - JavaScript 的 `Number.MAX_SAFE_INTEGER` 为 `9007199254740991` - - 雪花ID通常超过这个范围,导致精度丢失 - - 使用 `parseInt()` 转换大整数字符串时,超出安全范围的数字会被截断 - -2. **问题位置**: `safeConvertId` 函数 - - 原函数使用 `parseInt()` 将字符串ID转换为数字 - - 对于大整数(如雪花ID),转换后精度丢失 - -3. **影响范围**: 所有ID字段 - - `id`: 主键ID - - `studentId`: 学生ID - - `collegeId`: 学院ID - - `majorId`: 专业ID - - `classId`: 班级ID - - `scheduleId`: 考核计划ID - -## 修复方案 - -### 1. 修复 `safeConvertId` 函数 - -**文件**: `web/src/views/xskh/assessmentApplication/AssessmentApplicationForm.vue` - -**修复前**: -```typescript -const safeConvertId = (id: string | number | undefined): number | undefined => { - if (id === undefined || id === null) return undefined; - if (typeof id === 'number') return id; - if (typeof id === 'string') { - // 对于大整数,使用parseInt而不是Number,并检查是否超出安全范围 - const num = parseInt(id, 10); - if (isNaN(num)) return undefined; - // 检查是否超出JavaScript安全整数范围 - if (num > Number.MAX_SAFE_INTEGER) { - console.warn('ID超出JavaScript安全整数范围,可能导致精度丢失:', id); - } - return num; - } - return undefined; -}; -``` - -**修复后**: -```typescript -const safeConvertId = (id: string | number | undefined): string | undefined => { - if (id === undefined || id === null) return undefined; - // 始终将ID转换为字符串,避免JavaScript大整数精度丢失 - return String(id); -}; -``` - -### 2. 修复要点 - -#### 2.1 类型安全 -- **返回类型**: 从 `number | undefined` 改为 `string | undefined` -- **处理逻辑**: 始终使用 `String()` 转换,保持原始精度 -- **兼容性**: 后端API支持 `string | number` 类型,无需修改 - -#### 2.2 精度保持 -- **字符串转换**: 使用 `String()` 而不是 `parseInt()` -- **精度完整**: 保持雪花ID的完整精度 -- **无截断**: 避免大整数被截断为 `000` - -#### 2.3 项目一致性 -- **标准做法**: 与项目中其他模块的处理方式保持一致 -- **最佳实践**: 参考项目中已有的成功案例 - -## 技术原理 - -### JavaScript 大整数精度问题 - -#### 1. 安全整数范围 -```javascript -Number.MAX_SAFE_INTEGER = 9007199254740991 // 2^53 - 1 -Number.MIN_SAFE_INTEGER = -9007199254740991 // -(2^53 - 1) -``` - -#### 2. 雪花ID特点 -- **长度**: 通常为19位数字 -- **范围**: 远超JavaScript安全整数范围 -- **精度**: 需要保持完整精度 - -#### 3. 转换对比 -```javascript -// 错误的转换方式(精度丢失) -const bigId = "1958180338534527123"; -const wrongResult = parseInt(bigId); // 1958180338534527000 (精度丢失) - -// 正确的转换方式(保持精度) -const correctResult = String(bigId); // "1958180338534527123" (完整精度) -``` - -### 后端支持 - -#### 1. API接口支持 -```typescript -// 所有ID参数都支持 string | number 类型 -export function getAssessmentApplication(id: string | number) { - return request({ - url: '/assessment/application/' + id, - method: 'get' - }); -} -``` - -#### 2. 类型定义支持 -```typescript -export interface XskhAssessmentApplicationApprovalBo { - id?: string | number; - studentId?: string | number; - collegeId?: string | number; - majorId?: string | number; - classId?: string | number; - scheduleId?: string | number; - // ... 其他字段 -} -``` - -#### 3. 后端处理 -- **Spring Boot**: 自动处理字符串到Long的转换 -- **MyBatis-Plus**: 支持字符串类型的ID参数 -- **数据库**: bigint类型可以正确处理字符串输入 - -## 验证测试 - -### 1. 功能测试 - -#### 1.1 新增申请测试 -- **测试步骤**: - 1. 打开学生考核申请界面 - 2. 选择考核计划 - 3. 填写申请信息 - 4. 点击保存 -- **预期结果**: ID字段保持完整精度,数据库记录正确 - -#### 1.2 编辑申请测试 -- **测试步骤**: - 1. 打开已有申请记录 - 2. 修改申请信息 - 3. 点击保存 -- **预期结果**: ID字段保持完整精度,更新成功 - -#### 1.3 提交申请测试 -- **测试步骤**: - 1. 打开申请记录 - 2. 点击提交 -- **预期结果**: ID字段保持完整精度,提交成功 - -### 2. 数据验证 - -#### 2.1 前端数据验证 -```javascript -// 验证ID精度 -const testId = "1958180338534527123"; -const convertedId = safeConvertId(testId); -console.log('转换前:', testId); -console.log('转换后:', convertedId); -console.log('精度保持:', testId === convertedId); // 应该为 true -``` - -#### 2.2 后端数据验证 -```sql --- 查询保存的记录,验证ID精度 -SELECT id, student_id, college_id, major_id, class_id, schedule_id -FROM xskh_assessment_application_approval -WHERE id = '1958180338534527123'; -``` - -### 3. 边界测试 - -#### 3.1 大整数测试 -- **测试数据**: 19位雪花ID -- **预期结果**: 完整精度保持 - -#### 3.2 小整数测试 -- **测试数据**: 普通整数ID -- **预期结果**: 正常转换 - -#### 3.3 空值测试 -- **测试数据**: undefined, null -- **预期结果**: 返回 undefined - -## 影响范围 - -### 1. 直接影响 -- ✅ **学生考核申请模块**: 修复完成 -- ✅ **ID精度保持**: 所有ID字段完整精度 -- ✅ **数据一致性**: 前后端数据一致 - -### 2. 间接影响 -- ✅ **API接口**: 无需修改,已支持字符串类型 -- ✅ **数据库**: 无需修改,bigint类型支持字符串输入 -- ✅ **其他模块**: 无影响 - -### 3. 兼容性 -- ✅ **向后兼容**: 不影响现有功能 -- ✅ **向前兼容**: 支持新的ID格式 -- ✅ **跨平台**: 所有平台一致 - -## 最佳实践总结 - -### 1. 大整数处理原则 -```typescript -// ✅ 正确做法:保持字符串类型 -const safeConvertId = (id: string | number | undefined): string | undefined => { - if (id === undefined || id === null) return undefined; - return String(id); -}; - -// ❌ 错误做法:转换为数字类型 -const wrongConvertId = (id: string | number | undefined): number | undefined => { - return parseInt(String(id), 10); // 可能导致精度丢失 -}; -``` - -### 2. 项目统一规范 -- **ID字段**: 始终使用 `string | number` 类型 -- **转换函数**: 保持字符串格式,避免数字转换 -- **API接口**: 支持字符串类型参数 -- **类型定义**: 明确定义支持的类型 - -### 3. 测试验证 -- **功能测试**: 验证保存、编辑、提交功能 -- **数据验证**: 检查数据库记录精度 -- **边界测试**: 测试各种ID格式 -- **兼容性测试**: 确保向后兼容 - -## 总结 - -通过本次修复,成功解决了学生考核申请界面ID精度丢失的问题: - -1. **问题根除**: 修复了JavaScript大整数精度丢失的根本原因 -2. **精度保持**: 所有ID字段现在保持完整精度 -3. **数据一致**: 前后端数据完全一致 -4. **标准统一**: 与项目其他模块的处理方式保持一致 -5. **向后兼容**: 不影响现有功能和数据 - -修复方案简单有效,符合项目的最佳实践,为后续类似问题提供了标准解决方案。 \ No newline at end of file diff --git a/docs/问题解决方案/学生考核申请LocalDateTime类型转换错误修复报告.md b/docs/问题解决方案/学生考核申请LocalDateTime类型转换错误修复报告.md deleted file mode 100644 index 24a340d2..00000000 --- a/docs/问题解决方案/学生考核申请LocalDateTime类型转换错误修复报告.md +++ /dev/null @@ -1,180 +0,0 @@ -# 学生考核申请LocalDateTime类型转换错误修复报告 - -## 问题描述 - -**时间**: 2025-01-03 -**接口**: `GET /assessment/application/student/{studentId}/schedule/{scheduleId}` -**错误信息**: -``` -{ - "code": 500, - "msg": "class java.time.LocalDateTime cannot be cast to class java.lang.String (java.time.LocalDateTime and java.lang.String are in module java.base of loader 'bootstrap')", - "data": null -} -``` - -## 问题分析 - -### 错误原因 -在 `XskhAssessmentApplicationApprovalServiceImpl.queryById()` 方法中,第83-84行存在类型转换错误: - -```java -// 错误的代码 -selfScoreVo.setCreateTime((String) scoreMap.get("createTime")); -selfScoreVo.setUpdateTime((String) scoreMap.get("updateTime")); -``` - -### 根本原因 -1. **数据库字段类型**: `create_time` 和 `update_time` 字段在数据库中定义为 `datetime` 类型 -2. **MyBatis查询返回**: 通过 `selectSelfScoresWithIndicatorInfo` 查询时,MyBatis可能返回 `LocalDateTime` 类型 -3. **强制类型转换**: 代码中直接强制转换为 `String` 类型,导致 `ClassCastException` - -### 影响范围 -- 学生考核申请详情查询接口 -- 学生考核申请表单页面无法正常加载历史数据 - -## 解决方案 - -### 1. 修复Service实现类中的类型转换 - -**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentApplicationApprovalServiceImpl.java` - -**修改内容**: -```java -// 修复前 -selfScoreVo.setCreateTime((String) scoreMap.get("createTime")); -selfScoreVo.setUpdateTime((String) scoreMap.get("updateTime")); - -// 修复后 -Object createTimeObj = scoreMap.get("createTime"); -if (createTimeObj != null) { - selfScoreVo.setCreateTime(createTimeObj.toString()); -} -Object updateTimeObj = scoreMap.get("updateTime"); -if (updateTimeObj != null) { - selfScoreVo.setUpdateTime(updateTimeObj.toString()); -} -``` - -### 2. 添加JSON格式化注解 - -**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentSelfScoreVo.java` - -**修改内容**: -```java -import com.fasterxml.jackson.annotation.JsonFormat; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private String createTime; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private String updateTime; -``` - -**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentBonusApprovalVo.java` - -**修改内容**: -```java -import com.fasterxml.jackson.annotation.JsonFormat; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private String createTime; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private String updateTime; -``` - -**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentApplicationApprovalVo.java` - -**修改内容**: -```java -import com.fasterxml.jackson.annotation.JsonFormat; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private Date submitTime; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private Date teacherApprovalTime; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private Date createTime; - -@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") -private Date updateTime; -``` - -## 修复效果 - -### 修复前 -- 接口返回500错误 -- 前端无法正常显示考核申请详情 -- 用户无法查看历史申请记录 - -### 修复后 -- 接口正常返回数据 -- 前端可以正常显示考核申请详情 -- 时间字段格式化为标准格式 `yyyy-MM-dd HH:mm:ss` -- 支持查看历史申请记录 - -## 技术要点 - -### 1. 类型安全处理 -- 使用 `Object` 类型接收查询结果 -- 通过 `toString()` 方法安全转换 -- 添加空值检查避免NPE - -### 2. JSON序列化优化 -- 添加 `@JsonFormat` 注解统一时间格式 -- 确保前后端时间格式一致 -- 提升API响应质量 - -### 3. 数据库字段映射 -- 保持与BaseEntity基类的一致性 -- 遵循项目的时间字段处理规范 -- 避免类型转换异常 - -## 验证方法 - -### 1. 接口测试 -```bash -# 测试学生考核申请查询接口 -GET /assessment/application/student/1/schedule/1958180338534526978 -``` - -### 2. 前端验证 -- 打开学生考核申请表单页面 -- 验证历史申请记录正常显示 -- 检查时间字段格式是否正确 - -### 3. 日志检查 -- 查看后端日志确认无类型转换错误 -- 验证SQL查询正常执行 -- 确认数据正确返回 - -## 预防措施 - -### 1. 代码规范 -- 避免直接强制类型转换 -- 使用安全的类型转换方法 -- 添加适当的空值检查 - -### 2. 测试覆盖 -- 添加单元测试覆盖时间字段处理 -- 集成测试验证API响应格式 -- 前端测试确保UI正常显示 - -### 3. 文档维护 -- 更新API文档说明时间字段格式 -- 记录VO类字段类型定义 -- 维护问题解决方案文档 - -## 总结 - -本次修复解决了学生考核申请模块中的LocalDateTime类型转换错误,通过安全的类型处理和JSON格式化注解,确保了接口的稳定性和数据格式的一致性。修复后的代码更加健壮,能够正确处理各种时间类型的数据,提升了用户体验。 - ---- - -**修复人员**: AI Assistant -**修复时间**: 2025-01-03 -**影响模块**: 学生考核管理模块 -**测试状态**: 待验证 \ No newline at end of file diff --git a/docs/问题解决方案/学生考核申请更新主键冲突修复报告.md b/docs/问题解决方案/学生考核申请更新主键冲突修复报告.md deleted file mode 100644 index b5174da1..00000000 --- a/docs/问题解决方案/学生考核申请更新主键冲突修复报告.md +++ /dev/null @@ -1,144 +0,0 @@ -# 学生考核申请更新主键冲突修复报告 - -## 问题描述 - -### 现象 -在考核申请表单中,对已有数据进行修改后点击保存按钮,调用PUT接口更新数据时出现主键冲突错误: - -``` -Duplicate entry '1958434137320771585' for key 'PRIMARY' -``` - -### 错误详情 -- **接口**: `PUT /assessment/application` -- **错误类型**: `java.sql.SQLIntegrityConstraintViolationException` -- **错误位置**: `XskhAssessmentSelfScoreMapper.insert` -- **根本原因**: 更新操作误用插入逻辑 - -## 问题分析 - -### 根本原因 -在 `XskhAssessmentApplicationApprovalServiceImpl.updateSelfScores()` 和 `updateBonusApprovals()` 方法中,更新逻辑存在严重缺陷: - -**原有错误逻辑**: -1. 删除所有关联的子表记录 -2. 重新插入新的记录 - -**问题所在**: -- 前端传递的数据包含已存在记录的ID -- 后端删除后重新插入时,由于ID已存在导致主键冲突 -- 这种"删除重建"的方式会丢失原有的创建时间、创建人等审计信息 - -### 影响范围 -- 学生考核自评得分表 (`xskh_assessment_self_score`) -- 附加分申请表 (`xskh_assessment_bonus_approval`) - -## 解决方案 - -### 修复策略 -采用"智能更新"策略,根据记录ID是否存在决定执行更新或插入操作: - -```java -// 如果ID存在,则更新;否则新增 -if (selfScore.getId() != null) { - // 更新现有记录 - selfScoreMapper.updateById(selfScore); -} else { - // 新增记录 - selfScoreMapper.insert(selfScore); -} -``` - -### 具体修改 - -#### 1. 修复自评得分更新逻辑 -**文件**: `XskhAssessmentApplicationApprovalServiceImpl.java` -**方法**: `updateSelfScores()` - -```java -private void updateSelfScores(Long applicationId, List selfScoreList) { - if (selfScoreList != null && !selfScoreList.isEmpty()) { - for (XskhAssessmentSelfScoreBo selfScoreBo : selfScoreList) { - XskhAssessmentSelfScore selfScore = MapstructUtils.convert(selfScoreBo, XskhAssessmentSelfScore.class); - selfScore.setApplicationId(applicationId); - - // 如果ID存在,则更新;否则新增 - if (selfScore.getId() != null) { - // 更新现有记录 - selfScoreMapper.updateById(selfScore); - } else { - // 新增记录 - selfScoreMapper.insert(selfScore); - } - } - } -} -``` - -#### 2. 修复附加分申请更新逻辑 -**文件**: `XskhAssessmentApplicationApprovalServiceImpl.java` -**方法**: `updateBonusApprovals()` - -```java -private void updateBonusApprovals(Long applicationId, List bonusApprovalList) { - if (bonusApprovalList != null && !bonusApprovalList.isEmpty()) { - for (XskhAssessmentBonusApprovalBo bonusApprovalBo : bonusApprovalList) { - XskhAssessmentBonusApproval bonusApproval = MapstructUtils.convert(bonusApprovalBo, - XskhAssessmentBonusApproval.class); - bonusApproval.setApplicationId(applicationId); - - // 如果ID存在,则更新;否则新增 - if (bonusApproval.getId() != null) { - // 更新现有记录 - bonusApprovalMapper.updateById(bonusApproval); - } else { - // 新增记录,设置默认状态 - bonusApproval.setStatus("1"); // 默认待审核状态 - bonusApprovalMapper.insert(bonusApproval); - } - } - } -} -``` - -## 修复效果 - -### 优势 -1. **保留审计信息**: 不再删除重建,保留原有的创建时间、创建人等字段 -2. **避免主键冲突**: 根据ID存在性智能选择更新或插入操作 -3. **数据完整性**: 确保数据的连续性和完整性 -4. **性能优化**: 避免不必要的删除和插入操作 - -### 测试验证 -- ✅ 新增考核申请:正常插入新记录 -- ✅ 修改考核申请:正常更新现有记录 -- ✅ 混合操作:同时包含新增和修改的记录 -- ✅ 数据完整性:审计字段正确保留 - -## 经验总结 - -### 开发规范 -1. **更新操作原则**: 避免"删除重建"模式,优先使用"智能更新" -2. **ID处理策略**: 根据ID存在性决定操作类型 -3. **审计字段保护**: 更新时注意保护创建时间、创建人等审计信息 -4. **事务完整性**: 确保关联数据的更新在同一个事务中 - -### 代码审查要点 -- 检查更新逻辑是否采用"删除重建"模式 -- 验证是否正确处理已存在记录的ID -- 确认审计字段的保留策略 -- 测试混合操作场景(新增+修改) - -## 相关文件 - -- **修复文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentApplicationApprovalServiceImpl.java` -- **涉及表**: - - `xskh_assessment_self_score` (学生考核自评得分表) - - `xskh_assessment_bonus_approval` (附加分申请表) -- **相关接口**: `PUT /assessment/application` - ---- - -**修复时间**: 2025-08-21 -**修复人员**: AI Assistant -**状态**: 已完成 \ No newline at end of file diff --git a/docs/问题解决方案/学生考核自评得分查询字段错误修复报告.md b/docs/问题解决方案/学生考核自评得分查询字段错误修复报告.md deleted file mode 100644 index 40a3fbbb..00000000 --- a/docs/问题解决方案/学生考核自评得分查询字段错误修复报告.md +++ /dev/null @@ -1,153 +0,0 @@ -# 学生考核自评得分查询字段错误修复报告 - -## 问题描述 - -**错误时间**: 2025-08-21 11:53:24 -**错误接口**: `/assessment/approval/1958361581653856257` -**错误类型**: SQL语法错误 - 未知字段 - -### 错误详情 -``` -org.springframework.jdbc.BadSqlGrammarException: -Unknown column 'i.indicator_description' in 'field list' -``` - -### 错误SQL -```sql -SELECT s.id, s.application_id, s.indicator_id, s.indicator_name, - i.indicator_code, i.indicator_description, s.indicator_type, - s.max_score, s.self_score, s.self_remark, s.teacher_score, - s.teacher_remark, s.create_by, s.create_time, s.update_by, s.update_time -FROM xskh_assessment_self_score s -LEFT JOIN xskh_assessment_indicator i ON s.indicator_id = i.id AND i.tenant_id = '000000' -WHERE s.application_id = ? AND s.deleted = 0 AND s.tenant_id = '000000' -ORDER BY s.create_time ASC -``` - -## 问题分析 - -### 根本原因 -1. **字段名不匹配**: SQL查询中使用了 `i.indicator_description`,但数据库表中实际字段名为 `description` -2. **数据库表结构**: `xskh_assessment_indicator` 表中的字段名是 `description`,不是 `indicator_description` - -### 数据库表结构确认 -```sql --- xskh_assessment_indicator 表结构 -CREATE TABLE `xskh_assessment_indicator` ( - `id` bigint(20) NOT NULL COMMENT '主键ID', - `indicator_code` varchar(50) NOT NULL COMMENT '指标编码', - `indicator_name` varchar(100) NOT NULL COMMENT '指标名称', - `indicator_type_id` bigint(20) NOT NULL COMMENT '指标类型ID', - `indicator_type_name` varchar(100) NOT NULL COMMENT '指标类型名称', - `description` varchar(500) DEFAULT NULL COMMENT '指标说明', -- 正确字段名 - `max_score` decimal(5,2) NOT NULL COMMENT '最高分值', - -- ... 其他字段 -); -``` - -## 修复方案 - -### 修复文件 -- **文件路径**: `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentSelfScoreMapper.xml` -- **修复方法**: 将SQL查询中的字段名从 `i.indicator_description` 改为 `i.description` - -### 字段命名统一优化 -为了保持前后端字段命名的一致性,同时进行了以下优化: - -1. **VO类字段统一**: 将 `indicatorDescription` 改为 `description` -2. **Mapper映射统一**: 更新ResultMap中的字段映射 -3. **相关文件同步**: 修改了所有相关的Mapper文件和VO类 - -### 具体修复内容 - -#### 1. 修复ResultMap映射 -```xml - - - - - -``` - -#### 2. 修复SQL查询语句 -```xml - -SELECT - s.id, s.application_id, s.indicator_id, s.indicator_name, - i.indicator_code, i.indicator_description, s.indicator_type, - -- ... 其他字段 - - -SELECT - s.id, s.application_id, s.indicator_id, s.indicator_name, - i.indicator_code, i.description, s.indicator_type, - -- ... 其他字段 -``` - -### 修复后的完整SQL -```sql -SELECT - s.id, - s.application_id, - s.indicator_id, - s.indicator_name, - i.indicator_code, - i.description, -- 修复:使用正确的字段名 - s.indicator_type, - s.max_score, - s.self_score, - s.self_remark, - s.teacher_score, - s.teacher_remark, - s.create_by, - s.create_time, - s.update_by, - s.update_time -FROM xskh_assessment_self_score s -LEFT JOIN xskh_assessment_indicator i ON s.indicator_id = i.id AND i.tenant_id = '000000' -WHERE s.application_id = #{applicationId} -AND s.deleted = 0 -AND s.tenant_id = '000000' -ORDER BY s.create_time ASC -``` - -## 验证结果 - -### 修复验证 -1. ✅ **字段映射正确**: ResultMap中的 `description` 字段正确映射到数据库的 `description` 字段 -2. ✅ **SQL语法正确**: 查询语句使用正确的字段名,符合数据库表结构 -3. ✅ **VO类兼容**: `XskhAssessmentSelfScoreVo` 类中已正确定义 `description` 字段 -4. ✅ **字段命名统一**: 前后端字段名保持一致,避免混淆 - -### 测试建议 -1. **接口测试**: 重新调用 `/assessment/approval/{id}` 接口,确认不再出现字段错误 -2. **数据验证**: 确认返回的指标说明数据正确显示 -3. **功能测试**: 测试学生考核审核功能,确认自评得分查询正常 - -## 经验总结 - -### 开发规范提醒 -1. **字段命名一致性**: SQL查询中的字段名必须与数据库表结构完全一致 -2. **数据库设计规范**: 遵循项目统一的字段命名规范,避免混淆 -3. **代码审查**: 在开发过程中注意检查SQL语句的字段名正确性 - -### 预防措施 -1. **数据库文档**: 及时更新数据库设计文档,确保字段名准确 -2. **代码规范**: 建立SQL编写规范,要求字段名与数据库表结构保持一致 -3. **测试覆盖**: 增加SQL语法检查的自动化测试 - -## 相关文件 - -- **数据库设计**: `docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql` -- **Mapper文件**: - - `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentSelfScoreMapper.xml` - - `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentPlanMapper.xml` -- **VO类**: - - `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentSelfScoreVo.java` - - `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentPlanIndicatorVo.java` - ---- - -**修复人员**: Cursor AI -**修复时间**: 2025-08-21 -**修复状态**: ✅ 已完成 \ No newline at end of file diff --git a/web/src/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.ts b/web/src/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.ts index 5847317e..c182c13c 100644 --- a/web/src/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.ts +++ b/web/src/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.ts @@ -1,5 +1,6 @@ import request from '@/utils/request'; import { AxiosPromise } from 'axios'; +import { TableDataInfo } from '@/api/types'; import { DormHygieneCheckVO, DormHygieneCheckForm, @@ -16,7 +17,7 @@ import { * @returns {*} */ -export const listDormHygieneCheck = (query?: DormHygieneCheckQuery): AxiosPromise => { +export const listDormHygieneCheck = (query?: DormHygieneCheckQuery): AxiosPromise> => { return request({ url: '/dormHygieneCheck/dormHygieneCheck/list', method: 'get', diff --git a/web/src/api/xskh/disciplineType/disciplineType/types.ts b/web/src/api/xskh/disciplineType/disciplineType/types.ts index 874f0b75..da4e7af2 100644 --- a/web/src/api/xskh/disciplineType/disciplineType/types.ts +++ b/web/src/api/xskh/disciplineType/disciplineType/types.ts @@ -19,6 +19,21 @@ export interface DisciplineTypeVO { */ typeCategory: string; + /** + * 区域类型(1-宿舍区违纪 2-教学区违纪) + */ + areaType: string; + + /** + * 最低扣分 + */ + minScore: number; + + /** + * 最高扣分 + */ + maxScore: number; + /** * 类型说明 */ @@ -61,6 +76,21 @@ export interface DisciplineTypeForm extends BaseEntity { */ typeCategory?: string; + /** + * 区域类型(1-宿舍区违纪 2-教学区违纪) + */ + areaType?: string; + + /** + * 最低扣分 + */ + minScore?: number; + + /** + * 最高扣分 + */ + maxScore?: number; + /** * 类型说明 */ @@ -98,6 +128,11 @@ export interface DisciplineTypeQuery extends PageQuery { */ typeCategory?: string; + /** + * 区域类型(1-宿舍区违纪 2-教学区违纪) + */ + areaType?: string; + /** * 状态(0-正常 1-停用) */ diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue new file mode 100644 index 00000000..5a8de4db --- /dev/null +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue @@ -0,0 +1,825 @@ + + + + + diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckMainDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckMainDialog.vue new file mode 100644 index 00000000..0e9760f0 --- /dev/null +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckMainDialog.vue @@ -0,0 +1,735 @@ + + + + + diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue new file mode 100644 index 00000000..9a77c0f8 --- /dev/null +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue @@ -0,0 +1,1418 @@ + + + + + diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/components/ViolationRecordDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/components/ViolationRecordDialog.vue new file mode 100644 index 00000000..b22b8b69 --- /dev/null +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/components/ViolationRecordDialog.vue @@ -0,0 +1,995 @@ + + + + + diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue index f70656dd..74646ef4 100644 --- a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue @@ -137,17 +137,35 @@ - + - - + + + + @@ -171,7 +189,8 @@ import { DormHygieneCheckForm, DormHygieneCheckCompleteForm } from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types'; -import DormHygieneCheckDialog from './DormHygieneCheckDialog.vue'; +import DormHygieneCheckMainDialog from './DormHygieneCheckMainDialog.vue'; +import DormHygieneCheckSingleDialog from './DormHygieneCheckSingleDialog.vue'; // 类型定义 interface DialogOption { @@ -200,11 +219,21 @@ const total = ref(0); const queryFormRef = ref(); -const dialog = reactive({ +// 主弹窗(新增) +const mainDialog = reactive({ visible: false, title: '' }); +// 单个房间检查弹窗(修改) +const singleDialog = reactive({ + visible: false, + title: '' +}); + +// 存储当前编辑的数据 +const currentEditData = ref(null); + const initFormData: DormHygieneCheckForm = { id: undefined, deleted: undefined, @@ -287,22 +316,39 @@ const getList = async () => { } }; -/** 弹窗关闭事件处理 */ -const handleDialogClose = () => { - dialog.visible = false; +/** 主弹窗关闭事件处理 */ +const handleMainDialogClose = () => { + mainDialog.visible = false; reset(); }; -/** 弹窗提交事件处理 */ -const handleDialogSubmit = async (formData: DormHygieneCheckCompleteForm) => { +/** 单个房间检查弹窗关闭事件处理 */ +const handleSingleDialogClose = () => { + singleDialog.visible = false; + currentEditData.value = null; +}; + +/** 主弹窗提交事件处理 */ +const handleMainDialogSubmit = async (formData: any) => { try { - if (formData.id) { - await updateDormHygieneCheck(formData); - } else { - await addDormHygieneCheck(formData); - } + // 主弹窗提交的是新增数据 + await addDormHygieneCheck(formData); ElMessage.success('操作成功'); - dialog.visible = false; + mainDialog.visible = false; + await getList(); + } catch (error) { + console.error('提交失败:', error); + ElMessage.error('操作失败'); + } +}; + +/** 单个房间检查弹窗提交事件处理 */ +const handleSingleDialogSubmit = async (formData: any) => { + try { + // 单个房间检查弹窗提交的是修改数据 + await updateDormHygieneCheck(formData); + ElMessage.success('操作成功'); + singleDialog.visible = false; await getList(); } catch (error) { console.error('提交失败:', error); @@ -344,14 +390,14 @@ const handleSelectionChange = (selection: DormHygieneCheckVO[]) => { /** 新增按钮操作 */ const handleAdd = () => { reset(); - dialog.visible = true; - dialog.title = '添加宿舍卫生检查'; + mainDialog.visible = true; + mainDialog.title = '添加宿舍卫生检查'; }; /** 修改按钮操作 */ const handleUpdate = async (row?: DormHygieneCheckVO) => { - dialog.visible = true; - dialog.title = '修改宿舍卫生检查'; + singleDialog.visible = true; + singleDialog.title = '修改宿舍卫生检查'; const loadingInstance = ElLoading.service({ target: '.el-dialog__body', @@ -360,14 +406,13 @@ const handleUpdate = async (row?: DormHygieneCheckVO) => { }); try { - reset(); const _id = row?.id || ids.value[0]; const res = await getDormHygieneCheck(_id); - Object.assign(form.value, res.data); + currentEditData.value = res.data; } catch (error) { console.error('加载编辑数据失败:', error); ElMessage.error('加载数据失败,请重试'); - dialog.visible = false; + singleDialog.visible = false; } finally { loadingInstance.close(); } diff --git a/web/src/views/xskh/disciplineRecord/index.vue b/web/src/views/xskh/disciplineRecord/index.vue index 50d07461..8f327234 100644 --- a/web/src/views/xskh/disciplineRecord/index.vue +++ b/web/src/views/xskh/disciplineRecord/index.vue @@ -5,17 +5,25 @@ - + - + - + + + + + + + + + @@ -81,6 +89,13 @@ + + + + + + + + +