diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgConductScoreAuditController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgConductScoreAuditController.java new file mode 100644 index 00000000..eff38c51 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgConductScoreAuditController.java @@ -0,0 +1,111 @@ +package org.dromara.xg.controller; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.xg.domain.vo.XgConductScoreAuditVo; +import org.dromara.xg.domain.bo.XgConductScoreAuditQueryBo; +import org.dromara.xg.domain.bo.XgConductScoreAuditApproveBo; +import org.dromara.xg.domain.bo.XgConductScoreAuditRejectBo; +import org.dromara.xg.domain.bo.XgConductScoreAuditBatchBo; +import org.dromara.xg.service.IXgConductScoreAuditService; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 操行分审核 + * + * @author Lion Li + * @date 2025-01-16 + */ +@Validated +@RequiredArgsConstructor +@Slf4j +@RestController +@RequestMapping("/xg/conduct-score-audit") +public class XgConductScoreAuditController extends BaseController { + + private final IXgConductScoreAuditService xgConductScoreAuditService; + + /** + * 查询操行分审核列表 + */ + // @SaCheckPermission("xg:conductScoreAudit:list") + @GetMapping("/list") + public TableDataInfo list(XgConductScoreAuditQueryBo bo, PageQuery pageQuery) { + return xgConductScoreAuditService.queryPageList(bo, pageQuery); + } + + /** + * 获取操行分审核详细信息 + * + * @param id 主键 + */ + // @SaCheckPermission("xg:conductScoreAudit:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { + return R.ok(xgConductScoreAuditService.queryById(id)); + } + + /** + * 审核通过 + */ + // @SaCheckPermission("xg:conductScoreAudit:approve") + @Log(title = "操行分审核", businessType = BusinessType.UPDATE) + @PostMapping("/approve/{id}") + public R approve(@NotNull(message = "审核ID不能为空") @PathVariable Long id, + @Validated @RequestBody XgConductScoreAuditApproveBo bo) { + return toAjax(xgConductScoreAuditService.approveAudit(id, bo)); + } + + /** + * 审核驳回 + */ + // @SaCheckPermission("xg:conductScoreAudit:reject") + @Log(title = "操行分审核", businessType = BusinessType.UPDATE) + @PostMapping("/reject/{id}") + public R reject(@NotNull(message = "审核ID不能为空") @PathVariable Long id, + @Validated @RequestBody XgConductScoreAuditRejectBo bo) { + return toAjax(xgConductScoreAuditService.rejectAudit(id, bo)); + } + + /** + * 批量审核通过 + */ + // @SaCheckPermission("xg:conductScoreAudit:batchApprove") + @Log(title = "操行分审核", businessType = BusinessType.UPDATE) + @PostMapping("/batch-approve") + public R batchApprove(@Validated @RequestBody XgConductScoreAuditBatchBo bo) { + return toAjax(xgConductScoreAuditService.batchApproveAudit(bo)); + } + + /** + * 批量审核驳回 + */ + // @SaCheckPermission("xg:conductScoreAudit:batchReject") + @Log(title = "操行分审核", businessType = BusinessType.UPDATE) + @PostMapping("/batch-reject") + public R batchReject(@Validated @RequestBody XgConductScoreAuditBatchBo bo) { + return toAjax(xgConductScoreAuditService.batchRejectAudit(bo)); + } + + /** + * 删除操行分审核 + */ + // @SaCheckPermission("xg:conductScoreAudit:remove") + @Log(title = "操行分审核", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { + return toAjax(xgConductScoreAuditService.deleteWithValidByIds(List.of(ids), true)); + } + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgConductScoreController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgConductScoreController.java index 2bd45556..50837d1f 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgConductScoreController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgConductScoreController.java @@ -29,6 +29,7 @@ import org.dromara.xg.domain.vo.XgConductScoreSemesterAverageExportVo; import org.dromara.xg.domain.bo.XgConductScoreBo; import org.dromara.xg.domain.bo.XgConductScoreQueryBo; import org.dromara.xg.domain.bo.XgConductScoreBatchBo; +import org.dromara.xg.domain.bo.XgConductScoreBatchUpdateBo; import org.dromara.xg.domain.vo.XgConductScoreBatchResultVo; import org.dromara.xg.domain.vo.XgConductScoreClassSummaryVo; import org.dromara.xg.domain.vo.XgConductScoreSemesterAverageVo; @@ -105,6 +106,17 @@ public class XgConductScoreController extends BaseController { return R.ok(xgConductScoreService.batchInsertConductScores(bo)); } + /** + * 批量提交修改审核 + */ + // @SaCheckPermission("xg:conductScore:batchUpdate") + @RepeatSubmit() + @Log(title = "学生操行分", businessType = BusinessType.UPDATE) + @PostMapping("/batch-update") + public R batchUpdate(@Validated @RequestBody XgConductScoreBatchUpdateBo bo) { + return R.ok(xgConductScoreService.batchUpdateConductScores(bo)); + } + /** * 查询学生操行分列表 */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgConductScoreAudit.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgConductScoreAudit.java new file mode 100644 index 00000000..af9d630e --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgConductScoreAudit.java @@ -0,0 +1,89 @@ +package org.dromara.xg.domain; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableField; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 操行分审核对象 xg_conduct_score_audit + * + * @author Lion Li + * @date 2025-01-16 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class XgConductScoreAudit extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** 主键ID */ + private Long id; + + /** 租户ID */ + private String tenantId; + + /** 学生ID */ + private Long studentId; + + /** 学年 */ + private Integer academicYear; + + /** 学期:1-第一学期,2-第二学期 */ + private Integer semester; + + /** 月份 */ + private Integer month; + + /** 班级ID */ + private Long classId; + + /** 原分数 */ + private BigDecimal oldScore; + + /** 原备注 */ + private String oldRemark; + + /** 新分数 */ + private BigDecimal newScore; + + /** 新备注 */ + private String newRemark; + + /** 申请人ID */ + private Long applyUserId; + + /** 申请人姓名 */ + private String applyUserName; + + /** 申请时间 */ + private Date applyTime; + + /** 申请理由 */ + private String applyReason; + + /** 审核状态:0-待审核,1-审核通过,2-审核驳回 */ + private Integer auditStatus; + + /** 审核人ID */ + private Long auditUserId; + + /** 审核人姓名 */ + private String auditUserName; + + /** 审核时间 */ + private Date auditTime; + + /** 审核备注 */ + private String auditRemark; + + /** 删除标志 */ + private Integer deleted; + + /** 创建部门(审核表不需要此字段) */ + @TableField(exist = false) + private Long createDept; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditApproveBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditApproveBo.java new file mode 100644 index 00000000..b05b6d95 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditApproveBo.java @@ -0,0 +1,24 @@ +package org.dromara.xg.domain.bo; + +import lombok.Data; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 操行分审核审批业务对象 + * + * @author Lion Li + * @date 2025-01-16 + */ +@Data +public class XgConductScoreAuditApproveBo { + + private static final long serialVersionUID = 1L; + + /** + * 审核备注 + */ + @Size(max = 200, message = "审核备注不能超过200个字符") + private String auditRemark; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditBatchBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditBatchBo.java new file mode 100644 index 00000000..848bdc91 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditBatchBo.java @@ -0,0 +1,32 @@ +package org.dromara.xg.domain.bo; + +import lombok.Data; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; + +import java.util.List; + +/** + * 操行分批量审核业务对象 + * + * @author Lion Li + * @date 2025-01-16 + */ +@Data +public class XgConductScoreAuditBatchBo { + + private static final long serialVersionUID = 1L; + + /** + * 审核记录ID列表 + */ + @NotEmpty(message = "审核记录ID不能为空") + private List ids; + + /** + * 审核备注 + */ + @Size(max = 200, message = "审核备注不能超过200个字符") + private String auditRemark; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditQueryBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditQueryBo.java new file mode 100644 index 00000000..ec705bf5 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditQueryBo.java @@ -0,0 +1,95 @@ +package org.dromara.xg.domain.bo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.mybatis.core.domain.BaseEntity; + +/** + * 操行分审核查询业务对象 + * + * @author Lion Li + * @date 2025-01-16 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class XgConductScoreAuditQueryBo extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 学生ID + */ + private Long studentId; + + /** + * 学生姓名 + */ + private String studentName; + + /** + * 学号 + */ + private String studentNo; + + + /** + * 班级ID + */ + private Long classId; + + /** + * 班级名称 + */ + private String className; + + /** + * 申请人ID + */ + private Long applyUserId; + + /** + * 申请人姓名 + */ + private String applyUserName; + + /** + * 审核状态:0-待审核,1-审核通过,2-审核驳回 + */ + private Integer auditStatus; + + /** + * 审核人ID + */ + private Long auditUserId; + + /** + * 审核人姓名 + */ + private String auditUserName; + + /** + * 申请开始时间 + */ + private String applyBeginTime; + + /** + * 申请结束时间 + */ + private String applyEndTime; + + /** + * 审核开始时间 + */ + private String auditBeginTime; + + /** + * 审核结束时间 + */ + private String auditEndTime; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditRejectBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditRejectBo.java new file mode 100644 index 00000000..66e2f374 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreAuditRejectBo.java @@ -0,0 +1,24 @@ +package org.dromara.xg.domain.bo; + +import lombok.Data; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 操行分审核驳回业务对象 + * + * @author Lion Li + * @date 2025-01-16 + */ +@Data +public class XgConductScoreAuditRejectBo { + + private static final long serialVersionUID = 1L; + + /** + * 审核备注 + */ + @Size(max = 200, message = "审核备注不能超过200个字符") + private String auditRemark; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreBatchUpdateBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreBatchUpdateBo.java new file mode 100644 index 00000000..b9e767f1 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreBatchUpdateBo.java @@ -0,0 +1,50 @@ +package org.dromara.xg.domain.bo; + +import lombok.Data; +import lombok.experimental.Accessors; +import java.math.BigDecimal; +import java.util.List; + +import jakarta.validation.constraints.NotNull; + +/** + * 学生操行分批量修改审核业务对象 + * + * @author Lion Li + * @date 2025-01-16 + */ +@Data +@Accessors(chain = true) +public class XgConductScoreBatchUpdateBo { + + private static final long serialVersionUID = 1L; + + /** + * 学年 + */ + @NotNull(message = "学年不能为空") + private Integer academicYear; + + /** + * 学期 + */ + @NotNull(message = "学期不能为空") + private Integer semester; + + /** + * 月份 + */ + @NotNull(message = "月份不能为空") + private Integer month; + + /** + * 班级ID(前端传递字符串避免精度问题) + */ + private String classId; + + /** + * 修改分数列表 + */ + private List scores; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreBatchUpdateItemBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreBatchUpdateItemBo.java new file mode 100644 index 00000000..c68010c0 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgConductScoreBatchUpdateItemBo.java @@ -0,0 +1,39 @@ +package org.dromara.xg.domain.bo; + +import lombok.Data; +import lombok.experimental.Accessors; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; + +/** + * 学生操行分批量修改审核项业务对象 + * + * @author Lion Li + * @date 2025-01-16 + */ +@Data +@Accessors(chain = true) +public class XgConductScoreBatchUpdateItemBo { + + private static final long serialVersionUID = 1L; + + /** + * 学生ID + */ + @NotNull(message = "学生ID不能为空") + private Long studentId; + + /** + * 新分数 + */ + @NotNull(message = "分数不能为空") + @DecimalMin(value = "0", message = "分数不能小于0") + @DecimalMax(value = "100", message = "分数不能大于100") + private BigDecimal score; + + /** + * 新备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgConductScoreAuditVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgConductScoreAuditVo.java new file mode 100644 index 00000000..34f85b84 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgConductScoreAuditVo.java @@ -0,0 +1,166 @@ +package org.dromara.xg.domain.vo; + +import lombok.Data; +import java.math.BigDecimal; +import java.util.Date; +import java.io.Serial; +import java.io.Serializable; +import com.fasterxml.jackson.annotation.JsonFormat; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import org.dromara.xg.domain.XgConductScoreAudit; +import io.github.linpeilie.annotations.AutoMapper; + +/** + * 操行分审核视图对象 + * + * @author Lion Li + * @date 2025-01-16 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = XgConductScoreAudit.class) +public class XgConductScoreAuditVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + private Long id; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 学生ID + */ + private Long studentId; + + /** + * 学生姓名 + */ + private String studentName; + + /** + * 学号 + */ + private String studentNo; + + /** + * 学年 + */ + private Integer academicYear; + + /** + * 学期 + */ + private Integer semester; + + /** + * 月份 + */ + private Integer month; + + /** + * 班级ID + */ + private Long classId; + + /** + * 班级名称 + */ + private String className; + + /** + * 原分数 + */ + private BigDecimal oldScore; + + /** + * 原备注 + */ + private String oldRemark; + + /** + * 新分数 + */ + private BigDecimal newScore; + + /** + * 新备注 + */ + private String newRemark; + + /** + * 申请人ID + */ + private Long applyUserId; + + /** + * 申请人姓名 + */ + private String applyUserName; + + /** + * 申请时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date applyTime; + + /** + * 申请理由 + */ + private String applyReason; + + /** + * 审核状态:0-待审核,1-审核通过,2-审核驳回 + */ + private Integer auditStatus; + + /** + * 审核人ID + */ + private Long auditUserId; + + /** + * 审核人姓名 + */ + private String auditUserName; + + /** + * 审核时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date auditTime; + + /** + * 审核备注 + */ + private String auditRemark; + + /** + * 创建者 + */ + 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; + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/listener/XgConductScoreImportListener.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/listener/XgConductScoreImportListener.java index 87fdbd7a..54b924f0 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/listener/XgConductScoreImportListener.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/listener/XgConductScoreImportListener.java @@ -71,7 +71,8 @@ public class XgConductScoreImportListener extends AnalysisEventListener students = studentMsgService.queryList(queryBo); if (students == null || students.isEmpty()) { failureNum++; - failureMsg.append("
").append(failureNum).append("、学号 ").append(importVo.getStudentNo()).append(" 不存在"); + failureMsg.append("
").append(failureNum).append("、学号 ").append(importVo.getStudentNo()) + .append(" 不存在"); return; } XgStudentMsgVo student = students.get(0); @@ -80,34 +81,70 @@ public class XgConductScoreImportListener extends AnalysisEventListener").append(failureNum).append("、学号 ").append(importVo.getStudentNo()) - .append(" 姓名不匹配,Excel中:").append(importVo.getStudentName()) - .append(",系统中:").append(student.getName()); + .append(" 姓名不匹配,Excel中:").append(importVo.getStudentName()) + .append(",系统中:").append(student.getName()); return; } // 4. 验证班级权限(如果指定了班级) if (StringUtils.isNotBlank(classId) && !classId.equals(student.getClassId())) { failureNum++; - failureMsg.append("
").append(failureNum).append("、学号 ").append(importVo.getStudentNo()).append(" 不属于指定班级"); + failureMsg.append("
").append(failureNum).append("、学号 ").append(importVo.getStudentNo()) + .append(" 不属于指定班级"); return; } // 5. 检查是否已有记录 XgConductScoreVo existingRecord = conductScoreService.selectByStudentAndTime( - student.getId(), academicYear, semester, month); + student.getId(), academicYear, semester, month); if (existingRecord != null) { - // 更新现有记录 - XgConductScoreBo updateBo = new XgConductScoreBo(); - updateBo.setId(existingRecord.getId()); - updateBo.setScore(java.math.BigDecimal.valueOf(importVo.getScore())); - updateBo.setRemark(importVo.getRemark()); - updateBo.setUpdateBy(operUserId); - conductScoreService.updateByBo(updateBo); + // 检查数据是否发生变化 + boolean scoreChanged = !java.math.BigDecimal.valueOf(importVo.getScore()) + .equals(existingRecord.getScore()); + boolean remarkChanged = !StringUtils.equals(StringUtils.trim(importVo.getRemark()), + StringUtils.trim(existingRecord.getRemark())); - successNum++; - successMsg.append("
").append(successNum).append("、学号 ").append(importVo.getStudentNo()) - .append(" 更新成功(覆盖原有分数 ").append(existingRecord.getScore()).append(" → ").append(importVo.getScore()).append(")"); + if (scoreChanged || remarkChanged) { + // 数据有变化:提交审核 + try { + // 构造审核数据 + org.dromara.xg.domain.bo.XgConductScoreBatchUpdateItemBo updateItem = new org.dromara.xg.domain.bo.XgConductScoreBatchUpdateItemBo() + .setStudentId(student.getId()) + .setScore(java.math.BigDecimal.valueOf(importVo.getScore())) + .setRemark(importVo.getRemark()); + + org.dromara.xg.domain.bo.XgConductScoreBatchUpdateBo batchUpdateBo = new org.dromara.xg.domain.bo.XgConductScoreBatchUpdateBo() + .setAcademicYear(academicYear) + .setSemester(semester) + .setMonth(month) + .setClassId(student.getClassId()) + .setScores(java.util.Arrays.asList(updateItem)); + + // 调用审核接口 + conductScoreService.batchUpdateConductScores(batchUpdateBo); + + successNum++; + successMsg.append("
").append(successNum).append("、学号 ").append(importVo.getStudentNo()) + .append(" 提交审核(数据有变化)"); + } catch (Exception e) { + failureNum++; + failureMsg.append("
").append(failureNum).append("、学号 ").append(importVo.getStudentNo()) + .append(" 提交审核失败:").append(e.getMessage()); + } + } else { + // 数据无变化:直接更新 + XgConductScoreBo updateBo = new XgConductScoreBo(); + updateBo.setId(existingRecord.getId()); + updateBo.setScore(java.math.BigDecimal.valueOf(importVo.getScore())); + updateBo.setRemark(importVo.getRemark()); + updateBo.setUpdateBy(operUserId); + conductScoreService.updateByBo(updateBo); + + successNum++; + successMsg.append("
").append(successNum).append("、学号 ").append(importVo.getStudentNo()) + .append(" 更新成功(数据无变化)"); + } } else { // 插入新记录 XgConductScoreBo insertBo = new XgConductScoreBo(); @@ -124,7 +161,8 @@ public class XgConductScoreImportListener extends AnalysisEventListener").append(successNum).append("、学号 ").append(importVo.getStudentNo()).append(" 导入成功"); + successMsg.append("
").append(successNum).append("、学号 ").append(importVo.getStudentNo()) + .append(" 导入成功"); } } catch (Exception e) { @@ -197,4 +235,4 @@ public class XgConductScoreImportListener extends AnalysisEventListener { + + /** + * 查询操行分审核列表(包含关联数据) + * + * @param queryBo 查询条件 + * @return 操行分审核列表 + */ + List selectXgConductScoreAuditList(XgConductScoreAuditQueryBo queryBo); + + /** + * 根据审核ID列表批量更新审核状态 + * + * @param ids 审核ID列表 + * @param auditStatus 审核状态 + * @param auditUserId 审核人ID + * @param auditUserName 审核人姓名 + * @param auditRemark 审核备注 + * @return 影响行数 + */ + int updateBatchAuditStatus(@Param("ids") List ids, + @Param("auditStatus") Integer auditStatus, + @Param("auditUserId") Long auditUserId, + @Param("auditUserName") String auditUserName, + @Param("auditRemark") String auditRemark); + + /** + * 根据审核ID将数据同步到正式表 + * + * @param auditId 审核ID + * @return 影响行数 + */ + int syncToFormalTable(@Param("auditId") Long auditId); + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgConductScoreAuditService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgConductScoreAuditService.java new file mode 100644 index 00000000..1087d140 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgConductScoreAuditService.java @@ -0,0 +1,115 @@ +package org.dromara.xg.service; + +import org.dromara.xg.domain.XgConductScoreAudit; +import org.dromara.xg.domain.vo.XgConductScoreAuditVo; +import org.dromara.xg.domain.bo.XgConductScoreAuditQueryBo; +import org.dromara.xg.domain.bo.XgConductScoreAuditApproveBo; +import org.dromara.xg.domain.bo.XgConductScoreAuditRejectBo; +import org.dromara.xg.domain.bo.XgConductScoreAuditBatchBo; +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 Lion Li + * @date 2025-01-16 + */ +public interface IXgConductScoreAuditService { + + /** + * 查询操行分审核 + * + * @param id 主键 + * @return 操行分审核 + */ + XgConductScoreAuditVo queryById(Long id); + + /** + * 分页查询操行分审核列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 操行分审核分页列表 + */ + TableDataInfo queryPageList(XgConductScoreAuditQueryBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的操行分审核列表 + * + * @param bo 查询条件 + * @return 操行分审核列表 + */ + List queryList(XgConductScoreAuditQueryBo bo); + + /** + * 新增操行分审核 + * + * @param bo 操行分审核 + * @return 结果 + */ + Boolean insertByBo(Object bo); + + /** + * 修改操行分审核 + * + * @param bo 操行分审核 + * @return 结果 + */ + Boolean updateByBo(Object bo); + + /** + * 校验并批量删除操行分审核信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否校验,true-删除前校验,false-直接删除 + * @return 结果 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 审核通过 + * + * @param id 审核ID + * @param bo 审核参数 + * @return 结果 + */ + Boolean approveAudit(Long id, XgConductScoreAuditApproveBo bo); + + /** + * 审核驳回 + * + * @param id 审核ID + * @param bo 审核参数 + * @return 结果 + */ + Boolean rejectAudit(Long id, XgConductScoreAuditRejectBo bo); + + /** + * 批量审核通过 + * + * @param bo 批量审核参数 + * @return 结果 + */ + Boolean batchApproveAudit(XgConductScoreAuditBatchBo bo); + + /** + * 批量审核驳回 + * + * @param bo 批量审核参数 + * @return 结果 + */ + Boolean batchRejectAudit(XgConductScoreAuditBatchBo bo); + + /** + * 批量提交修改审核 + * + * @param auditList 审核记录列表 + * @return 结果 + */ + Boolean batchInsertAudits(List auditList); + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgConductScoreService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgConductScoreService.java index 7d2edc42..3bb475fc 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgConductScoreService.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgConductScoreService.java @@ -4,6 +4,7 @@ import org.dromara.xg.domain.vo.XgConductScoreVo; import org.dromara.xg.domain.bo.XgConductScoreBo; import org.dromara.xg.domain.bo.XgConductScoreQueryBo; import org.dromara.xg.domain.bo.XgConductScoreBatchBo; +import org.dromara.xg.domain.bo.XgConductScoreBatchUpdateBo; import org.dromara.xg.domain.vo.XgConductScoreBatchResultVo; import org.dromara.xg.domain.vo.XgConductScoreClassSummaryVo; import org.dromara.xg.domain.vo.XgConductScoreSemesterAverageVo; @@ -101,6 +102,14 @@ public interface IXgConductScoreService { */ XgConductScoreBatchResultVo batchInsertConductScores(XgConductScoreBatchBo bo); + /** + * 批量提交修改审核 + * + * @param bo 批量修改审核参数 + * @return 审核结果 + */ + XgConductScoreBatchResultVo batchUpdateConductScores(XgConductScoreBatchUpdateBo bo); + /** * 获取班主任负责的班级学生列表 * diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgConductScoreAuditServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgConductScoreAuditServiceImpl.java new file mode 100644 index 00000000..c9d75e53 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgConductScoreAuditServiceImpl.java @@ -0,0 +1,328 @@ +package org.dromara.xg.service.impl; + +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.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.ObjectUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.xg.domain.XgConductScoreAudit; +import org.dromara.xg.domain.bo.XgConductScoreAuditQueryBo; +import org.dromara.xg.domain.bo.XgConductScoreAuditApproveBo; +import org.dromara.xg.domain.bo.XgConductScoreAuditRejectBo; +import org.dromara.xg.domain.bo.XgConductScoreAuditBatchBo; +import org.dromara.xg.domain.vo.XgConductScoreAuditVo; +import org.dromara.xg.mapper.IXgConductScoreAuditMapper; +import org.dromara.xg.service.IXgConductScoreAuditService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * 操行分审核Service业务层处理 + * + * @author Lion Li + * @date 2025-01-16 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class XgConductScoreAuditServiceImpl implements IXgConductScoreAuditService { + + private final IXgConductScoreAuditMapper xgConductScoreAuditMapper; + + /** + * 查询操行分审核 + * + * @param id 主键 + * @return 操行分审核 + */ + @Override + public XgConductScoreAuditVo queryById(Long id) { + return xgConductScoreAuditMapper.selectVoById(id); + } + + /** + * 分页查询操行分审核列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 操行分审核分页列表 + */ + @Override + public TableDataInfo queryPageList(XgConductScoreAuditQueryBo bo, PageQuery pageQuery) { + // 设置租户ID + if (StringUtils.isBlank(bo.getTenantId())) { + bo.setTenantId(TenantHelper.getTenantId()); + } + + // 设置默认审核状态为待审核 + if (bo.getAuditStatus() == null) { + bo.setAuditStatus(0); + } + + // 使用自定义查询方法,支持关联查询 + List list = xgConductScoreAuditMapper.selectXgConductScoreAuditList(bo); + + // 手动分页(因为使用了自定义SQL查询) + int total = list.size(); + int startIndex = (pageQuery.getPageNum() - 1) * pageQuery.getPageSize(); + int endIndex = Math.min(startIndex + pageQuery.getPageSize(), total); + + List pageList = new ArrayList<>(); + if (startIndex < total) { + pageList = list.subList(startIndex, endIndex); + } + + return new TableDataInfo<>(pageList, total); + } + + /** + * 查询符合条件的操行分审核列表 + * + * @param bo 查询条件 + * @return 操行分审核列表 + */ + @Override + public List queryList(XgConductScoreAuditQueryBo bo) { + return xgConductScoreAuditMapper.selectXgConductScoreAuditList(bo); + } + + + /** + * 新增操行分审核 + * + * @param bo 操行分审核 + * @return 结果 + */ + @Override + public Boolean insertByBo(Object bo) { + XgConductScoreAudit audit = MapstructUtils.convert(bo, XgConductScoreAudit.class); + validEntityBeforeSave(audit); + return xgConductScoreAuditMapper.insert(audit) > 0; + } + + /** + * 修改操行分审核 + * + * @param bo 操行分审核 + * @return 结果 + */ + @Override + public Boolean updateByBo(Object bo) { + XgConductScoreAudit audit = MapstructUtils.convert(bo, XgConductScoreAudit.class); + validEntityBeforeSave(audit); + return xgConductScoreAuditMapper.updateById(audit) > 0; + } + + /** + * 校验并批量删除操行分审核信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否校验,true-删除前校验,false-直接删除 + * @return 结果 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // 只能删除待审核状态的记录 + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.in(XgConductScoreAudit::getId, ids); + lqw.ne(XgConductScoreAudit::getAuditStatus, 0); + if (xgConductScoreAuditMapper.exists(lqw)) { + throw new ServiceException("只能删除待审核状态的记录"); + } + } + return xgConductScoreAuditMapper.deleteBatchIds(ids) > 0; + } + + /** + * 审核通过 + * + * @param id 审核ID + * @param bo 审核参数 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean approveAudit(Long id, XgConductScoreAuditApproveBo bo) { + // 1. 更新审核记录状态 + XgConductScoreAudit audit = new XgConductScoreAudit(); + audit.setId(id); + audit.setAuditStatus(1); // 审核通过 + audit.setAuditUserId(LoginHelper.getUserId()); + audit.setAuditUserName(LoginHelper.getUsername()); + audit.setAuditTime(new Date()); + audit.setAuditRemark(bo.getAuditRemark()); + audit.setUpdateBy(LoginHelper.getUserId()); + audit.setUpdateTime(new Date()); + + int updateCount = xgConductScoreAuditMapper.updateById(audit); + if (updateCount <= 0) { + throw new ServiceException("审核记录不存在或状态已变更"); + } + + // 2. 同步数据到正式表 + int syncCount = xgConductScoreAuditMapper.syncToFormalTable(id); + if (syncCount <= 0) { + throw new ServiceException("同步数据到正式表失败"); + } + + log.info("审核通过成功,审核ID: {}, 同步记录数: {}", id, syncCount); + return true; + } + + /** + * 审核驳回 + * + * @param id 审核ID + * @param bo 审核参数 + * @return 结果 + */ + @Override + public Boolean rejectAudit(Long id, XgConductScoreAuditRejectBo bo) { + XgConductScoreAudit audit = new XgConductScoreAudit(); + audit.setId(id); + audit.setAuditStatus(2); // 审核驳回 + audit.setAuditUserId(LoginHelper.getUserId()); + audit.setAuditUserName(LoginHelper.getUsername()); + audit.setAuditTime(new Date()); + audit.setAuditRemark(bo.getAuditRemark()); + audit.setUpdateBy(LoginHelper.getUserId()); + audit.setUpdateTime(new Date()); + + int updateCount = xgConductScoreAuditMapper.updateById(audit); + if (updateCount <= 0) { + throw new ServiceException("审核记录不存在或状态已变更"); + } + + log.info("审核驳回成功,审核ID: {}", id); + return true; + } + + /** + * 批量审核通过 + * + * @param bo 批量审核参数 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean batchApproveAudit(XgConductScoreAuditBatchBo bo) { + Long auditUserId = LoginHelper.getUserId(); + String auditUserName = LoginHelper.getUsername(); + String auditRemark = bo.getAuditRemark(); + + // 批量更新审核状态 + int updateCount = xgConductScoreAuditMapper.updateBatchAuditStatus( + bo.getIds(), 1, auditUserId, auditUserName, auditRemark); + + if (updateCount != bo.getIds().size()) { + throw new ServiceException("部分审核记录不存在或状态已变更"); + } + + // 批量同步数据到正式表 + for (Long auditId : bo.getIds()) { + int syncCount = xgConductScoreAuditMapper.syncToFormalTable(auditId); + if (syncCount <= 0) { + throw new ServiceException("同步数据到正式表失败,审核ID: " + auditId); + } + } + + log.info("批量审核通过成功,审核ID数量: {}", bo.getIds().size()); + return true; + } + + /** + * 批量审核驳回 + * + * @param bo 批量审核参数 + * @return 结果 + */ + @Override + public Boolean batchRejectAudit(XgConductScoreAuditBatchBo bo) { + Long auditUserId = LoginHelper.getUserId(); + String auditUserName = LoginHelper.getUsername(); + String auditRemark = bo.getAuditRemark(); + + int updateCount = xgConductScoreAuditMapper.updateBatchAuditStatus( + bo.getIds(), 2, auditUserId, auditUserName, auditRemark); + + if (updateCount != bo.getIds().size()) { + throw new ServiceException("部分审核记录不存在或状态已变更"); + } + + log.info("批量审核驳回成功,审核ID数量: {}", bo.getIds().size()); + return true; + } + + /** + * 批量提交修改审核 + * + * @param auditList 审核记录列表 + * @return 结果 + */ + @Override + public Boolean batchInsertAudits(List auditList) { + if (auditList == null || auditList.isEmpty()) { + return true; + } + + // 批量验证 + for (XgConductScoreAudit audit : auditList) { + validEntityBeforeSave(audit); + } + + // 批量插入 + xgConductScoreAuditMapper.insertBatch(auditList); + return true; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(XgConductScoreAudit entity) { + // 基础字段校验 + if (entity.getStudentId() == null) { + throw new ServiceException("学生ID不能为空"); + } + if (entity.getAcademicYear() == null) { + throw new ServiceException("学年不能为空"); + } + if (entity.getSemester() == null) { + throw new ServiceException("学期不能为空"); + } + if (entity.getMonth() == null) { + throw new ServiceException("月份不能为空"); + } + if (entity.getClassId() == null) { + throw new ServiceException("班级ID不能为空"); + } + if (entity.getNewScore() == null) { + throw new ServiceException("新分数不能为空"); + } + if (entity.getApplyUserId() == null) { + throw new ServiceException("申请人ID不能为空"); + } + if (StringUtils.isBlank(entity.getApplyUserName())) { + throw new ServiceException("申请人姓名不能为空"); + } + + // 分数范围校验 + if (entity.getNewScore().compareTo(BigDecimal.ZERO) < 0 || + entity.getNewScore().compareTo(new BigDecimal("100")) > 0) { + throw new ServiceException("分数必须在0-100之间"); + } + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgConductScoreServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgConductScoreServiceImpl.java index fe9c8836..d13d7765 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgConductScoreServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgConductScoreServiceImpl.java @@ -17,10 +17,12 @@ import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.tenant.helper.TenantHelper; import org.dromara.xg.domain.XgConductScore; +import org.dromara.xg.domain.XgConductScoreAudit; import org.dromara.xg.domain.XgClassMsg; import org.dromara.xg.domain.XgStudentManager; import org.dromara.xg.domain.XgStudentManagerSource; import org.dromara.xg.domain.bo.XgConductScoreBatchBo; +import org.dromara.xg.domain.bo.XgConductScoreBatchUpdateBo; import org.dromara.xg.domain.bo.XgConductScoreQueryBo; import org.dromara.xg.domain.bo.XgConductScoreBo; import org.dromara.xg.domain.vo.XgConductScoreBatchResultVo; @@ -38,6 +40,7 @@ import org.dromara.xg.mapper.XgClassMsgMapper; import org.dromara.xg.mapper.XgStudentManagerMapper; import org.dromara.xg.mapper.XgStudentManagerSourceMapper; import org.dromara.xg.service.IXgConductScoreService; +import org.dromara.xg.service.IXgConductScoreAuditService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -69,6 +72,7 @@ public class XgConductScoreServiceImpl implements IXgConductScoreService { private final XgStudentManagerSourceMapper xgStudentManagerSourceMapper; private final XgClassMsgMapper xgClassMsgMapper; private final SysDeptMapper sysDeptMapper; + private final IXgConductScoreAuditService auditService; /** * 查询学生操行分 @@ -383,6 +387,112 @@ public class XgConductScoreServiceImpl implements IXgConductScoreService { return result; } + /** + * 批量提交修改审核 + * + * @param bo 批量修改审核参数 + * @return 审核结果 + */ + @Override + public XgConductScoreBatchResultVo batchUpdateConductScores(XgConductScoreBatchUpdateBo bo) { + Long teacherId = LoginHelper.getUserId(); + String tenantId = TenantHelper.getTenantId(); + + // 将String类型的classId转换为Long + Long classId = Long.valueOf(bo.getClassId()); + + // 校验班主任权限 + if (!checkTeacherPermission(classId, teacherId)) { + throw new ServiceException("您不是该班级的班主任,无权修改操行分"); + } + + List failList = new ArrayList<>(); + int successCount = 0; + int failCount = 0; + + List auditList = new ArrayList<>(); + + for (var item : bo.getScores()) { + try { + // 校验分数范围 + if (item.getScore().compareTo(BigDecimal.ZERO) < 0 + || item.getScore().compareTo(new BigDecimal("100.00")) > 0) { + failList.add("学生ID:" + item.getStudentId() + " - 分数范围错误"); + failCount++; + continue; + } + + // 检查学生是否已存在当月记录 + if (!existsByStudentAndMonth(item.getStudentId(), bo.getAcademicYear(), bo.getSemester(), bo.getMonth(), + tenantId)) { + failList.add("学生ID:" + item.getStudentId() + " - 当月无历史分数记录,无法提交修改审核"); + failCount++; + continue; + } + + // 查询现有的分数记录,获取原分数和备注 + XgConductScoreVo existingRecord = selectByStudentAndTime(item.getStudentId(), + bo.getAcademicYear(), bo.getSemester(), bo.getMonth()); + + if (existingRecord == null) { + failList.add("学生ID:" + item.getStudentId() + " - 未找到历史记录"); + failCount++; + continue; + } + + // 检查是否有实际的修改(分数或备注不同) + boolean hasScoreChange = !item.getScore().equals(existingRecord.getScore()); + boolean hasRemarkChange = !ObjectUtils.equals(item.getRemark(), existingRecord.getRemark()); + + if (!hasScoreChange && !hasRemarkChange) { + failList.add("学生ID:" + item.getStudentId() + " - 无实际修改内容"); + failCount++; + continue; + } + + // 创建审核记录 + XgConductScoreAudit audit = new XgConductScoreAudit(); + audit.setTenantId(tenantId); + audit.setStudentId(item.getStudentId()); + audit.setAcademicYear(bo.getAcademicYear()); + audit.setSemester(bo.getSemester()); + audit.setMonth(bo.getMonth()); + audit.setClassId(classId); + audit.setOldScore(existingRecord.getScore()); + audit.setOldRemark(existingRecord.getRemark()); + audit.setNewScore(item.getScore()); + audit.setNewRemark(item.getRemark()); + audit.setApplyUserId(teacherId); + audit.setApplyUserName(LoginHelper.getUsername()); + audit.setApplyTime(new Date()); + audit.setApplyReason("班主任修改操行分"); + audit.setAuditStatus(0); // 待审核 + audit.setCreateBy(teacherId); + audit.setCreateTime(new Date()); + + auditList.add(audit); + successCount++; + + } catch (Exception e) { + log.error("处理学生操行分修改审核失败,学生ID: {}", item.getStudentId(), e); + failList.add("学生ID:" + item.getStudentId() + " - 处理失败: " + e.getMessage()); + failCount++; + } + } + + // 批量提交审核 + if (!auditList.isEmpty()) { + auditService.batchInsertAudits(auditList); + } + + XgConductScoreBatchResultVo result = new XgConductScoreBatchResultVo(); + result.setSuccessCount(successCount); + result.setFailCount(failCount); + result.setFailList(failList); + + return result; + } + /** * 获取班主任负责的班级学生列表 * diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/conductScore/XgConductScoreAuditMapper.xml b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/conductScore/XgConductScoreAuditMapper.xml new file mode 100644 index 00000000..10fb85f9 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/conductScore/XgConductScoreAuditMapper.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UPDATE xg_conduct_score_audit + SET + audit_status = #{auditStatus}, + audit_user_id = #{auditUserId}, + audit_user_name = #{auditUserName}, + audit_time = NOW(), + audit_remark = #{auditRemark}, + update_by = #{auditUserId}, + update_time = NOW() + WHERE id IN + + #{id} + + + + + + UPDATE xg_conduct_score cs + INNER JOIN xg_conduct_score_audit audit ON + cs.student_id = audit.student_id + AND cs.academic_year = audit.academic_year + AND cs.semester = audit.semester + AND cs.month = audit.month + AND cs.tenant_id = audit.tenant_id + AND cs.deleted = 0 + SET + cs.score = audit.new_score, + cs.remark = audit.new_remark, + cs.update_by = audit.audit_user_id, + cs.update_time = NOW() + WHERE audit.id = #{auditId} + + + \ No newline at end of file diff --git a/backend/script/sql/xg_conduct_score_audit.sql b/backend/script/sql/xg_conduct_score_audit.sql new file mode 100644 index 00000000..3cb0e9f5 --- /dev/null +++ b/backend/script/sql/xg_conduct_score_audit.sql @@ -0,0 +1,66 @@ +-- ---------------------------- +-- 操行分审核表 +-- ---------------------------- +DROP TABLE IF EXISTS `xg_conduct_score_audit`; +CREATE TABLE `xg_conduct_score_audit` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID', + `student_id` bigint(20) NOT NULL COMMENT '学生ID', + `academic_year` int(11) NOT NULL COMMENT '学年', + `semester` tinyint(1) NOT NULL COMMENT '学期:1-第一学期,2-第二学期', + `month` tinyint(2) NOT NULL COMMENT '月份', + `class_id` bigint(20) NOT NULL COMMENT '班级ID', + `old_score` decimal(5,2) DEFAULT NULL COMMENT '原分数', + `old_remark` varchar(500) DEFAULT NULL COMMENT '原备注', + `new_score` decimal(5,2) NOT NULL COMMENT '新分数', + `new_remark` varchar(500) DEFAULT NULL COMMENT '新备注', + `apply_user_id` bigint(20) NOT NULL COMMENT '申请人ID', + `apply_user_name` varchar(50) NOT NULL COMMENT '申请人姓名', + `apply_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间', + `apply_reason` varchar(200) DEFAULT NULL COMMENT '申请理由', + `audit_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '审核状态:0-待审核,1-审核通过,2-审核驳回', + `audit_user_id` bigint(20) DEFAULT NULL COMMENT '审核人ID', + `audit_user_name` varchar(50) DEFAULT NULL COMMENT '审核人姓名', + `audit_time` datetime DEFAULT NULL COMMENT '审核时间', + `audit_remark` varchar(200) DEFAULT NULL COMMENT '审核备注', + `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 '更新时间', + `deleted` tinyint(1) DEFAULT '0' COMMENT '删除标志', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_student_period` (`student_id`,`academic_year`,`semester`,`month`,`deleted`), + KEY `idx_class_period` (`class_id`,`academic_year`,`semester`,`month`), + KEY `idx_audit_status` (`audit_status`,`apply_time`), + KEY `idx_apply_user` (`apply_user_id`,`apply_time`), + KEY `idx_audit_user` (`audit_user_id`,`audit_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操行分审核表'; + +-- ---------------------------- +-- 为审核表添加必要的索引和约束 +-- ---------------------------- +ALTER TABLE `xg_conduct_score_audit` +ADD CONSTRAINT `fk_audit_student` FOREIGN KEY (`student_id`) REFERENCES `xg_student_manager` (`student_id`) ON DELETE CASCADE, +ADD CONSTRAINT `fk_audit_class` FOREIGN KEY (`class_id`) REFERENCES `xg_class_msg` (`class_id`) ON DELETE CASCADE, +ADD CONSTRAINT `fk_audit_apply_user` FOREIGN KEY (`apply_user_id`) REFERENCES `sys_user` (`user_id`), +ADD CONSTRAINT `fk_audit_user` FOREIGN KEY (`audit_user_id`) REFERENCES `sys_user` (`user_id`); + +-- ---------------------------- +-- 初始化审核表数据(可选) +-- ---------------------------- +-- 将现有操行分数据标记为已审核状态(如果需要) +-- INSERT INTO xg_conduct_score_audit ( +-- tenant_id, student_id, academic_year, semester, month, class_id, +-- old_score, old_remark, new_score, new_remark, +-- apply_user_id, apply_user_name, apply_time, apply_reason, +-- audit_status, audit_user_id, audit_user_name, audit_time, audit_remark, +-- create_by, create_time, update_by, update_time, deleted +-- ) +-- SELECT +-- s.tenant_id, s.student_id, s.academic_year, s.semester, s.month, s.class_id, +-- NULL as old_score, NULL as old_remark, s.score as new_score, s.remark as new_remark, +-- s.class_teacher_id as apply_user_id, '系统导入' as apply_user_name, s.create_time as apply_time, '系统初始化' as apply_reason, +-- 1 as audit_status, s.class_teacher_id as audit_user_id, '系统导入' as audit_user_name, s.create_time as audit_time, '系统初始化审核' as audit_remark, +-- s.create_by, s.create_time, s.update_by, s.update_time, s.deleted +-- FROM xg_conduct_score s +-- WHERE s.deleted = 0; diff --git a/web/src/api/xg/conductScore.ts b/web/src/api/xg/conductScore.ts index 30c11f8f..4ecac992 100644 --- a/web/src/api/xg/conductScore.ts +++ b/web/src/api/xg/conductScore.ts @@ -70,6 +70,15 @@ export function conductScoreBatchAdd(data: any) { }); } +// 批量提交操行分修改审核 +export function conductScoreBatchUpdate(data: any) { + return request({ + url: '/xg/conduct-score/batch-update', + method: 'post', + data: data + }); +} + // 获取班主任负责的班级学生列表 export function conductScoreClassStudents(query: any) { return request({ diff --git a/web/src/api/xg/conductScoreAudit.ts b/web/src/api/xg/conductScoreAudit.ts new file mode 100644 index 00000000..b6f6cecd --- /dev/null +++ b/web/src/api/xg/conductScoreAudit.ts @@ -0,0 +1,72 @@ +import request from '@/utils/request'; + +// 查询操行分审核列表 +export function conductScoreAuditList(query: any) { + return request({ + url: '/xg/conduct-score-audit/list', + method: 'get', + params: query + }); +} + +// 查询操行分审核详细 +export function conductScoreAuditInfo(id: any) { + return request({ + url: '/xg/conduct-score-audit/' + id, + method: 'get' + }); +} + +// 审核通过 +export function conductScoreAuditApprove(id: any, data: any) { + return request({ + url: '/xg/conduct-score-audit/approve/' + id, + method: 'post', + data: data + }); +} + +// 审核驳回 +export function conductScoreAuditReject(id: any, data: any) { + return request({ + url: '/xg/conduct-score-audit/reject/' + id, + method: 'post', + data: data + }); +} + +// 批量审核通过 +export function conductScoreAuditBatchApprove(data: any) { + return request({ + url: '/xg/conduct-score-audit/batch-approve', + method: 'post', + data: data + }); +} + +// 批量审核驳回 +export function conductScoreAuditBatchReject(data: any) { + return request({ + url: '/xg/conduct-score-audit/batch-reject', + method: 'post', + data: data + }); +} + +// 导出操行分审核 +export function conductScoreAuditExport(query: any) { + return request({ + url: '/xg/conduct-score-audit/export', + method: 'post', + params: query + }); +} + +// 获取审核统计 +export function conductScoreAuditStatistics(query: any) { + return request({ + url: '/xg/conduct-score-audit/statistics', + method: 'get', + params: query + }); +} \ No newline at end of file diff --git a/web/src/views/xg/xsjcxx/ConductScoreAudit/components/AuditDialog.vue b/web/src/views/xg/xsjcxx/ConductScoreAudit/components/AuditDialog.vue new file mode 100644 index 00000000..7ddf3470 --- /dev/null +++ b/web/src/views/xg/xsjcxx/ConductScoreAudit/components/AuditDialog.vue @@ -0,0 +1,394 @@ + + + + + \ No newline at end of file diff --git a/web/src/views/xg/xsjcxx/ConductScoreAudit/index.vue b/web/src/views/xg/xsjcxx/ConductScoreAudit/index.vue new file mode 100644 index 00000000..09b7abd9 --- /dev/null +++ b/web/src/views/xg/xsjcxx/ConductScoreAudit/index.vue @@ -0,0 +1,381 @@ + + + + + diff --git a/web/src/views/xg/xsjcxx/ConductScoreInput/index.vue b/web/src/views/xg/xsjcxx/ConductScoreInput/index.vue index 73aa9a4d..215b9159 100644 --- a/web/src/views/xg/xsjcxx/ConductScoreInput/index.vue +++ b/web/src/views/xg/xsjcxx/ConductScoreInput/index.vue @@ -290,11 +290,9 @@ - + @@ -330,7 +328,7 @@ import { ArrowDown, InfoFilled } from '@element-plus/icons-vue'; import { globalHeaders } from '@/utils/request'; // API接口 -import { conductScoreClassStudents, conductScoreBatchAdd } from '@/api/xg/conductScore'; +import { conductScoreClassStudents, conductScoreBatchAdd, conductScoreBatchUpdate } from '@/api/xg/conductScore'; // 获取当前实例 const { proxy } = getCurrentInstance() as any; @@ -506,6 +504,55 @@ const getStudentStatusType = (status) => { } }; +// 获取学生录入状态 +const getStudentStatus = (student) => { + const hasExistingScore = student.existingScore !== null && student.existingScore !== undefined; + const hasCurrentScore = student.score !== null && student.score !== undefined; + + if (!hasExistingScore && !hasCurrentScore) { + return '未录入'; + } + + if (!hasExistingScore && hasCurrentScore) { + return '待保存'; + } + + if (hasExistingScore && !hasCurrentScore) { + return '已录入'; + } + + if (hasExistingScore && hasCurrentScore) { + // 比较分数和备注是否变化 + const scoreChanged = Number(student.score) !== Number(student.existingScore.score); + const remarkChanged = String(student.remark || '').trim() !== String(student.existingScore.remark || '').trim(); + + if (scoreChanged || remarkChanged) { + return '待审核'; + } else { + return '已录入'; + } + } + + return '未录入'; +}; + +// 获取学生录入状态的标签类型 +const getStudentStatusTagType = (student) => { + const status = getStudentStatus(student); + switch (status) { + case '未录入': + return 'info'; + case '待保存': + return 'primary'; + case '已录入': + return 'success'; + case '待审核': + return 'warning'; + default: + return 'info'; + } +}; + // 方法 const handleQuery = () => { getList(); @@ -623,7 +670,7 @@ const getList = async () => { // 批量操作方法 const handleBatchInput = () => { - // 打开批量录入弹窗(弹窗中输入分数和备注,保存时应用到所选学生) + // 批量录入:根据分数变化情况智能处理 console.log('handleBatchInput selectedStudents:', selectedStudents.value); if (!selectedStudents.value || selectedStudents.value.length === 0) { ElMessage.warning('请先选择要批量录入的学生'); @@ -634,30 +681,108 @@ const handleBatchInput = () => { batchModalForm.score = null; batchModalForm.remark = ''; batchModalVisible.value = true; - // debug info for users to see modal opening - ElMessage.info('已打开批量录入弹窗'); + + // 显示操作提示 + ElMessage.info(`已选择 ${selectedStudents.value.length} 个学生进行批量录入。系统将根据分数变化情况自动判断是否需要审核。`); }; const handleSaveAll = () => { - const validStudents = filteredStudents.value.filter( - (student) => student.score !== null && student.score !== undefined && student.existingScore === null - ); + // 获取所有有分值的学生 + const studentsWithScore = filteredStudents.value.filter((student) => student.score !== null && student.score !== undefined); - if (validStudents.length === 0) { + if (studentsWithScore.length === 0) { ElMessage.warning('没有需要保存的数据'); return; } - ElMessageBox.confirm(`确定要保存当前班级的 ${validStudents.length} 个学生的操行分吗?`, '保存确认', { + // 区分需要审核和不需要审核的学生 + const directSaveStudents = []; // 直接保存的学生 + const auditStudents = []; // 需要审核的学生 + + studentsWithScore.forEach((student) => { + const hasExistingScore = student.existingScore !== null && student.existingScore !== undefined; + + if (!hasExistingScore) { + // 第一次录入:直接保存 + directSaveStudents.push(student); + } else { + // 有历史记录:检查分数和备注是否都相同 + // 确保数据类型一致进行比较 + const currentScore = Number(student.score); + const originalScore = Number(student.existingScore.score); + const currentRemark = String(student.remark || '').trim(); + const originalRemark = String(student.existingScore.remark || '').trim(); + + const scoreChanged = currentScore !== originalScore; + const remarkChanged = currentRemark !== originalRemark; + + // 调试信息:检查每个学生的比较结果 + console.log(`学生 ${student.studentName}:`, { + studentId: student.studentId, + hasExistingScore, + currentScore, + originalScore, + scoreChanged, + currentRemark: `"${currentRemark}"`, + originalRemark: `"${originalRemark}"`, + remarkChanged, + existingScore: student.existingScore, + studentRemark: student.remark, + existingScoreRemark: student.existingScore?.remark + }); + + if (!scoreChanged && !remarkChanged) { + // 分数和备注都没有变化:直接保存 + directSaveStudents.push(student); + } else { + // 分数或备注发生了变化:需要审核 + console.log(`学生 ${student.studentName} 需要审核,原因:`, { + scoreChanged, + remarkChanged, + reason: scoreChanged ? '分数变化' : remarkChanged ? '备注变化' : '未知' + }); + auditStudents.push(student); + } + } + }); + + if (directSaveStudents.length === 0 && auditStudents.length === 0) { + ElMessage.warning('没有需要保存的数据'); + return; + } + + // 构建确认消息 + let confirmMessage = '确定要'; + const actions = []; + if (directSaveStudents.length > 0) { + actions.push(`保存 ${directSaveStudents.length} 个学生`); + } + if (auditStudents.length > 0) { + actions.push(`提交 ${auditStudents.length} 个修改学生审核`); + } + confirmMessage += actions.join(' 和 ') + '的操行分吗?'; + + if (auditStudents.length > 0) { + confirmMessage += '\n\n注意:分数或备注发生变化的学生需要管理员审核才能生效。'; + } + + ElMessageBox.confirm(confirmMessage, '保存确认', { confirmButtonText: '确定', cancelButtonText: '取消', - type: 'warning' + type: auditStudents.length > 0 ? 'warning' : 'success', + dangerouslyUseHTMLString: true }).then(() => { - saveBatchData(validStudents); + // 分批处理直接保存和审核 + if (directSaveStudents.length > 0) { + saveBatchData(directSaveStudents, 'add'); + } + if (auditStudents.length > 0) { + saveBatchData(auditStudents, 'update'); + } }); }; -const saveBatchData = async (students) => { +const saveBatchData = async (students, operationType = 'add') => { try { const batchData = { academicYear: queryParams.academicYear, @@ -671,18 +796,32 @@ const saveBatchData = async (students) => { })) }; - const response = await conductScoreBatchAdd(batchData); - - if (response.code === 200) { - ElMessage.success(`成功保存 ${response.data.successCount} 条记录`); - if (response.data.failCount > 0) { - ElMessage.warning(`失败 ${response.data.failCount} 条记录`); + let response; + if (operationType === 'update') { + // 修改操作:提交审核 + response = await conductScoreBatchUpdate(batchData); + if (response.code === 200) { + ElMessage.success(`成功提交 ${response.data.successCount} 条审核申请`); + if (response.data.failCount > 0) { + ElMessage.warning(`失败 ${response.data.failCount} 条记录`); + } + } + } else { + // 新增操作:直接保存 + response = await conductScoreBatchAdd(batchData); + if (response.code === 200) { + ElMessage.success(`成功保存 ${response.data.successCount} 条记录`); + if (response.data.failCount > 0) { + ElMessage.warning(`失败 ${response.data.failCount} 条记录`); + } } - getList(); } + + getList(); } catch (error) { console.error('保存数据失败:', error); - ElMessage.error('保存失败'); + const operationName = operationType === 'update' ? '提交审核' : '保存'; + ElMessage.error(`${operationName}失败`); } }; @@ -703,7 +842,7 @@ const importUpload = reactive({ }); const confirmBatchModalSave = () => { - // 批量录入弹窗填写的分数/备注将应用到所有已勾选的学生(包含已录入的,视为覆盖) + // 批量录入弹窗:根据分数变化情况决定是否需要审核 const targets = selectedStudents.value || []; if (targets.length === 0) { @@ -717,15 +856,66 @@ const confirmBatchModalSave = () => { return; } - const studentsToSave = targets.map((stu) => ({ - studentId: stu.studentId, - score: batchModalForm.score, - remark: batchModalForm.remark || '' - })); + // 区分需要审核和不需要审核的学生 + const directSaveStudents = []; + const auditStudents = []; + + targets.forEach((stu) => { + const hasExistingScore = stu.existingScore !== null && stu.existingScore !== undefined; + + if (!hasExistingScore) { + // 第一次录入:直接保存 + directSaveStudents.push(stu); + } else { + // 有历史记录:检查分数和备注是否都相同 + const currentScore = Number(batchModalForm.score); + const originalScore = Number(stu.existingScore.score); + const currentRemark = String(batchModalForm.remark || '').trim(); + const originalRemark = String(stu.existingScore.remark || '').trim(); + + const scoreChanged = currentScore !== originalScore; + const remarkChanged = currentRemark !== originalRemark; + + if (!scoreChanged && !remarkChanged) { + // 分数和备注都没有变化:直接保存 + directSaveStudents.push(stu); + } else { + // 分数或备注发生了变化:需要审核 + auditStudents.push(stu); + } + } + }); batchModalVisible.value = false; - // 保存时会覆盖已有分数(后端应支持更新) - saveBatchData(studentsToSave); + + // 分批处理 + if (directSaveStudents.length > 0) { + const directSaveData = directSaveStudents.map((stu) => ({ + studentId: stu.studentId, + score: batchModalForm.score, + remark: batchModalForm.remark || '' + })); + saveBatchData(directSaveData, 'add'); + } + + if (auditStudents.length > 0) { + const auditData = auditStudents.map((stu) => ({ + studentId: stu.studentId, + score: batchModalForm.score, + remark: batchModalForm.remark || '' + })); + saveBatchData(auditData, 'update'); + } + + // 显示操作结果 + let message = '批量操作完成:'; + if (directSaveStudents.length > 0) { + message += `保存 ${directSaveStudents.length} 个学生`; + } + if (auditStudents.length > 0) { + message += `${directSaveStudents.length > 0 ? ',' : ''}提交 ${auditStudents.length} 个修改学生审核`; + } + ElMessage.success(message); }; const handleRefresh = () => { @@ -733,30 +923,66 @@ const handleRefresh = () => { }; const handleSingleInput = (row) => { - // 单条保存:支持覆盖已录入的分数。根据是否已有分数,调整提示文案。 + // 单条保存:根据分数和备注变化情况决定是否需要审核 if (row.score === null || row.score === undefined) { ElMessage.warning('请先输入分数'); return; } - const confirmMessage = row.existingScore - ? `该学生已有分数,确定要覆盖 ${row.studentName} 的分数为 ${row.score} 吗?` - : `确定保存 ${row.studentName} 的分数 ${row.score} 吗?`; + const hasExistingScore = row.existingScore !== null && row.existingScore !== undefined; - ElMessageBox.confirm(confirmMessage, '保存确认', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning' - }).then(() => { - // 传入简化对象以避免多余字段 - saveBatchData([ - { - studentId: row.studentId, - score: row.score, - remark: row.remark || '' - } - ]); - }); + if (!hasExistingScore) { + // 第一次录入:直接保存 + const confirmMessage = `确定保存 ${row.studentName} 的分数 ${row.score} 吗?`; + ElMessageBox.confirm(confirmMessage, '保存确认', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'success' + }).then(() => { + saveBatchData( + [ + { + studentId: row.studentId, + score: row.score, + remark: row.remark || '' + } + ], + 'add' + ); + }); + } else { + // 有历史记录:检查分数和备注是否都相同 + const currentScore = Number(row.score); + const originalScore = Number(row.existingScore.score); + const currentRemark = String(row.remark || '').trim(); + const originalRemark = String(row.existingScore.remark || '').trim(); + + const scoreChanged = currentScore !== originalScore; + const remarkChanged = currentRemark !== originalRemark; + const needsAudit = scoreChanged || remarkChanged; + + const operationName = needsAudit ? '修改审核' : '保存'; + const confirmMessage = needsAudit + ? `该学生${scoreChanged ? '分数' : ''}${scoreChanged && remarkChanged ? '和' : ''}${remarkChanged ? '备注' : ''}发生变化,修改后需要管理员审核。确定要提交 ${row.studentName} 的修改吗?` + : `该学生信息没有变化,确定要保存吗?`; + + ElMessageBox.confirm(confirmMessage, `${operationName}确认`, { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: needsAudit ? 'warning' : 'success' + }).then(() => { + saveBatchData( + [ + { + studentId: row.studentId, + score: row.score, + remark: row.remark || '' + } + ], + needsAudit ? 'update' : 'add' + ); + }); + } }; const handleSelectionChange = (selection) => {