Quellcode durchsuchen

feat(risk-inspectionRecord): 巡检打卡审核模块补全

1. SQL 升级: risk_inspection_record 表加 audit_status/auditor_id/auditor_name/audit_time/audit_opinion 5 字段
2. 字典 seed: audit_status (待审核/审核通过/审核不通过) 3 项
3. 实体: RiskInspectionRecord Do/Vo 加 5 字段
4. 新建 RiskInspectionRecordBo 用于审核接口
5. Service: auditRecord 方法实现 (校验+自动取审核人+同步 record_status)
6. submitPunch 默认 audit_status='待审核'
7. Controller: PUT /risk/inspectionRecord/audit (复用 risk:inspectionRecord:review 权限)
yangjingjing vor 3 Tagen
Ursprung
Commit
a5b483b83f

+ 33 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskInspectionRecordController.java

@@ -1,5 +1,7 @@
 package org.dromara.risk.controller;
 
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.ObjectUtil;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.validate.AddGroup;
@@ -8,12 +10,14 @@ import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.web.core.BaseController;
 import org.dromara.risk.domain.bo.MobilePunchBo;
+import org.dromara.risk.domain.bo.RiskInspectionRecordBo;
 import org.dromara.risk.domain.vo.RiskInspectionRecordVo;
 import org.dromara.risk.service.IRiskInspectionRecordService;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -58,4 +62,33 @@ public class RiskInspectionRecordController extends BaseController {
     public R<RiskInspectionRecordVo> getInfo(@PathVariable("id") Long id) {
         return R.ok(inspectionRecordService.queryById(id));
     }
+
+    /**
+     * 审核巡检打卡记录
+     * <p>
+     * 巡检人提交打卡后默认为"待审核"状态,审核人可调用此接口审核:
+     * <ul>
+     *   <li>auditStatus=审核通过 → record_status 同步标记为"审核通过"</li>
+     *   <li>auditStatus=审核不通过 → 必须填 auditOpinion(不通过原因), record_status 标记为"审核不通过",移动端允许巡检人重新打卡</li>
+     * </ul>
+     *
+     * @param bo 审核参数 (id/auditStatus/auditOpinion/auditorId/auditorName)
+     */
+    @SaCheckPermission("risk:inspectionRecord:review")
+    @Log(title = "巡检打卡审核", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping("/audit")
+    public R<Void> audit(@Validated @RequestBody RiskInspectionRecordBo bo) {
+        if (ObjectUtil.isNull(bo.getId())) {
+            return R.fail("打卡记录ID不能为空");
+        }
+        boolean ok = inspectionRecordService.auditRecord(
+            bo.getId(),
+            bo.getAuditStatus(),
+            bo.getAuditorId(),
+            bo.getAuditorName(),
+            bo.getAuditOpinion()
+        );
+        return ok ? R.ok() : R.fail("审核失败,记录可能不存在或状态不允许");
+    }
 }

+ 25 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskInspectionRecord.java

@@ -140,6 +140,31 @@ public class RiskInspectionRecord extends TenantEntity {
      */
     private String remark;
 
+    /**
+     * 审核状态(待审核/审核通过/审核不通过)
+     */
+    private String auditStatus;
+
+    /**
+     * 审核人ID
+     */
+    private Long auditorId;
+
+    /**
+     * 审核人
+     */
+    private String auditorName;
+
+    /**
+     * 审核时间
+     */
+    private Date auditTime;
+
+    /**
+     * 审核意见/不通过原因
+     */
+    private String auditOpinion;
+
     /**
      * 删除标志
      */

+ 96 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/bo/RiskInspectionRecordBo.java

@@ -0,0 +1,96 @@
+package org.dromara.risk.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.risk.domain.RiskInspectionRecord;
+
+/**
+ * 巡检打卡记录 业务对象 risk_inspection_record
+ * <p>
+ * 主要用于审核接口 (audit),以及其他可选的更新场景。
+ * 移动端提交打卡仍走 {@link MobilePunchBo} + {@code /punch} 接口。
+ *
+ * @author ruoyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = RiskInspectionRecord.class, reverseConvertGenerate = false)
+public class RiskInspectionRecordBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 记录编号
+     */
+    private String recordCode;
+
+    /**
+     * 任务ID
+     */
+    private Long taskId;
+
+    /**
+     * 任务名称
+     */
+    private String taskName;
+
+    /**
+     * 门店ID
+     */
+    private Long storeId;
+
+    /**
+     * 门店名称
+     */
+    private String storeName;
+
+    /**
+     * 打卡人ID
+     */
+    private Long inspectorId;
+
+    /**
+     * 打卡人
+     */
+    private String inspectorName;
+
+    /**
+     * 记录状态(已打卡/未打卡/异常打卡)
+     */
+    private String recordStatus;
+
+    /**
+     * 审核状态(待审核/审核通过/审核不通过)
+     */
+    private String auditStatus;
+
+    /**
+     * 审核人ID
+     */
+    private Long auditorId;
+
+    /**
+     * 审核人
+     */
+    private String auditorName;
+
+    /**
+     * 审核时间
+     */
+    private java.util.Date auditTime;
+
+    /**
+     * 审核意见/不通过原因
+     */
+    private String auditOpinion;
+
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 30 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskInspectionRecordVo.java

@@ -157,6 +157,36 @@ public class RiskInspectionRecordVo implements Serializable {
     @ExcelProperty(value = "备注")
     private String remark;
 
+    /**
+     * 审核状态(待审核/审核通过/审核不通过)
+     */
+    @ExcelProperty(value = "审核状态")
+    private String auditStatus;
+
+    /**
+     * 审核人ID
+     */
+    private Long auditorId;
+
+    /**
+     * 审核人
+     */
+    @ExcelProperty(value = "审核人")
+    private String auditorName;
+
+    /**
+     * 审核时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ExcelProperty(value = "审核时间")
+    private Date auditTime;
+
+    /**
+     * 审核意见/不通过原因
+     */
+    @ExcelProperty(value = "审核意见")
+    private String auditOpinion;
+
     /**
      * 创建时间
      */

+ 12 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskInspectionRecordService.java

@@ -22,4 +22,16 @@ public interface IRiskInspectionRecordService {
      * 移动端-查询单条打卡记录
      */
     RiskInspectionRecordVo queryById(Long id);
+
+    /**
+     * 审核巡检打卡记录
+     *
+     * @param id           打卡记录ID
+     * @param auditStatus  审核结果(待审核/审核通过/审核不通过)
+     * @param auditorId    审核人ID(为空时取当前登录用户)
+     * @param auditorName  审核人姓名
+     * @param auditOpinion 审核意见/不通过原因
+     * @return 是否成功
+     */
+    boolean auditRecord(Long id, String auditStatus, Long auditorId, String auditorName, String auditOpinion);
 }

+ 96 - 1
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionRecordServiceImpl.java

@@ -1,10 +1,13 @@
 package org.dromara.risk.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 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.StringUtils;
 import org.dromara.common.satoken.utils.LoginHelper;
@@ -19,7 +22,9 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.Assert;
 
+import java.util.Arrays;
 import java.util.Date;
+import java.util.List;
 
 /**
  * 巡检打卡记录 业务层实现
@@ -31,6 +36,15 @@ import java.util.Date;
 @Service
 public class RiskInspectionRecordServiceImpl implements IRiskInspectionRecordService {
 
+    /** 审核状态:待审核(巡检人提交后默认) */
+    private static final String AUDIT_PENDING = "待审核";
+    /** 审核状态:审核通过 */
+    private static final String AUDIT_PASS = "审核通过";
+    /** 审核状态:审核不通过 */
+    private static final String AUDIT_REJECT = "审核不通过";
+
+    private static final List<String> AUDIT_STATUSES = Arrays.asList(AUDIT_PENDING, AUDIT_PASS, AUDIT_REJECT);
+
     private final RiskInspectionRecordMapper baseMapper;
     private final RiskInspectionTaskMapper taskMapper;
 
@@ -106,8 +120,9 @@ public class RiskInspectionRecordServiceImpl implements IRiskInspectionRecordSer
         rec.setProblemCount(problem);
         rec.setResult(overallResult);
 
-        // 6. 记录状态
+        // 6. 记录状态 + 审核状态
         rec.setRecordStatus(hasAbnormal ? "异常打卡" : "已打卡");
+        rec.setAuditStatus(AUDIT_PENDING);
 
         // 7. 写主表
         baseMapper.insert(rec);
@@ -141,4 +156,84 @@ public class RiskInspectionRecordServiceImpl implements IRiskInspectionRecordSer
     public RiskInspectionRecordVo queryById(Long id) {
         return baseMapper.selectVoById(id);
     }
+
+    /**
+     * 审核巡检打卡记录
+     * <p>
+     * 流程:
+     * 1. 校验记录存在
+     * 2. 校验 auditStatus 合法 (待审核/审核通过/审核不通过)
+     * 3. 校验:审核不通过时 auditOpinion 不能为空
+     * 4. 设置审核时间=now,审核人=当前登录用户(若未传)
+     * 5. 审计不通过后,巡检记录 recordStatus 同步标记为"审核不通过",
+     *    移动端可基于此状态允许巡检人重新打卡修改
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean auditRecord(Long id, String auditStatus, Long auditorId, String auditorName, String auditOpinion) {
+        Assert.notNull(id, "打卡记录ID不能为空");
+        if (StrUtil.isBlank(auditStatus)) {
+            throw new ServiceException("审核结果不能为空");
+        }
+        if (!AUDIT_STATUSES.contains(auditStatus)) {
+            throw new ServiceException("审核结果不合法,可选值: " + AUDIT_STATUSES);
+        }
+        if (AUDIT_REJECT.equals(auditStatus) && StrUtil.isBlank(auditOpinion)) {
+            throw new ServiceException("审核不通过时必须填写原因");
+        }
+
+        RiskInspectionRecord exist = baseMapper.selectById(id);
+        Assert.notNull(exist, "打卡记录不存在: " + id);
+        if (AUDIT_PENDING.equals(exist.getAuditStatus())) {
+            // 允许从"待审核" -> 通过/不通过
+        } else if (StrUtil.isBlank(exist.getAuditStatus())) {
+            // 兼容存量数据:从未审核状态进入
+        } else {
+            // 已审核过的记录,允许从"审核不通过"重新审核为"审核通过"
+            if (AUDIT_REJECT.equals(exist.getAuditStatus()) && AUDIT_PASS.equals(auditStatus)) {
+                // OK,允许
+            } else {
+                throw new ServiceException("当前审核状态(" + exist.getAuditStatus() + ")不允许再次审核");
+            }
+        }
+
+        // 审核人信息:前端未传则自动取当前登录用户
+        Long finalAuditorId = auditorId;
+        String finalAuditorName = auditorName;
+        try {
+            if (finalAuditorId == null) {
+                finalAuditorId = LoginHelper.getUserId();
+            }
+            if (StrUtil.isBlank(finalAuditorName)) {
+                finalAuditorName = LoginHelper.getUsername();
+            }
+        } catch (Exception ignore) {
+            // 移动端/免登录场景:留空
+        }
+
+        RiskInspectionRecord update = new RiskInspectionRecord();
+        update.setId(id);
+        update.setAuditStatus(auditStatus);
+        update.setAuditorId(finalAuditorId);
+        update.setAuditorName(finalAuditorName);
+        update.setAuditTime(new Date());
+        update.setAuditOpinion(auditOpinion);
+
+        // 同步记录状态:审核不通过时,recordStatus 标记为"审核不通过"
+        // (前端列表按 recordStatus 着色,审核通过/不通过均显眼呈现)
+        if (AUDIT_REJECT.equals(auditStatus)) {
+            update.setRecordStatus("审核不通过");
+        } else if (AUDIT_PASS.equals(auditStatus)) {
+            // 不覆盖已有的"异常打卡/已打卡"等业务状态,仅在原本无意义时设置为"审核通过"
+            if (StrUtil.isBlank(exist.getRecordStatus())
+                || AUDIT_PENDING.equals(exist.getRecordStatus())) {
+                update.setRecordStatus("审核通过");
+            }
+        }
+
+        int row = baseMapper.updateById(update);
+        log.info("巡检审核完成 recordId={} auditStatus={} auditor={} opinion={}",
+            id, auditStatus, finalAuditorName, auditOpinion);
+        return row > 0;
+    }
 }

+ 40 - 0
sql/risk_inspection_record_audit.sql

@@ -0,0 +1,40 @@
+-- =============================================================
+-- 巡检记录 - 审核模块升级 SQL
+-- 说明:
+--   1. risk_inspection_record 表新增审核相关字段(审核状态/审核人/审核时间/审核意见)
+--   2. 字典 seed:audit_status (待审核/审核通过/审核不通过)
+--   3. 已有数据默认 audit_status='待审核'
+-- 适用版本: 5.6.2+
+-- 维护人: yangjingjing
+-- =============================================================
+
+-- 1. 巡检记录表加审核字段
+ALTER TABLE `risk_inspection_record`
+    ADD COLUMN `audit_status`   VARCHAR(20)  DEFAULT '待审核' COMMENT '审核状态(待审核/审核通过/审核不通过)',
+    ADD COLUMN `auditor_id`     BIGINT       DEFAULT NULL    COMMENT '审核人ID',
+    ADD COLUMN `auditor_name`   VARCHAR(50)  DEFAULT NULL    COMMENT '审核人',
+    ADD COLUMN `audit_time`     DATETIME     DEFAULT NULL    COMMENT '审核时间',
+    ADD COLUMN `audit_opinion`  TEXT         COMMENT '审核意见/不通过原因',
+    ADD KEY `idx_audit_status` (`audit_status`);
+
+-- 兼容已存在 audit_status 列的情况:确保 NOT NULL DEFAULT 已生效
+-- (MySQL ALTER ADD COLUMN 已经是 DEFAULT '待审核',存量历史数据会自动回填)
+
+-- 2. 字典 seed: audit_status
+-- 先清空旧 audit_status 字典项避免重复(只清理本批次涉及的 3 项,保留用户自定义项)
+DELETE FROM `sys_dict_data`
+WHERE `dict_type` = 'audit_status'
+  AND `dict_label` IN ('待审核', '审核通过', '审核不通过');
+
+INSERT INTO `sys_dict_data`
+    (`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `remark`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`)
+VALUES
+    (1, '待审核',     '待审核',     'audit_status', '', 'warning',  'N', '巡检人提交后默认状态', 103, 1, NOW(), 1, NOW()),
+    (2, '审核通过',   '审核通过',   'audit_status', '', 'success',  'N', '审核人审核通过',     103, 1, NOW(), 1, NOW()),
+    (3, '审核不通过', '审核不通过', 'audit_status', '', 'danger',   'N', '审核不通过需填原因', 103, 1, NOW(), 1, NOW());
+
+-- 3. 字典类型表 seed(若不存在则补)
+INSERT IGNORE INTO `sys_dict_type`
+    (`dict_name`, `dict_type`, `remark`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`)
+VALUES
+    ('审核状态', 'audit_status', '巡检/作业/人员变动等业务通用审核状态', 103, 1, NOW(), 1, NOW());