Переглянути джерело

feat: 监督抽检/平台客诉模块升级 v2

1. 5 节点流程改造:案件处理/抽检不合格案件处理 → 风险控制/异议复检
2. 案件状态:已闭环 → 归档(显示层统一)
3. 抽检表加 8 字段(条码/批号/生产日期/供应商/抽样数量/检测机构/不合格项目/不合格指标)
4. 客诉表加 6 字段(商品名称/条码/批号/生产日期/供应商/数量)
5. 新增 4 个字典:不合格项目/控制措施/复检结果/调查报告原因
6. SQL 脚本:risk_supervision_v2.sql / risk_supervision_v2_part2.sql / risk_dept_store.sql / risk_role_menu.sql
7. 巡检打卡明细模块:RiskInspectionRecordItem 系列(12 个文件)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
yangjingjing 1 місяць тому
батько
коміт
175dc0a16e
59 змінених файлів з 1794 додано та 381 видалено
  1. 2 1
      ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java
  2. 12 0
      ruoyi-common/ruoyi-common-excel/pom.xml
  3. 5 1
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskFoodSafetyCampaignController.java
  4. 47 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskInspectionRecordItemController.java
  5. 4 1
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskInspectionTaskController.java
  6. 36 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskInspectionTaskItemController.java
  7. 4 1
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskQualityInspectionTaskController.java
  8. 38 2
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskSupervisionCaseController.java
  9. 3 2
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskSupervisionComplaintController.java
  10. 66 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskInspectionRecordItem.java
  11. 67 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskInspectionTaskItem.java
  12. 14 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskSupervisionCaseNode.java
  13. 30 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskSupervisionComplaint.java
  14. 14 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskSupervisionComplaintNode.java
  15. 40 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskSupervisionInspection.java
  16. 12 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/bo/RiskSupervisionCaseBo.java
  17. 30 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/bo/RiskSupervisionComplaintBo.java
  18. 40 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/bo/RiskSupervisionInspectionBo.java
  19. 64 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskInspectionRecordItemVo.java
  20. 70 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskInspectionTaskItemVo.java
  21. 5 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskInspectionTaskVo.java
  22. 2 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskSupervisionCaseVo.java
  23. 21 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskSupervisionComplaintVo.java
  24. 25 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskSupervisionInspectionVo.java
  25. 9 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/SupervisionCaseNodeVo.java
  26. 9 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/SupervisionComplaintNodeVo.java
  27. 20 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/SupervisionStatsVo.java
  28. 59 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/mapper/RiskInspectionRecordItemMapper.java
  29. 54 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/mapper/RiskInspectionTaskItemMapper.java
  30. 5 4
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskFoodSafetyCampaignService.java
  31. 29 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskInspectionRecordItemService.java
  32. 21 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskInspectionTaskItemService.java
  33. 5 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskSupervisionCaseService.java
  34. 5 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskSupervisionComplaintService.java
  35. 5 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskSupervisionInspectionService.java
  36. 11 5
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskFoodSafetyCampaignServiceImpl.java
  37. 53 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionRecordItemServiceImpl.java
  38. 61 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionRecordServiceImpl.java
  39. 31 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionTaskItemServiceImpl.java
  40. 9 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionTaskServiceImpl.java
  41. 16 6
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskSupervisionCaseServiceImpl.java
  42. 28 14
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskSupervisionComplaintServiceImpl.java
  43. 9 0
      ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskSupervisionInspectionServiceImpl.java
  44. 0 7
      ruoyi-modules/ruoyi-risk/src/main/resources/mapper/risk/RiskInspectionItemMapper.xml
  45. 52 0
      ruoyi-modules/ruoyi-risk/src/main/resources/mapper/risk/RiskInspectionRecordItemMapper.xml
  46. 38 0
      ruoyi-modules/ruoyi-risk/src/main/resources/mapper/risk/RiskInspectionTaskItemMapper.xml
  47. 34 0
      sql/risk_dept_store.sql
  48. 2 2
      sql/risk_food_safety_campaign_menu.sql
  49. 31 31
      sql/risk_menu.sql
  50. 0 236
      sql/risk_menu.sql.bak
  51. 2 2
      sql/risk_quality_factory_visit_menu.sql
  52. 2 2
      sql/risk_quality_menu.sql
  53. 25 0
      sql/risk_role_menu.sql
  54. 42 42
      sql/risk_supervision.sql
  55. 245 0
      sql/risk_supervision.sql.bak
  56. 22 22
      sql/risk_supervision_menu.sql
  57. 82 0
      sql/risk_supervision_v2.sql
  58. 70 0
      sql/risk_supervision_v2_part2.sql
  59. 57 0
      sql/risk_tables.sql

+ 2 - 1
ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java

@@ -83,7 +83,8 @@ public class AuthController {
      * @param body 登录信息
      * @return 结果
      */
-    @ApiEncrypt
+    // 注意: 登录是免登录公开接口, 不强制走 ApiEncrypt 加密通道;
+    // 移动端(uniapp)未实现 request 加密, 如果带 @ApiEncrypt 会被 CryptoFilter 拒绝.
     @PostMapping("/login")
     public R<LoginVo> login(@RequestBody String body) {
         LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);

+ 12 - 0
ruoyi-common/ruoyi-common-excel/pom.xml

@@ -25,6 +25,18 @@
             <groupId>cn.idev.excel</groupId>
             <artifactId>fastexcel</artifactId>
         </dependency>
+
+        <!--
+            强制升级 commons-compress 到 1.27.1:
+            fastexcel 依赖的 poi-ooxml 5.4.1 调用了 ZipArchiveOutputStream.putArchiveEntry(ZipArchiveEntry),
+            该方法在 commons-compress 1.26.0+ 才提供;如果被 minio 等传递依赖仲裁到旧版本(如 1.24.0)
+            会导致 Excel 导出时报 NoSuchMethodError,从而在控制器侧表现为 HTTP 500。
+        -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId>
+            <version>1.27.1</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 5 - 1
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskFoodSafetyCampaignController.java

@@ -127,7 +127,11 @@ public class RiskFoodSafetyCampaignController extends BaseController {
         if (StrUtil.isBlank(summary)) {
             return R.fail("活动总结不能为空");
         }
-        boolean ok = campaignService.finishCampaign(id, summary);
+        Integer actualParticipants = bo != null ? bo.getActualParticipants() : null;
+        if (actualParticipants == null || actualParticipants < 0) {
+            return R.fail("实际参与人数不能为空且不能小于 0");
+        }
+        boolean ok = campaignService.finishCampaign(id, summary, actualParticipants);
         return ok ? R.ok() : R.fail("活动结束失败");
     }
 

+ 47 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskInspectionRecordItemController.java

@@ -0,0 +1,47 @@
+package org.dromara.risk.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.risk.domain.vo.RiskInspectionRecordItemVo;
+import org.dromara.risk.service.IRiskInspectionRecordItemService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 巡检打卡记录项目明细
+ * <p>
+ * 提供按 recordId / taskId 查询打卡时每个项目的具体结果
+ *
+ * @author ruoyi
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/risk/inspectionRecordItem")
+public class RiskInspectionRecordItemController extends BaseController {
+
+    private final IRiskInspectionRecordItemService inspectionRecordItemService;
+
+    /**
+     * 按打卡记录ID查询项目明细列表
+     */
+    @SaIgnore
+    @GetMapping("/listByRecord/{recordId}")
+    public R<List<RiskInspectionRecordItemVo>> listByRecord(@PathVariable("recordId") Long recordId) {
+        return R.ok(inspectionRecordItemService.listByRecordId(recordId));
+    }
+
+    /**
+     * 按任务ID查询最近一次打卡的明细列表(供 PC 端任务列表联动用)
+     */
+    @SaIgnore
+    @GetMapping("/listLatestByTask/{taskId}")
+    public R<List<RiskInspectionRecordItemVo>> listLatestByTask(@PathVariable("taskId") Long taskId) {
+        return R.ok(inspectionRecordItemService.listLatestByTaskId(taskId));
+    }
+}

+ 4 - 1
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskInspectionTaskController.java

@@ -16,6 +16,7 @@ import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
 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.web.core.BaseController;
 import org.dromara.risk.domain.bo.RiskInspectionTaskBo;
 import org.dromara.risk.domain.vo.InspectionStatsVo;
@@ -140,7 +141,9 @@ public class RiskInspectionTaskController extends BaseController {
         @RequestParam(required = false) String status,
         PageQuery pageQuery) {
         // 移动端登录态取当前用户作为巡检人,获取"我的巡检任务"
-        Long inspectorId = StpUtil.getLoginIdAsLong();
+        // 注意: 不要用 StpUtil.getLoginIdAsLong(), 框架的 loginId 是 "sys_user:1" 前缀形式,
+        // 直接 parseLong 会抛 NumberFormatException. 应走 LoginHelper.getUserId() 从 session 拿纯数字 id.
+        Long inspectorId = LoginHelper.getUserId();
         return inspectionTaskService.queryMobileList(inspectorId, status, pageQuery);
     }
 

+ 36 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskInspectionTaskItemController.java

@@ -0,0 +1,36 @@
+package org.dromara.risk.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.risk.domain.vo.RiskInspectionTaskItemVo;
+import org.dromara.risk.service.IRiskInspectionTaskItemService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 巡检任务项目
+ *
+ * @author ruoyi
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/risk/inspectionItem")
+public class RiskInspectionTaskItemController extends BaseController {
+
+    private final IRiskInspectionTaskItemService inspectionItemService;
+
+    /**
+     * 按任务ID查询巡检项目列表(任务定义 + 最近一次打卡结果)
+     */
+    @SaIgnore
+    @GetMapping("/listByTask/{taskId}")
+    public R<List<RiskInspectionTaskItemVo>> listByTask(@PathVariable("taskId") Long taskId) {
+        return R.ok(inspectionItemService.listByTaskId(taskId));
+    }
+}

+ 4 - 1
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskQualityInspectionTaskController.java

@@ -15,6 +15,7 @@ import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
 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.web.core.BaseController;
 import org.dromara.risk.domain.bo.RiskInspectionTaskBo;
 import org.dromara.risk.domain.vo.InspectionStatsVo;
@@ -145,7 +146,9 @@ public class RiskQualityInspectionTaskController extends BaseController {
     public TableDataInfo<org.dromara.risk.domain.vo.MobileInspectionTaskVo> mobileList(
         @RequestParam(required = false) String status,
         PageQuery pageQuery) {
-        Long inspectorId = StpUtil.getLoginIdAsLong();
+        // 注意: 不要用 StpUtil.getLoginIdAsLong(), 框架的 loginId 是 "sys_user:1" 前缀形式,
+        // 直接 parseLong 会抛 NumberFormatException. 应走 LoginHelper.getUserId() 从 session 拿纯数字 id.
+        Long inspectorId = LoginHelper.getUserId();
         return inspectionTaskService.queryQualityMobileList(inspectorId, status, pageQuery);
     }
 

+ 38 - 2
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskSupervisionCaseController.java

@@ -28,6 +28,8 @@ import java.util.Arrays;
 import java.util.List;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 
 /**
  * 监督抽检 - 不合格案件
@@ -69,8 +71,9 @@ public class RiskSupervisionCaseController extends BaseController {
     @Log(title = "不合格案件处理-新增", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping
-    public R<Void> add(@Validated(AddGroup.class) @RequestBody RiskSupervisionCaseBo bo) {
-        return toAjax(caseService.insertByBo(bo));
+    public R<Long> add(@Validated(AddGroup.class) @RequestBody RiskSupervisionCaseBo bo) {
+        caseService.insertByBo(bo);
+        return R.ok(bo.getId());
     }
 
     /**
@@ -142,6 +145,39 @@ public class RiskSupervisionCaseController extends BaseController {
         SupervisionStatsVo.MonthPassRate rate = inspectionService.calcMonthPassRate(ym);
         vo.setMonthPassRate(rate);
 
+        // 5) 趋势计算(较前一期增长百分比)
+        SupervisionStatsVo.Trend trend = new SupervisionStatsVo.Trend();
+        // 今日待审核抽检 较前日
+        long yesterdayPending = inspectionService.countYesterdayPending();
+        trend.setTodayPendingPct(calcPctChange(todayPending, yesterdayPending));
+        // 不合格待处理案件 较前日
+        long yesterdayCases = caseService.countYesterdayProcessing();
+        trend.setPendingCasesPct(calcPctChange(pendingCases, yesterdayCases));
+        // 待处理客诉移交 较前日
+        long yesterdayComplaints = complaintService.countYesterdayProcessing();
+        trend.setPendingComplaintsPct(calcPctChange(pendingComplaints, yesterdayComplaints));
+        // 本月抽检合格率 较上月(百分点变化, 不是百分比)
+        String lastYm = LocalDate.now().minusMonths(1).format(DateTimeFormatter.ofPattern("yyyy-MM"));
+        SupervisionStatsVo.MonthPassRate lastRate = inspectionService.calcMonthPassRate(lastYm);
+        if (rate.getPassRate() != null && lastRate.getPassRate() != null) {
+            BigDecimal delta = BigDecimal.valueOf(rate.getPassRate() - lastRate.getPassRate())
+                .setScale(2, RoundingMode.HALF_UP);
+            trend.setMonthPassRateDelta(delta.doubleValue());
+        }
+        vo.setTrend(trend);
+
         return R.ok(vo);
     }
+
+    /**
+     * 计算变化百分比(新-旧)/旧 * 100,保留 2 位小数。
+     * 旧值=0 且新值>0 → 100.0(增长 100%);旧值=0 且新值=0 → 0.0;旧值>0 且新值=0 → -100.0
+     */
+    private static double calcPctChange(long cur, long prev) {
+        if (prev == 0L) {
+            return cur > 0 ? 100.0 : 0.0;
+        }
+        double diff = (cur - prev) * 100.0 / prev;
+        return BigDecimal.valueOf(diff).setScale(2, RoundingMode.HALF_UP).doubleValue();
+    }
 }

+ 3 - 2
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskSupervisionComplaintController.java

@@ -62,8 +62,9 @@ public class RiskSupervisionComplaintController extends BaseController {
     @Log(title = "平台客诉处理-新增", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping
-    public R<Void> add(@Validated(AddGroup.class) @RequestBody RiskSupervisionComplaintBo bo) {
-        return toAjax(complaintService.insertByBo(bo));
+    public R<Long> add(@Validated(AddGroup.class) @RequestBody RiskSupervisionComplaintBo bo) {
+        complaintService.insertByBo(bo);
+        return R.ok(bo.getId());
     }
 
     /**

+ 66 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskInspectionRecordItem.java

@@ -0,0 +1,66 @@
+package org.dromara.risk.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 巡检打卡记录项目明细对象 risk_inspection_record_item
+ * <p>
+ * 每次打卡时,巡检人对每个项目的具体结果(正常/异常/原因/照片)写入此表
+ * 区别于 risk_inspection_task_item(任务级项目定义,可被多次打卡复用)
+ *
+ * @author ruoyi
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("risk_inspection_record_item")
+public class RiskInspectionRecordItem extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    @TableId(value = "id")
+    private Long id;
+
+    /** 打卡记录ID */
+    private Long recordId;
+
+    /** 任务ID */
+    private Long taskId;
+
+    /** 巡检项目定义ID(可空) */
+    private Long itemId;
+
+    /** 项目大类 */
+    private String itemCategory;
+
+    /** 巡检内容 */
+    private String itemContent;
+
+    /** 巡检标准 */
+    private String itemStandard;
+
+    /** 巡检结果(0正常/1异常) */
+    private String checkResult;
+
+    /** 问题数(0/1) */
+    private Integer problemCount;
+
+    /** 备注/异常原因 */
+    private String remark;
+
+    /** 照片(逗号分隔) */
+    private String photos;
+
+    /** 删除标志 */
+    @TableLogic
+    private String delFlag;
+}

+ 67 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskInspectionTaskItem.java

@@ -0,0 +1,67 @@
+package org.dromara.risk.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 巡检任务项目定义对象 risk_inspection_task_item
+ * <p>
+ * 任务级别的巡检项目定义(每个巡检任务包含哪些巡检项目),
+ * APP 端提交打卡时可基于此表生成项目卡片,实际打卡结果写入 risk_inspection_record_item
+ *
+ * @author ruoyi
+ */
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("risk_inspection_task_item")
+public class RiskInspectionTaskItem extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    @TableId(value = "id")
+    private Long id;
+
+    /** 任务ID */
+    private Long taskId;
+
+    /** 项目大类(同字典 risk_inspection_item_type) */
+    private String itemCategory;
+
+    /** 巡检内容 */
+    private String itemContent;
+
+    /** 巡检标准 */
+    private String itemStandard;
+
+    /** 是否需要拍照(0否 1是) */
+    private String needPhoto;
+
+    /** 排序 */
+    private Integer sortOrder;
+
+    /** 最近一次打卡结果(0正常/1异常),由 Service 在最近打卡时回填 */
+    private String checkResult;
+
+    /** 最近一次打卡备注/异常原因 */
+    private String remark;
+
+    /** 问题数(固定 0/1) */
+    private Integer problemCount;
+
+    /** 最近一次打卡时间 */
+    private Date checkTime;
+
+    /** 删除标志 */
+    @TableLogic
+    private String delFlag;
+}

+ 14 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskSupervisionCaseNode.java

@@ -2,6 +2,7 @@ package org.dromara.risk.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 import org.dromara.common.tenant.core.TenantEntity;
 
@@ -15,6 +16,7 @@ import java.util.Date;
  * @author ruoyi
  */
 @Data
+@EqualsAndHashCode(callSuper = true)
 @NoArgsConstructor
 @TableName("risk_supervision_case_node")
 public class RiskSupervisionCaseNode extends TenantEntity {
@@ -45,6 +47,18 @@ public class RiskSupervisionCaseNode extends TenantEntity {
      */
     private String opinion;
 
+    /**
+     * 节点扩展数据(JSON,按节点类型存不同字段)
+     * <p>
+     * 1-案件处理: {riskLevel, riskDesc, controlMeasure, handlerId, handlerName, attachmentUrl, attachmentName, opinion}
+     * 2-抽检不合格案件处理: {reviewReason, applyDate, reviewOrg, sampleInfo, sendRecord, reviewResult, reviewConclusion, reportUrl, reportName, opinion}
+     * 3-调查取证: {producerQualUrl, producerQualName, productQualUrl, productQualName, inventoryUrl, inventoryName, opinion}
+     * 4-调查报告: {reason, responsible, lossList, investigation, attachmentUrl, attachmentName, opinion}
+     * 5-闭环管理: {causeAnalysis, rectification, opinion}
+     * </p>
+     */
+    private String extraData;
+
     /**
      * 凭证附件URL(MinIO)
      */

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

@@ -50,6 +50,36 @@ public class RiskSupervisionComplaint extends TenantEntity {
      */
     private String complainantPhone;
 
+    /**
+     * 客诉商品名称
+     */
+    private String productName;
+
+    /**
+     * 客诉商品条码
+     */
+    private String productBarcode;
+
+    /**
+     * 客诉商品批号
+     */
+    private String lotNo;
+
+    /**
+     * 客诉商品生产日期
+     */
+    private Date productionDate;
+
+    /**
+     * 客诉商品供应商
+     */
+    private String supplier;
+
+    /**
+     * 客诉数量
+     */
+    private Integer quantity;
+
     /**
      * 涉事门店ID
      */

+ 14 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskSupervisionComplaintNode.java

@@ -2,6 +2,7 @@ package org.dromara.risk.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 import org.dromara.common.tenant.core.TenantEntity;
 
@@ -15,6 +16,7 @@ import java.util.Date;
  * @author ruoyi
  */
 @Data
+@EqualsAndHashCode(callSuper = true)
 @NoArgsConstructor
 @TableName("risk_supervision_complaint_node")
 public class RiskSupervisionComplaintNode extends TenantEntity {
@@ -45,6 +47,18 @@ public class RiskSupervisionComplaintNode extends TenantEntity {
      */
     private String opinion;
 
+    /**
+     * 节点扩展数据(JSON,按节点类型存不同字段)
+     * <p>
+     * 1-案件处理: {riskLevel, riskDesc, controlMeasure, handlerId, handlerName, attachmentUrl, attachmentName, opinion}
+     * 2-抽检不合格案件处理: {reviewReason, applyDate, reviewOrg, sampleInfo, sendRecord, reviewResult, reviewConclusion, reportUrl, reportName, opinion}
+     * 3-调查取证: {producerQualUrl, producerQualName, productQualUrl, productQualName, inventoryUrl, inventoryName, opinion}
+     * 4-调查报告: {reason, responsible, lossList, investigation, attachmentUrl, attachmentName, opinion}
+     * 5-闭环管理: {causeAnalysis, rectification, opinion}
+     * </p>
+     */
+    private String extraData;
+
     /**
      * 凭证附件URL
      */

+ 40 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskSupervisionInspection.java

@@ -55,6 +55,46 @@ public class RiskSupervisionInspection extends TenantEntity {
      */
     private String productName;
 
+    /**
+     * 抽检商品条码
+     */
+    private String productBarcode;
+
+    /**
+     * 抽检商品批号
+     */
+    private String lotNo;
+
+    /**
+     * 抽检商品生产日期
+     */
+    private Date productionDate;
+
+    /**
+     * 抽检商品供应商
+     */
+    private String supplier;
+
+    /**
+     * 抽样数量
+     */
+    private Integer sampleQuantity;
+
+    /**
+     * 检测机构
+     */
+    private String detectOrg;
+
+    /**
+     * 不合格项目(字典:risk_supervision_fail_item)
+     */
+    private String failItem;
+
+    /**
+     * 不合格指标数值
+     */
+    private String failValue;
+
     /**
      * 抽检结果(字典:抽检合格/抽检不合格)
      */

+ 12 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/bo/RiskSupervisionCaseBo.java

@@ -83,4 +83,16 @@ public class RiskSupervisionCaseBo extends BaseEntity {
 
     /** 凭证附件原始文件名 */
     private String attachmentName;
+
+    /**
+     * 节点扩展数据(JSON字符串,按节点类型存不同字段)
+     * <p>
+     * 1-案件处理: {riskLevel, riskDesc, controlMeasure, handlerId, handlerName, attachmentUrl, attachmentName, opinion}
+     * 2-抽检不合格案件处理: {reviewReason, applyDate, reviewOrg, sampleInfo, sendRecord, reviewResult, reviewConclusion, reportUrl, reportName, opinion}
+     * 3-调查取证: {producerQualUrl/Name, productQualUrl/Name, inventoryUrl/Name, opinion}
+     * 4-调查报告: {reason, responsible, lossList, investigation, attachmentUrl/Name, opinion}
+     * 5-闭环管理: {causeAnalysis, rectification, opinion}
+     * </p>
+     */
+    private String extraData;
 }

+ 30 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/bo/RiskSupervisionComplaintBo.java

@@ -35,6 +35,24 @@ public class RiskSupervisionComplaintBo extends BaseEntity {
     /** 投诉人电话 */
     private String complainantPhone;
 
+    /** 客诉商品名称 */
+    private String productName;
+
+    /** 客诉商品条码 */
+    private String productBarcode;
+
+    /** 客诉商品批号 */
+    private String lotNo;
+
+    /** 客诉商品生产日期 */
+    private Date productionDate;
+
+    /** 客诉商品供应商 */
+    private String supplier;
+
+    /** 客诉数量 */
+    private Integer quantity;
+
     /** 涉事门店ID */
     private Long storeId;
 
@@ -80,4 +98,16 @@ public class RiskSupervisionComplaintBo extends BaseEntity {
 
     /** 凭证附件原始文件名(处理环节上传) */
     private String processAttachmentName;
+
+    /**
+     * 节点扩展数据(JSON字符串,按节点类型存不同字段)
+     * <p>
+     * 1-案件处理: {riskLevel, riskDesc, controlMeasure, handlerId, handlerName, attachmentUrl, attachmentName, opinion}
+     * 2-抽检不合格案件处理: {reviewReason, applyDate, reviewOrg, sampleInfo, sendRecord, reviewResult, reviewConclusion, reportUrl, reportName, opinion}
+     * 3-调查取证: {producerQualUrl/Name, productQualUrl/Name, inventoryUrl/Name, opinion}
+     * 4-调查报告: {reason, responsible, lossList, investigation, attachmentUrl/Name, opinion}
+     * 5-闭环管理: {causeAnalysis, rectification, opinion}
+     * </p>
+     */
+    private String extraData;
 }

+ 40 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/bo/RiskSupervisionInspectionBo.java

@@ -48,6 +48,46 @@ public class RiskSupervisionInspectionBo extends BaseEntity {
      */
     private String productName;
 
+    /**
+     * 抽检商品条码
+     */
+    private String productBarcode;
+
+    /**
+     * 抽检商品批号
+     */
+    private String lotNo;
+
+    /**
+     * 抽检商品生产日期
+     */
+    private Date productionDate;
+
+    /**
+     * 抽检商品供应商
+     */
+    private String supplier;
+
+    /**
+     * 抽样数量
+     */
+    private Integer sampleQuantity;
+
+    /**
+     * 检测机构
+     */
+    private String detectOrg;
+
+    /**
+     * 不合格项目(字典:risk_supervision_fail_item)
+     */
+    private String failItem;
+
+    /**
+     * 不合格指标数值
+     */
+    private String failValue;
+
     /**
      * 抽检结果(字典:抽检合格/抽检不合格)
      */

+ 64 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskInspectionRecordItemVo.java

@@ -0,0 +1,64 @@
+package org.dromara.risk.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.risk.domain.RiskInspectionRecordItem;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 巡检打卡记录项目明细视图对象
+ * <p>
+ * 用于 PC 端详情弹窗渲染最近一次打卡的每个项目结果
+ *
+ * @author ruoyi
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = RiskInspectionRecordItem.class)
+public class RiskInspectionRecordItemVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 打卡记录ID */
+    private Long recordId;
+
+    /** 任务ID */
+    private Long taskId;
+
+    /** 巡检项目定义ID */
+    private Long itemId;
+
+    /** 项目大类 */
+    private String itemCategory;
+
+    /** 项目大类(用于 PC 详情弹窗,等同 itemCategory) */
+    private String itemType;
+
+    /** 巡检内容 */
+    private String itemContent;
+
+    /** 巡检标准 */
+    private String itemStandard;
+
+    /** 巡检结果(0正常/1异常) */
+    private String checkResult;
+
+    /** 问题数(0/1) */
+    private Integer problemCount;
+
+    /** 备注/异常原因 */
+    private String remark;
+
+    /** 照片URL(逗号分隔) */
+    private String photos;
+
+    /** 照片(别名,前端可能用 checkPhotos) */
+    private String checkPhotos;
+}

+ 70 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskInspectionTaskItemVo.java

@@ -0,0 +1,70 @@
+package org.dromara.risk.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.risk.domain.RiskInspectionTaskItem;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 巡检任务项目视图对象
+ * <p>
+ * 用于 PC 端列表/详情以及 APP 端任务详情展示
+ *
+ * @author ruoyi
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = RiskInspectionTaskItem.class)
+public class RiskInspectionTaskItemVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 任务ID */
+    private Long taskId;
+
+    /** 项目大类 */
+    private String itemCategory;
+
+    /** 项目名称(项目大类 + 巡检内容) */
+    private String itemName;
+
+    /** 项目大类(用于 PC 详情弹窗) */
+    private String itemType;
+
+    /** 巡检内容 */
+    private String itemContent;
+
+    /** 巡检标准 */
+    private String itemStandard;
+
+    /** 是否需要拍照 */
+    private String needPhoto;
+
+    /** 排序 */
+    private Integer sortOrder;
+
+    /** 巡检结果(0正常/1异常) */
+    private String checkResult;
+
+    /** 备注/异常原因 */
+    private String remark;
+
+    /** 问题数(0/1) */
+    private Integer problemCount;
+
+    /** 照片(逗号分隔) */
+    private String photos;
+
+    /** 最近打卡时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date checkTime;
+}

+ 5 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskInspectionTaskVo.java

@@ -8,6 +8,7 @@ import java.io.Serial;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.util.Date;
+import java.util.List;
 import lombok.Data;
 import org.dromara.common.excel.annotation.ExcelDictFormat;
 import org.dromara.common.excel.convert.ExcelDictConvert;
@@ -254,4 +255,8 @@ public class RiskInspectionTaskVo implements Serializable {
      */
     private Integer totalCount;
 
+    /**
+     * 最近一次打卡的巡检项目明细(由 Service fillLatestRecord 从 risk_inspection_task_item 填充)
+     */
+    private List<RiskInspectionTaskItemVo> items;
 }

+ 2 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskSupervisionCaseVo.java

@@ -1,5 +1,6 @@
 package org.dromara.risk.domain.vo;
 
+import cn.idev.excel.annotation.ExcelIgnore;
 import cn.idev.excel.annotation.ExcelProperty;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import io.github.linpeilie.annotations.AutoMapper;
@@ -85,5 +86,6 @@ public class RiskSupervisionCaseVo implements Serializable {
     /**
      * 历史节点处理记录(仅详情接口填充)
      */
+    @ExcelIgnore
     private List<SupervisionCaseNodeVo> nodeRecords;
 }

+ 21 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/RiskSupervisionComplaintVo.java

@@ -1,5 +1,6 @@
 package org.dromara.risk.domain.vo;
 
+import cn.idev.excel.annotation.ExcelIgnore;
 import cn.idev.excel.annotation.ExcelProperty;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import io.github.linpeilie.annotations.AutoMapper;
@@ -38,6 +39,25 @@ public class RiskSupervisionComplaintVo implements Serializable {
     @ExcelProperty(value = "投诉人电话")
     private String complainantPhone;
 
+    @ExcelProperty(value = "客诉商品名称")
+    private String productName;
+
+    @ExcelProperty(value = "客诉商品条码")
+    private String productBarcode;
+
+    @ExcelProperty(value = "客诉商品批号")
+    private String lotNo;
+
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @ExcelProperty(value = "客诉商品生产日期")
+    private Date productionDate;
+
+    @ExcelProperty(value = "客诉商品供应商")
+    private String supplier;
+
+    @ExcelProperty(value = "客诉数量")
+    private Integer quantity;
+
     @ExcelProperty(value = "涉事门店ID")
     private Long storeId;
 
@@ -83,5 +103,6 @@ public class RiskSupervisionComplaintVo implements Serializable {
     /**
      * 历史节点处理记录(仅详情接口填充)
      */
+    @ExcelIgnore
     private List<SupervisionComplaintNodeVo> nodeRecords;
 }

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

@@ -41,6 +41,31 @@ public class RiskSupervisionInspectionVo implements Serializable {
     @ExcelProperty(value = "抽检商品")
     private String productName;
 
+    @ExcelProperty(value = "抽检商品条码")
+    private String productBarcode;
+
+    @ExcelProperty(value = "抽检商品批号")
+    private String lotNo;
+
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @ExcelProperty(value = "生产日期")
+    private Date productionDate;
+
+    @ExcelProperty(value = "供应商")
+    private String supplier;
+
+    @ExcelProperty(value = "抽样数量")
+    private Integer sampleQuantity;
+
+    @ExcelProperty(value = "检测机构")
+    private String detectOrg;
+
+    @ExcelProperty(value = "不合格项目")
+    private String failItem;
+
+    @ExcelProperty(value = "不合格指标数值")
+    private String failValue;
+
     @ExcelProperty(value = "抽检结果")
     private String inspectionResult;
 

+ 9 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/SupervisionCaseNodeVo.java

@@ -1,7 +1,9 @@
 package org.dromara.risk.domain.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
 import lombok.Data;
+import org.dromara.risk.domain.RiskSupervisionCaseNode;
 
 import java.io.Serial;
 import java.io.Serializable;
@@ -13,6 +15,7 @@ import java.util.Date;
  * @author ruoyi
  */
 @Data
+@AutoMapper(target = RiskSupervisionCaseNode.class)
 public class SupervisionCaseNodeVo implements Serializable {
 
     @Serial
@@ -33,6 +36,12 @@ public class SupervisionCaseNodeVo implements Serializable {
     /** 当前节点处理意见 */
     private String opinion;
 
+    /**
+     * 节点扩展数据(JSON,按节点类型存不同字段)
+     * <p>1-案件处理 / 2-抽检不合格案件处理 / 3-调查取证 / 4-调查报告 / 5-闭环管理</p>
+     */
+    private String extraData;
+
     /** 凭证附件URL(MinIO) */
     private String attachmentUrl;
 

+ 9 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/SupervisionComplaintNodeVo.java

@@ -1,7 +1,9 @@
 package org.dromara.risk.domain.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
+import io.github.linpeilie.annotations.AutoMapper;
 import lombok.Data;
+import org.dromara.risk.domain.RiskSupervisionComplaintNode;
 
 import java.io.Serial;
 import java.io.Serializable;
@@ -13,6 +15,7 @@ import java.util.Date;
  * @author ruoyi
  */
 @Data
+@AutoMapper(target = RiskSupervisionComplaintNode.class)
 public class SupervisionComplaintNodeVo implements Serializable {
 
     @Serial
@@ -33,6 +36,12 @@ public class SupervisionComplaintNodeVo implements Serializable {
     /** 当前节点处理意见 */
     private String opinion;
 
+    /**
+     * 节点扩展数据(JSON,按节点类型存不同字段)
+     * <p>1-案件处理 / 2-抽检不合格案件处理 / 3-调查取证 / 4-调查报告 / 5-闭环管理</p>
+     */
+    private String extraData;
+
     /** 凭证附件URL */
     private String attachmentUrl;
 

+ 20 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/SupervisionStatsVo.java

@@ -30,6 +30,9 @@ public class SupervisionStatsVo implements Serializable {
     /** 本月抽检合格率(分子分母 + 百分比) */
     private MonthPassRate monthPassRate;
 
+    /** 趋势信息(较前一期增长百分比) */
+    private Trend trend;
+
     /**
      * 本月抽检合格率明细
      */
@@ -46,4 +49,21 @@ public class SupervisionStatsVo implements Serializable {
         /** 合格率(0-100, 保留 2 位小数) */
         private Double passRate;
     }
+
+    /**
+     * 趋势信息(较前一期增长百分比)
+     */
+    @Data
+    public static class Trend implements Serializable {
+        @Serial
+        private static final long serialVersionUID = 1L;
+        /** 今日待审核抽检 较前日变化百分比(12.0=+12%) */
+        private Double todayPendingPct;
+        /** 不合格待处理案件 较前日变化百分比 */
+        private Double pendingCasesPct;
+        /** 待处理客诉移交 较前日变化百分比 */
+        private Double pendingComplaintsPct;
+        /** 本月抽检合格率 较上月变化百分点(2.3=+2.3 个百分点) */
+        private Double monthPassRateDelta;
+    }
 }

+ 59 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/mapper/RiskInspectionRecordItemMapper.java

@@ -0,0 +1,59 @@
+package org.dromara.risk.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.risk.domain.RiskInspectionRecordItem;
+import org.dromara.risk.domain.vo.RiskInspectionRecordItemVo;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 巡检打卡记录项目明细 数据层
+ *
+ * @author ruoyi
+ */
+public interface RiskInspectionRecordItemMapper extends BaseMapperPlus<RiskInspectionRecordItem, RiskInspectionRecordItemVo> {
+
+    /**
+     * 按打卡记录ID查询项目明细列表(按 id 升序保持与提交顺序一致)
+     */
+    List<RiskInspectionRecordItemVo> selectVoListByRecordId(Long recordId);
+
+    /**
+     * 按任务ID查询项目明细(取最新一条 record 的明细)
+     */
+    List<RiskInspectionRecordItemVo> selectVoListByTaskIdLatest(Long taskId);
+
+    @Override
+    default <P extends IPage<RiskInspectionRecordItemVo>> P selectVoPage(IPage<RiskInspectionRecordItem> page, Wrapper<RiskInspectionRecordItem> wrapper) {
+        return selectVoPage(page, wrapper, this.currentVoClass());
+    }
+
+    @Override
+    default List<RiskInspectionRecordItemVo> selectVoList(Wrapper<RiskInspectionRecordItem> wrapper) {
+        return selectVoList(wrapper, this.currentVoClass());
+    }
+
+    @Override
+    default RiskInspectionRecordItemVo selectVoById(Serializable id) {
+        return selectVoById(id, this.currentVoClass());
+    }
+
+    @Override
+    default List<RiskInspectionRecordItemVo> selectVoByIds(Collection<? extends Serializable> idList) {
+        return selectVoByIds(idList, this.currentVoClass());
+    }
+
+    @Override
+    default RiskInspectionRecordItemVo selectVoOne(Wrapper<RiskInspectionRecordItem> wrapper) {
+        return selectVoOne(wrapper, this.currentVoClass());
+    }
+
+    @Override
+    int updateById(@Param(Constants.ENTITY) RiskInspectionRecordItem entity);
+}

+ 54 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/mapper/RiskInspectionTaskItemMapper.java

@@ -0,0 +1,54 @@
+package org.dromara.risk.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.risk.domain.RiskInspectionTaskItem;
+import org.dromara.risk.domain.vo.RiskInspectionTaskItemVo;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 巡检任务项目 数据层
+ *
+ * @author ruoyi
+ */
+public interface RiskInspectionTaskItemMapper extends BaseMapperPlus<RiskInspectionTaskItem, RiskInspectionTaskItemVo> {
+
+    /**
+     * 按任务ID查询巡检项目列表(包含最近一次打卡结果)
+     */
+    List<RiskInspectionTaskItemVo> selectVoListByTaskId(Long taskId);
+
+    @Override
+    default <P extends IPage<RiskInspectionTaskItemVo>> P selectVoPage(IPage<RiskInspectionTaskItem> page, Wrapper<RiskInspectionTaskItem> wrapper) {
+        return selectVoPage(page, wrapper, this.currentVoClass());
+    }
+
+    @Override
+    default List<RiskInspectionTaskItemVo> selectVoList(Wrapper<RiskInspectionTaskItem> wrapper) {
+        return selectVoList(wrapper, this.currentVoClass());
+    }
+
+    @Override
+    default RiskInspectionTaskItemVo selectVoById(Serializable id) {
+        return selectVoById(id, this.currentVoClass());
+    }
+
+    @Override
+    default List<RiskInspectionTaskItemVo> selectVoByIds(Collection<? extends Serializable> idList) {
+        return selectVoByIds(idList, this.currentVoClass());
+    }
+
+    @Override
+    default RiskInspectionTaskItemVo selectVoOne(Wrapper<RiskInspectionTaskItem> wrapper) {
+        return selectVoOne(wrapper, this.currentVoClass());
+    }
+
+    @Override
+    int updateById(@Param(Constants.ENTITY) RiskInspectionTaskItem entity);
+}

+ 5 - 4
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskFoodSafetyCampaignService.java

@@ -58,13 +58,14 @@ public interface IRiskFoodSafetyCampaignService {
     /**
      * 活动结束(状态:进行中 → 已结束)
      * <p>
-     * 仅"进行中"状态可调用此接口。需传入活动总结,同步填充 end_real_time。
+     * 仅"进行中"状态可调用此接口。需传入活动总结和实际参与人数,同步填充 end_real_time。
      *
-     * @param id      活动ID
-     * @param summary 活动总结(必填)
+     * @param id                  活动ID
+     * @param summary             活动总结(必填)
+     * @param actualParticipants  实际参与人数(必填,业务规范要求结束活动时必须录入)
      * @return 是否成功
      */
-    boolean finishCampaign(Long id, String summary);
+    boolean finishCampaign(Long id, String summary, Integer actualParticipants);
 
     /**
      * 查询统计数据(看板)

+ 29 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskInspectionRecordItemService.java

@@ -0,0 +1,29 @@
+package org.dromara.risk.service;
+
+import org.dromara.risk.domain.RiskInspectionRecordItem;
+import org.dromara.risk.domain.vo.RiskInspectionRecordItemVo;
+
+import java.util.List;
+
+/**
+ * 巡检打卡记录项目明细 Service 接口
+ *
+ * @author ruoyi
+ */
+public interface IRiskInspectionRecordItemService {
+
+    /**
+     * 按打卡记录ID查询项目明细列表
+     */
+    List<RiskInspectionRecordItemVo> listByRecordId(Long recordId);
+
+    /**
+     * 按任务ID查询最新一次打卡的明细列表
+     */
+    List<RiskInspectionRecordItemVo> listLatestByTaskId(Long taskId);
+
+    /**
+     * 批量插入(由 submitPunch 调用)
+     */
+    void batchInsert(List<RiskInspectionRecordItem> items);
+}

+ 21 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskInspectionTaskItemService.java

@@ -0,0 +1,21 @@
+package org.dromara.risk.service;
+
+import org.dromara.risk.domain.vo.RiskInspectionTaskItemVo;
+
+import java.util.List;
+
+/**
+ * 巡检任务项目 Service 接口
+ *
+ * @author ruoyi
+ */
+public interface IRiskInspectionTaskItemService {
+
+    /**
+     * 按任务ID查询巡检项目列表(任务定义 + 最近一次打卡结果)
+     *
+     * @param taskId 任务ID
+     * @return 项目列表
+     */
+    List<RiskInspectionTaskItemVo> listByTaskId(Long taskId);
+}

+ 5 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskSupervisionCaseService.java

@@ -53,4 +53,9 @@ public interface IRiskSupervisionCaseService {
      * 统计处置中案件数(用于顶部看板)
      */
     long countProcessing();
+
+    /**
+     * 统计昨日创建的处置中案件数(用于趋势对比)
+     */
+    long countYesterdayProcessing();
 }

+ 5 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskSupervisionComplaintService.java

@@ -53,4 +53,9 @@ public interface IRiskSupervisionComplaintService {
      * 统计处置中客诉数(用于顶部看板)
      */
     long countProcessing();
+
+    /**
+     * 统计昨日创建的处置中客诉数(用于趋势对比)
+     */
+    long countYesterdayProcessing();
 }

+ 5 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/IRiskSupervisionInspectionService.java

@@ -55,6 +55,11 @@ public interface IRiskSupervisionInspectionService {
      */
     long countTodayPending();
 
+    /**
+     * 统计昨日抽检单据总量(用于趋势对比)
+     */
+    long countYesterdayPending();
+
     /**
      * 计算指定月份的抽检合格率(用于顶部看板)
      *

+ 11 - 5
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskFoodSafetyCampaignServiceImpl.java

@@ -154,7 +154,7 @@ public class RiskFoodSafetyCampaignServiceImpl implements IRiskFoodSafetyCampaig
     }
 
     @Override
-    public boolean finishCampaign(Long id, String summary) {
+    public boolean finishCampaign(Long id, String summary, Integer actualParticipants) {
         Assert.notNull(id, "活动ID不能为空");
         if (StrUtil.isBlank(summary)) {
             throw new ServiceException("活动总结不能为空");
@@ -170,8 +170,13 @@ public class RiskFoodSafetyCampaignServiceImpl implements IRiskFoodSafetyCampaig
         update.setStatus(STATUS_FINISHED);
         update.setEndRealTime(new Date());
         update.setSummary(summary);
+        // 持久化实际参与人数(业务规范要求结束活动时必须录入)
+        if (actualParticipants != null) {
+            update.setActualParticipants(actualParticipants);
+        }
         int row = baseMapper.updateById(update);
-        log.info("活动结束成功 id={} campaignCode={}", id, campaign.getCampaignCode());
+        log.info("活动结束成功 id={} campaignCode={} actualParticipants={}",
+            id, campaign.getCampaignCode(), update.getActualParticipants());
         return row > 0;
     }
 
@@ -193,10 +198,11 @@ public class RiskFoodSafetyCampaignServiceImpl implements IRiskFoodSafetyCampaig
         Long totalCount = baseMapper.selectCount(Wrappers.<RiskFoodSafetyCampaign>lambdaQuery());
         vo.setTotalCount(totalCount);
 
-        // 2. 参与人次(所有活动累计参与总人数求和)
+        // 2. 参与人次(全部活动参与总人数求和,包括未开始/进行中/已结束)
+// 规范:参与人次 = 所有活动累计参与总人数求和(不限状态)
         Long participantsTotal = baseMapper.selectList(Wrappers.<RiskFoodSafetyCampaign>lambdaQuery())
-            .stream().filter(c -> c.getActualParticipants() != null)
-            .mapToLong(RiskFoodSafetyCampaign::getActualParticipants).sum();
+            .stream().map(c -> c.getActualParticipants() == null ? 0 : c.getActualParticipants())
+            .mapToLong(Integer::longValue).sum();
         vo.setParticipantsTotal(participantsTotal);
 
         // 3. 宣传资料数量(全部上传资料总数)

+ 53 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionRecordItemServiceImpl.java

@@ -0,0 +1,53 @@
+package org.dromara.risk.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import org.dromara.risk.domain.RiskInspectionRecordItem;
+import org.dromara.risk.domain.vo.RiskInspectionRecordItemVo;
+import org.dromara.risk.mapper.RiskInspectionRecordItemMapper;
+import org.dromara.risk.service.IRiskInspectionRecordItemService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 巡检打卡记录项目明细 Service 实现
+ *
+ * @author ruoyi
+ */
+@RequiredArgsConstructor
+@Service
+public class RiskInspectionRecordItemServiceImpl implements IRiskInspectionRecordItemService {
+
+    private final RiskInspectionRecordItemMapper baseMapper;
+
+    @Override
+    public List<RiskInspectionRecordItemVo> listByRecordId(Long recordId) {
+        if (ObjectUtil.isNull(recordId)) {
+            return Collections.emptyList();
+        }
+        return baseMapper.selectVoListByRecordId(recordId);
+    }
+
+    @Override
+    public List<RiskInspectionRecordItemVo> listLatestByTaskId(Long taskId) {
+        if (ObjectUtil.isNull(taskId)) {
+            return Collections.emptyList();
+        }
+        return baseMapper.selectVoListByTaskIdLatest(taskId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void batchInsert(List<RiskInspectionRecordItem> items) {
+        if (CollUtil.isEmpty(items)) {
+            return;
+        }
+        for (RiskInspectionRecordItem item : items) {
+            baseMapper.insert(item);
+        }
+    }
+}

+ 61 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionRecordServiceImpl.java

@@ -11,11 +11,16 @@ 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;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.risk.domain.RiskInspectionRecordItem;
 import org.dromara.risk.domain.RiskInspectionTask;
+import org.dromara.risk.domain.RiskInspectionTaskItem;
 import org.dromara.risk.domain.bo.MobilePunchBo;
 import org.dromara.risk.domain.RiskInspectionRecord;
 import org.dromara.risk.domain.vo.RiskInspectionRecordVo;
+import org.dromara.risk.mapper.RiskInspectionRecordItemMapper;
 import org.dromara.risk.mapper.RiskInspectionRecordMapper;
+import org.dromara.risk.mapper.RiskInspectionTaskItemMapper;
 import org.dromara.risk.mapper.RiskInspectionTaskMapper;
 import org.dromara.risk.service.IRiskInspectionRecordService;
 import org.springframework.stereotype.Service;
@@ -47,6 +52,8 @@ public class RiskInspectionRecordServiceImpl implements IRiskInspectionRecordSer
 
     private final RiskInspectionRecordMapper baseMapper;
     private final RiskInspectionTaskMapper taskMapper;
+    private final RiskInspectionRecordItemMapper recordItemMapper;
+    private final RiskInspectionTaskItemMapper taskItemMapper;
 
     /**
      * 移动端-提交巡检打卡
@@ -143,12 +150,66 @@ public class RiskInspectionRecordServiceImpl implements IRiskInspectionRecordSer
         }
         taskMapper.updateById(task);
 
+        // 9. 写入 risk_inspection_record_item(每次打卡的明细,历史保留)
+        writeRecordItems(rec.getId(), task.getId(), bo);
+
+        // 10. 回写 risk_inspection_task_item(覆盖式,只保留最近一次结果)
+        updateTaskItemResults(task.getId(), bo, rec.getCheckInTime());
+
         log.info("巡检打卡成功 recordId={} taskId={} result={} total={} problem={}",
             rec.getId(), task.getId(), overallResult, total, problem);
 
         return rec.getId();
     }
 
+    /**
+     * 把 bo.items 写入 risk_inspection_record_item(每次打卡明细,历史保留)
+     */
+    private void writeRecordItems(Long recordId, Long taskId, MobilePunchBo bo) {
+        if (ObjectUtil.isEmpty(bo.getItems())) {
+            return;
+        }
+        java.util.List<RiskInspectionRecordItem> rows = new java.util.ArrayList<>(bo.getItems().size());
+        for (MobilePunchBo.ItemResult it : bo.getItems()) {
+            RiskInspectionRecordItem row = new RiskInspectionRecordItem();
+            row.setRecordId(recordId);
+            row.setTaskId(taskId);
+            row.setItemId(it.getItemId());
+            row.setItemCategory(it.getItemType());
+            row.setItemContent(it.getItemName());
+            row.setItemStandard(it.getStandardValue());
+            row.setCheckResult(it.getResult());
+            row.setProblemCount("1".equals(it.getResult()) ? 1 : 0);
+            row.setRemark(it.getReason());
+            row.setPhotos(it.getPhotos());
+            rows.add(row);
+        }
+        for (RiskInspectionRecordItem row : rows) {
+            recordItemMapper.insert(row);
+        }
+    }
+
+    /**
+     * 回写 risk_inspection_task_item(覆盖式,只保留最近一次结果)
+     */
+    private void updateTaskItemResults(Long taskId, MobilePunchBo bo, java.util.Date checkTime) {
+        if (ObjectUtil.isEmpty(bo.getItems())) {
+            return;
+        }
+        for (MobilePunchBo.ItemResult it : bo.getItems()) {
+            if (it.getItemId() == null) {
+                continue;
+            }
+            RiskInspectionTaskItem update = new RiskInspectionTaskItem();
+            update.setId(it.getItemId());
+            update.setCheckResult(it.getResult());
+            update.setRemark(it.getReason());
+            update.setProblemCount("1".equals(it.getResult()) ? 1 : 0);
+            update.setCheckTime(checkTime);
+            taskItemMapper.updateById(update);
+        }
+    }
+
     /**
      * 移动端-查询单条打卡记录
      */

+ 31 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionTaskItemServiceImpl.java

@@ -0,0 +1,31 @@
+package org.dromara.risk.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import org.dromara.risk.domain.vo.RiskInspectionTaskItemVo;
+import org.dromara.risk.mapper.RiskInspectionTaskItemMapper;
+import org.dromara.risk.service.IRiskInspectionTaskItemService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 巡检任务项目 Service 实现
+ *
+ * @author ruoyi
+ */
+@RequiredArgsConstructor
+@Service
+public class RiskInspectionTaskItemServiceImpl implements IRiskInspectionTaskItemService {
+
+    private final RiskInspectionTaskItemMapper baseMapper;
+
+    @Override
+    public List<RiskInspectionTaskItemVo> listByTaskId(Long taskId) {
+        if (ObjectUtil.isNull(taskId)) {
+            return Collections.emptyList();
+        }
+        return baseMapper.selectVoListByTaskId(taskId);
+    }
+}

+ 9 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionTaskServiceImpl.java

@@ -14,11 +14,14 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.risk.domain.RiskInspectionTask;
 import org.dromara.risk.domain.bo.RiskInspectionTaskBo;
 import org.dromara.risk.domain.RiskInspectionRecord;
+import org.dromara.risk.domain.RiskInspectionTaskItem;
 import org.dromara.risk.domain.vo.MobileInspectionTaskVo;
 import org.dromara.risk.domain.vo.RiskInspectionRecordVo;
 import org.dromara.risk.domain.vo.InspectionStatsVo;
+import org.dromara.risk.domain.vo.RiskInspectionTaskItemVo;
 import org.dromara.risk.domain.vo.RiskInspectionTaskVo;
 import org.dromara.risk.mapper.RiskInspectionRecordMapper;
+import org.dromara.risk.mapper.RiskInspectionTaskItemMapper;
 import org.dromara.risk.mapper.RiskInspectionTaskMapper;
 import org.dromara.risk.service.IRiskInspectionTaskService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -103,6 +106,9 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
                 vo.setRecordStatus(latest.getRecordStatus());
                 vo.setCheckResult(latest.getResult());
             }
+            // 查任务级巡检项目(含最近一次打卡结果),供 PC 端列表/详情展示
+            List<RiskInspectionTaskItemVo> items = taskItemMapper.selectVoListByTaskId(vo.getId());
+            vo.setItems(items);
         }
     }
 
@@ -176,6 +182,9 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
     @Autowired
     @Lazy
     private RiskInspectionRecordMapper recordMapper;
+    @Autowired
+    @Lazy
+    private RiskInspectionTaskItemMapper taskItemMapper;
 
     /**
      * 查询任务的打卡记录列表

+ 16 - 6
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskSupervisionCaseServiceImpl.java

@@ -54,8 +54,8 @@ public class RiskSupervisionCaseServiceImpl implements IRiskSupervisionCaseServi
 
     /** 状态:处置中 */
     private static final String STATUS_PROCESSING = "处置中";
-    /** 状态:已闭环 */
-    private static final String STATUS_CLOSED = "已闭环";
+    /** 状态:归档(原"已闭环",按需求统一为"归档") */
+    private static final String STATUS_CLOSED = "归档";
 
     private final RiskSupervisionCaseMapper baseMapper;
     private final RiskSupervisionCaseNodeMapper nodeMapper;
@@ -175,16 +175,15 @@ public class RiskSupervisionCaseServiceImpl implements IRiskSupervisionCaseServi
         RiskSupervisionCase caze = baseMapper.selectById(bo.getId());
         Assert.notNull(caze, "案件不存在: " + bo.getId());
         if (STATUS_CLOSED.equals(caze.getStatus())) {
-            throw new ServiceException("案件已闭环,不允许再处理");
+            throw new ServiceException("案件已归档,不允许再处理");
         }
         Assert.notNull(caze.getCurrentNode(), "案件当前节点为空");
         int cur = caze.getCurrentNode();
         if (cur < 1 || cur > MAX_NODE) {
             throw new ServiceException("案件当前节点编号非法: " + cur);
         }
-        // 处理意见必填
-        if (StrUtil.isBlank(bo.getOpinion())) {
-            throw new ServiceException("处理意见不能为空");
+        if (StrUtil.isBlank(bo.getExtraData()) && StrUtil.isBlank(bo.getOpinion())) {
+            throw new ServiceException("节点数据不能为空,请填写表单后提交");
         }
         // 写节点记录
         RiskSupervisionCaseNode node = new RiskSupervisionCaseNode();
@@ -192,6 +191,8 @@ public class RiskSupervisionCaseServiceImpl implements IRiskSupervisionCaseServi
         node.setNodeCode(cur);
         node.setNodeName(NODE_NAMES[cur]);
         node.setOpinion(bo.getOpinion());
+        node.setExtraData(bo.getExtraData());
+        // 兼容主表顶层 attachment(节点 1/2/4 可能用)
         node.setAttachmentUrl(bo.getAttachmentUrl());
         node.setAttachmentName(bo.getAttachmentName());
         try {
@@ -227,6 +228,15 @@ public class RiskSupervisionCaseServiceImpl implements IRiskSupervisionCaseServi
         return baseMapper.selectCount(lqw);
     }
 
+    @Override
+    public long countYesterdayProcessing() {
+        // 昨日创建的处置中案件(用于趋势对比)
+        LambdaQueryWrapper<RiskSupervisionCase> lqw = Wrappers.lambdaQuery();
+        lqw.eq(RiskSupervisionCase::getStatus, STATUS_PROCESSING);
+        lqw.apply("DATE(create_time) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)");
+        return baseMapper.selectCount(lqw);
+    }
+
     /**
      * 生成案件编号:AJ+yyyyMMdd+4位随机序号
      */

+ 28 - 14
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskSupervisionComplaintServiceImpl.java

@@ -35,7 +35,7 @@ import java.util.concurrent.ThreadLocalRandom;
 /**
  * 监督抽检 - 平台客诉 业务层实现
  *
- * <p>7 节点流程:风险控制 → 调查取证 → 调查判断 → 部门协同 → 依规处理 → 溯源整改 → 闭环管理</p>
+ * <p>5 节点流程:风险控制 → 异议复检 → 调查取证 → 调查报告 → 闭环管理</p>
  *
  * @author ruoyi
  */
@@ -45,24 +45,22 @@ import java.util.concurrent.ThreadLocalRandom;
 public class RiskSupervisionComplaintServiceImpl implements IRiskSupervisionComplaintService {
 
     /** 客诉最大节点编号 */
-    private static final int MAX_NODE = 7;
+    private static final int MAX_NODE = 5;
 
-    /** 节点名称常量(索引 1-7) */
+    /** 节点名称常量(索引 1-5) */
     private static final String[] NODE_NAMES = {
         "",
         "风险控制",
+        "异议复检",
         "调查取证",
-        "调查判断",
-        "部门协同",
-        "依规处理",
-        "溯源整改",
+        "调查报告",
         "闭环管理"
     };
 
     /** 状态:处置中 */
     private static final String STATUS_PROCESSING = "处置中";
-    /** 状态:已闭环 */
-    private static final String STATUS_CLOSED = "已闭环";
+    /** 状态:归档(原"已闭环",按需求统一为"归档") */
+    private static final String STATUS_CLOSED = "归档";
 
     private final RiskSupervisionComplaintMapper baseMapper;
     private final RiskSupervisionComplaintNodeMapper nodeMapper;
@@ -155,15 +153,15 @@ public class RiskSupervisionComplaintServiceImpl implements IRiskSupervisionComp
         RiskSupervisionComplaint cpt = baseMapper.selectById(bo.getId());
         Assert.notNull(cpt, "客诉不存在: " + bo.getId());
         if (STATUS_CLOSED.equals(cpt.getStatus())) {
-            throw new ServiceException("客诉已闭环,不允许再处理");
+            throw new ServiceException("客诉已归档,不允许再处理");
         }
         Assert.notNull(cpt.getCurrentNode(), "客诉当前节点为空");
         int cur = cpt.getCurrentNode();
         if (cur < 1 || cur > MAX_NODE) {
             throw new ServiceException("客诉当前节点编号非法: " + cur);
         }
-        if (StrUtil.isBlank(bo.getOpinion())) {
-            throw new ServiceException("处理意见不能为空");
+        if (StrUtil.isBlank(bo.getExtraData()) && StrUtil.isBlank(bo.getOpinion())) {
+            throw new ServiceException("节点数据不能为空,请填写表单后提交");
         }
         // 写节点记录
         RiskSupervisionComplaintNode node = new RiskSupervisionComplaintNode();
@@ -171,8 +169,15 @@ public class RiskSupervisionComplaintServiceImpl implements IRiskSupervisionComp
         node.setNodeCode(cur);
         node.setNodeName(NODE_NAMES[cur]);
         node.setOpinion(bo.getOpinion());
-        node.setAttachmentUrl(bo.getProcessAttachmentUrl());
-        node.setAttachmentName(bo.getProcessAttachmentName());
+        node.setExtraData(bo.getExtraData());
+        // 兼容主表顶层 attachment
+        if (StrUtil.isNotBlank(bo.getProcessAttachmentUrl())) {
+            node.setAttachmentUrl(bo.getProcessAttachmentUrl());
+            node.setAttachmentName(bo.getProcessAttachmentName());
+        } else {
+            node.setAttachmentUrl(bo.getAttachmentUrl());
+            node.setAttachmentName(bo.getAttachmentName());
+        }
         try {
             node.setHandlerId(LoginHelper.getUserId());
             node.setHandlerName(LoginHelper.getUsername());
@@ -206,6 +211,15 @@ public class RiskSupervisionComplaintServiceImpl implements IRiskSupervisionComp
         return baseMapper.selectCount(lqw);
     }
 
+    @Override
+    public long countYesterdayProcessing() {
+        // 昨日创建的处置中客诉(用于趋势对比)
+        LambdaQueryWrapper<RiskSupervisionComplaint> lqw = Wrappers.lambdaQuery();
+        lqw.eq(RiskSupervisionComplaint::getStatus, STATUS_PROCESSING);
+        lqw.apply("DATE(transfer_time) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)");
+        return baseMapper.selectCount(lqw);
+    }
+
     /**
      * 生成客诉编号:TS+yyyyMMdd+4位随机序号
      */

+ 9 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskSupervisionInspectionServiceImpl.java

@@ -160,6 +160,15 @@ public class RiskSupervisionInspectionServiceImpl implements IRiskSupervisionIns
         return baseMapper.selectCount(lqw);
     }
 
+    @Override
+    public long countYesterdayPending() {
+        // 昨日抽检单(status='待审核' 且 inspection_time 昨日)
+        LambdaQueryWrapper<RiskSupervisionInspection> lqw = Wrappers.lambdaQuery();
+        lqw.eq(RiskSupervisionInspection::getStatus, STATUS_PENDING);
+        lqw.apply("DATE(inspection_time) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)");
+        return baseMapper.selectCount(lqw);
+    }
+
     @Override
     public SupervisionStatsVo.MonthPassRate calcMonthPassRate(String ym) {
         SupervisionStatsVo.MonthPassRate rate = new SupervisionStatsVo.MonthPassRate();

+ 0 - 7
ruoyi-modules/ruoyi-risk/src/main/resources/mapper/risk/RiskInspectionItemMapper.xml

@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="org.dromara.risk.mapper.RiskInspectionItemMapper">
-    <!-- 所有标准 CRUD 继承自 BaseMapperPlus -->
-</mapper>

+ 52 - 0
ruoyi-modules/ruoyi-risk/src/main/resources/mapper/risk/RiskInspectionRecordItemMapper.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.risk.mapper.RiskInspectionRecordItemMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="baseResultMap" type="org.dromara.risk.domain.RiskInspectionRecordItem">
+        <id property="id" column="id"/>
+        <result property="recordId" column="record_id"/>
+        <result property="taskId" column="task_id"/>
+        <result property="itemId" column="item_id"/>
+        <result property="itemCategory" column="item_category"/>
+        <result property="itemContent" column="item_content"/>
+        <result property="itemStandard" column="item_standard"/>
+        <result property="checkResult" column="check_result"/>
+        <result property="problemCount" column="problem_count"/>
+        <result property="remark" column="remark"/>
+        <result property="photos" column="photos"/>
+        <result property="tenantId" column="tenant_id"/>
+        <result property="createDept" column="create_dept"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="delFlag" column="del_flag"/>
+    </resultMap>
+
+    <!-- 按 recordId 查询项目明细(按 id 升序保持提交顺序) -->
+    <select id="selectVoListByRecordId" resultType="org.dromara.risk.domain.vo.RiskInspectionRecordItemVo">
+        SELECT id, record_id, task_id, item_id, item_category, item_content, item_standard,
+               check_result, problem_count, remark, photos, item_category AS itemType
+        FROM risk_inspection_record_item
+        WHERE record_id = #{recordId} AND del_flag = '0'
+        ORDER BY id ASC
+    </select>
+
+    <!-- 按 taskId 取最新一条 record 的明细 -->
+    <select id="selectVoListByTaskIdLatest" resultType="org.dromara.risk.domain.vo.RiskInspectionRecordItemVo">
+        SELECT id, record_id, task_id, item_id, item_category, item_content, item_standard,
+               check_result, problem_count, remark, photos, item_category AS itemType
+        FROM risk_inspection_record_item
+        WHERE task_id = #{taskId} AND del_flag = '0'
+          AND record_id = (
+              SELECT id FROM risk_inspection_record
+              WHERE task_id = #{taskId} AND del_flag = '0'
+              ORDER BY check_in_time DESC LIMIT 1
+          )
+        ORDER BY id ASC
+    </select>
+
+</mapper>

+ 38 - 0
ruoyi-modules/ruoyi-risk/src/main/resources/mapper/risk/RiskInspectionTaskItemMapper.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.risk.mapper.RiskInspectionTaskItemMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="baseResultMap" type="org.dromara.risk.domain.RiskInspectionTaskItem">
+        <id property="id" column="id"/>
+        <result property="taskId" column="task_id"/>
+        <result property="itemCategory" column="item_category"/>
+        <result property="itemContent" column="item_content"/>
+        <result property="itemStandard" column="item_standard"/>
+        <result property="needPhoto" column="need_photo"/>
+        <result property="sortOrder" column="sort_order"/>
+        <result property="tenantId" column="tenant_id"/>
+        <result property="createDept" column="create_dept"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="delFlag" column="del_flag"/>
+        <result property="checkResult" column="check_result"/>
+        <result property="remark" column="remark"/>
+        <result property="problemCount" column="problem_count"/>
+        <result property="checkTime" column="check_time"/>
+    </resultMap>
+
+    <!-- 按任务ID查询项目列表(按 sort_order 升序) -->
+    <select id="selectVoListByTaskId" resultType="org.dromara.risk.domain.vo.RiskInspectionTaskItemVo">
+        SELECT id, task_id, item_category, item_content, item_standard,
+               need_photo, sort_order, check_result, remark, problem_count, check_time
+        FROM risk_inspection_task_item
+        WHERE task_id = #{taskId} AND del_flag = '0'
+        ORDER BY sort_order ASC, id ASC
+    </select>
+
+</mapper>

+ 34 - 0
sql/risk_dept_store.sql

@@ -0,0 +1,34 @@
+-- ============================================================
+-- 把部门表的"终端叶子结点"标记为门店(dept_category='门店')
+-- 逻辑:parent_id 不在 dept_id 集合里的 = 叶子节点
+--       (即没有任何部门以这个 id 为 parent_id)
+-- 同时支持幂等(已经设为门店的不再改)
+-- ============================================================
+
+USE risk_control_platform;
+
+UPDATE sys_dept
+SET dept_category = '门店'
+WHERE del_flag = '0'
+  AND status = '0'
+  AND dept_id NOT IN (
+      SELECT t.parent_id FROM (SELECT DISTINCT parent_id FROM sys_dept
+                                WHERE parent_id IS NOT NULL AND parent_id <> 0 AND del_flag = '0') t
+  )
+  AND parent_id <> 0  -- 排除顶级虚拟节点
+  AND (dept_category IS NULL OR dept_category = '' OR dept_category <> '门店');
+
+-- 验证:看叶子节点被改了几个 + 抽样看几个
+SELECT '--- 改后统计 ---' AS info;
+SELECT
+    COUNT(*) AS total,
+    SUM(CASE WHEN dept_category = '门店' THEN 1 ELSE 0 END) AS store_count,
+    SUM(CASE WHEN dept_category IS NULL OR dept_category = '' THEN 1 ELSE 0 END) AS unset_count
+FROM sys_dept
+WHERE del_flag='0' AND status='0' AND parent_id <> 0;
+
+SELECT '--- 抽样 5 个门店 ---' AS info;
+SELECT dept_id, dept_name, parent_id, dept_category
+FROM sys_dept
+WHERE del_flag='0' AND status='0' AND dept_category='门店'
+ORDER BY dept_id LIMIT 5;

+ 2 - 2
sql/risk_food_safety_campaign_menu.sql

@@ -9,13 +9,13 @@
 SET @risk_parent_id = (SELECT menu_id FROM sys_menu WHERE menu_name = '风控平台' AND menu_type = 'M' LIMIT 1);
 
 -- 1. 食品安全宣传主菜单
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('食品安全宣传', @risk_parent_id, 15, 'foodSafety', 'risk/foodSafety/index', 1, 0, 'C', '0', '0', 'risk:foodSafety:list', '#', 103, 1, NOW(), '食品安全宣传管理菜单');
 
 SET @food_safety_id = LAST_INSERT_ID();
 
 -- 2. 食品安全宣传按钮权限(列表/新增/修改/删除/导出/活动开始/活动结束/上传资料)
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('食品安全宣传查询', @food_safety_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:foodSafety:list', '#', 103, 1, NOW(), ''),
 ('食品安全宣传新增', @food_safety_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:foodSafety:add', '#', 103, 1, NOW(), ''),
 ('食品安全宣传修改', @food_safety_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:foodSafety:edit', '#', 103, 1, NOW(), ''),

+ 31 - 31
sql/risk_menu.sql

@@ -8,7 +8,7 @@
 SET @risk_parent_id = 0;
 
 -- 1. 一级目录: 风控平台
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('风控平台', 0, 6, 'risk', NULL, 1, 0, 'M', '0', '0', '', 'warning', 103, 1, NOW(), '风控平台目录');
 
 SET @risk_parent_id = LAST_INSERT_ID();
@@ -16,13 +16,13 @@ SET @risk_parent_id = LAST_INSERT_ID();
 -- ============================================
 -- 2. 安全隐患
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('安全隐患', @risk_parent_id, 1, 'hiddenDanger', 'risk/hiddenDanger/index', 1, 0, 'C', '0', '0', 'risk:hiddenDanger:list', '#', 103, 1, NOW(), '安全隐患菜单');
 
 SET @hidden_danger_id = LAST_INSERT_ID();
 
 -- 安全隐患按钮
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('隐患查询', @hidden_danger_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:hiddenDanger:query', '#', 103, 1, NOW(), ''),
 ('隐患新增', @hidden_danger_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:hiddenDanger:add', '#', 103, 1, NOW(), ''),
 ('隐患修改', @hidden_danger_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:hiddenDanger:edit', '#', 103, 1, NOW(), ''),
@@ -33,12 +33,12 @@ INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame
 -- ============================================
 -- 3. 危险源上报
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('危险源上报', @risk_parent_id, 2, 'dangerSource', 'risk/dangerSource/index', 1, 0, 'C', '0', '0', 'risk:dangerSource:list', '#', 103, 1, NOW(), '危险源上报菜单');
 
 SET @danger_source_id = LAST_INSERT_ID();
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('危险源查询', @danger_source_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:dangerSource:query', '#', 103, 1, NOW(), ''),
 ('危险源新增', @danger_source_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:dangerSource:add', '#', 103, 1, NOW(), ''),
 ('危险源修改', @danger_source_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:dangerSource:edit', '#', 103, 1, NOW(), ''),
@@ -48,12 +48,12 @@ INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame
 -- ============================================
 -- 4. 作业上报
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('作业上报', @risk_parent_id, 3, 'workReport', 'risk/workReport/index', 1, 0, 'C', '0', '0', 'risk:workReport:list', '#', 103, 1, NOW(), '作业上报菜单');
 
 SET @work_report_id = LAST_INSERT_ID();
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('作业查询', @work_report_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:workReport:query', '#', 103, 1, NOW(), ''),
 ('作业新增', @work_report_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:workReport:add', '#', 103, 1, NOW(), ''),
 ('作业修改', @work_report_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:workReport:edit', '#', 103, 1, NOW(), ''),
@@ -64,12 +64,12 @@ INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame
 -- ============================================
 -- 5. 人员信息
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('人员信息', @risk_parent_id, 4, 'personnel', 'risk/personnel/index', 1, 0, 'C', '0', '0', 'risk:personnel:list', '#', 103, 1, NOW(), '人员信息菜单');
 
 SET @personnel_id = LAST_INSERT_ID();
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('人员查询', @personnel_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:personnel:query', '#', 103, 1, NOW(), ''),
 ('人员新增', @personnel_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:personnel:add', '#', 103, 1, NOW(), ''),
 ('人员修改', @personnel_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:personnel:edit', '#', 103, 1, NOW(), ''),
@@ -82,12 +82,12 @@ INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame
 -- ============================================
 -- 6. 审计工作
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('审计工作', @risk_parent_id, 5, 'personnelChange', 'risk/personnelChange/index', 1, 0, 'C', '0', '0', 'risk:personnelChange:list', '#', 103, 1, NOW(), '审计工作菜单');
 
 SET @personnel_change_id = LAST_INSERT_ID();
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('人员变动查询', @personnel_change_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelChange:query', '#', 103, 1, NOW(), ''),
 ('人员变动新增', @personnel_change_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelChange:add', '#', 103, 1, NOW(), ''),
 ('人员变动修改', @personnel_change_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelChange:edit', '#', 103, 1, NOW(), ''),
@@ -99,12 +99,12 @@ INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame
 -- ============================================
 -- 7. 风险警示
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('风险警示', @risk_parent_id, 6, 'warning', 'risk/warning/index', 1, 0, 'C', '0', '0', 'risk:warning:list', '#', 103, 1, NOW(), '风险警示菜单');
 
 SET @warning_id = LAST_INSERT_ID();
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('警示查询', @warning_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:warning:query', '#', 103, 1, NOW(), ''),
 ('警示新增', @warning_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:warning:add', '#', 103, 1, NOW(), ''),
 ('警示修改', @warning_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:warning:edit', '#', 103, 1, NOW(), ''),
@@ -113,12 +113,12 @@ INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame
 -- ============================================
 -- 8. 特种设备
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('特种设备', @risk_parent_id, 7, 'specialEquipment', 'risk/specialEquipment/index', 1, 0, 'C', '0', '0', 'risk:specialEquipment:list', '#', 103, 1, NOW(), '特种设备菜单');
 
 SET @special_equipment_id = LAST_INSERT_ID();
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('设备查询', @special_equipment_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:specialEquipment:query', '#', 103, 1, NOW(), ''),
 ('设备新增', @special_equipment_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:specialEquipment:add', '#', 103, 1, NOW(), ''),
 ('设备修改', @special_equipment_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:specialEquipment:edit', '#', 103, 1, NOW(), ''),
@@ -131,12 +131,12 @@ INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame
 -- ============================================
 -- 9. 舆情推送
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('舆情推送', @risk_parent_id, 8, 'publicOpinion', 'risk/publicOpinion/index', 1, 0, 'C', '0', '0', 'risk:publicOpinion:list', '#', 103, 1, NOW(), '舆情推送菜单');
 
 SET @public_opinion_id = LAST_INSERT_ID();
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('舆情查询', @public_opinion_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:publicOpinion:query', '#', 103, 1, NOW(), ''),
 ('舆情新增', @public_opinion_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:publicOpinion:add', '#', 103, 1, NOW(), ''),
 ('舆情修改', @public_opinion_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:publicOpinion:edit', '#', 103, 1, NOW(), ''),
@@ -146,12 +146,12 @@ INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame
 -- ============================================
 -- 10. 事故上报
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('事故上报', @risk_parent_id, 9, 'accident', 'risk/accident/index', 1, 0, 'C', '0', '0', 'risk:accident:list', '#', 103, 1, NOW(), '事故上报菜单');
 
 SET @accident_id = LAST_INSERT_ID();
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('事故查询', @accident_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:accident:query', '#', 103, 1, NOW(), ''),
 ('事故新增', @accident_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:accident:add', '#', 103, 1, NOW(), ''),
 ('事故修改', @accident_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:accident:edit', '#', 103, 1, NOW(), ''),
@@ -160,12 +160,12 @@ INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame
 -- ============================================
 -- 11. 巡检管理
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('巡检任务', @risk_parent_id, 10, 'inspectionTask', 'risk/inspectionTask/index', 1, 0, 'C', '0', '0', 'risk:inspectionTask:list', '#', 103, 1, NOW(), '巡检任务菜单');
 
 SET @inspection_task_id = LAST_INSERT_ID();
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('巡检任务查询', @inspection_task_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionTask:query', '#', 103, 1, NOW(), ''),
 ('巡检任务新增', @inspection_task_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionTask:add', '#', 103, 1, NOW(), ''),
 ('巡检任务修改', @inspection_task_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionTask:edit', '#', 103, 1, NOW(), ''),
@@ -178,21 +178,21 @@ INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame
 -- ============================================
 -- 12. 文件上传
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('文件上传', @risk_parent_id, 11, '#', '', 1, 0, 'F', '0', '0', 'risk:file:upload', '#', 103, 1, NOW(), '文件上传');
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('文件删除', @risk_parent_id, 12, '#', '', 1, 0, 'F', '0', '0', 'risk:file:remove', '#', 103, 1, NOW(), '文件删除');
 
 -- ============================================
 -- 13. 风控大屏 (3.3 大屏菜单)
 -- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('风控大屏', @risk_parent_id, 0, 'bigScreen', 'risk/bigScreen/index', 1, 0, 'C', '0', '0', 'risk:bigScreen:view', 'data-board', 103, 1, NOW(), '风控品控管理平台大屏');
 
 -- ============================================
 -- 13. 给超级管理员角色授权所有风控平台权限
 -- ============================================
-INSERT INTO sys_role_menu (role_id, menu_id)
+INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
 SELECT 1, menu_id FROM sys_menu WHERE perms LIKE 'risk:%';
 
 -- 完成提示
@@ -206,11 +206,11 @@ WHERE perms LIKE 'risk:%';
 -- ============================================
 
 -- 14.1 舆情来源字典
-INSERT INTO sys_dict_type (dict_name, dict_type, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, create_dept, create_by, create_time, remark)
 VALUES ('舆情来源', 'opinion_source', 103, 1, NOW(), '舆情来源列表');
 SET @source_type_id = LAST_INSERT_ID();
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, remark) VALUES
 (1, '新浪微博', '新浪微博', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
 (2, '抖音', '抖音', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
 (3, '今日头条', '今日头条', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
@@ -222,21 +222,21 @@ INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_cla
 (9, '其他', '其他', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), '');
 
 -- 14.2 风险等级字典
-INSERT INTO sys_dict_type (dict_name, dict_type, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, create_dept, create_by, create_time, remark)
 VALUES ('风险等级', 'risk_opinion_level', 103, 1, NOW(), '风险等级列表');
 SET @level_type_id = LAST_INSERT_ID();
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, remark) VALUES
 (1, '重大', '重大', 'risk_opinion_level', '', 'danger', 'N', 103, 1, NOW(), '重大风险'),
 (2, '较大', '较大', 'risk_opinion_level', '', 'warning', 'N', 103, 1, NOW(), '较大风险'),
 (3, '一般', '一般', 'risk_opinion_level', '', 'success', 'N', 103, 1, NOW(), '一般风险');
 
 -- 14.3 处置状态字典
-INSERT INTO sys_dict_type (dict_name, dict_type, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, create_dept, create_by, create_time, remark)
 VALUES ('处置状态', 'risk_opinion_status', 103, 1, NOW(), '处置状态列表');
 SET @status_type_id = LAST_INSERT_ID();
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, remark) VALUES
 (1, '待处理', '待处理', 'risk_opinion_status', '', 'danger', 'N', 103, 1, NOW(), '待处理'),
 (2, '处理中', '处理中', 'risk_opinion_status', '', 'warning', 'N', 103, 1, NOW(), '处理中'),
 (3, '已处理', '已处理', 'risk_opinion_status', '', 'success', 'N', 103, 1, NOW(), '已处理');

+ 0 - 236
sql/risk_menu.sql.bak

@@ -1,236 +0,0 @@
--- ============================================
--- 风控平台菜单权限 SQL 脚本
--- 创建时间: 2026-01-17
--- 依赖: 已存在 sys_menu 表(由 ry_vue_5.X.sql 创建)
--- ============================================
-
--- 设置变量(用于存储父菜单ID)
-SET @risk_parent_id = 0;
-
--- 1. 一级目录: 风控平台
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('风控平台', 0, 6, 'risk', NULL, 1, 0, 'M', '0', '0', '', 'warning', 103, 1, NOW(), '风控平台目录');
-
-SET @risk_parent_id = LAST_INSERT_ID();
-
--- ============================================
--- 2. 安全隐患
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('安全隐患', @risk_parent_id, 1, 'hiddenDanger', 'risk/hiddenDanger/index', 1, 0, 'C', '0', '0', 'risk:hiddenDanger:list', '#', 103, 1, NOW(), '安全隐患菜单');
-
-SET @hidden_danger_id = LAST_INSERT_ID();
-
--- 安全隐患按钮
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
-('隐患查询', @hidden_danger_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:hiddenDanger:query', '#', 103, 1, NOW(), ''),
-('隐患新增', @hidden_danger_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:hiddenDanger:add', '#', 103, 1, NOW(), ''),
-('隐患修改', @hidden_danger_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:hiddenDanger:edit', '#', 103, 1, NOW(), ''),
-('隐患删除', @hidden_danger_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:hiddenDanger:remove', '#', 103, 1, NOW(), ''),
-('隐患导出', @hidden_danger_id, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:hiddenDanger:export', '#', 103, 1, NOW(), ''),
-('隐患处理', @hidden_danger_id, 6, '#', '', 1, 0, 'F', '0', '0', 'risk:hiddenDanger:handle', '#', 103, 1, NOW(), '');
-
--- ============================================
--- 3. 危险源上报
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('危险源上报', @risk_parent_id, 2, 'dangerSource', 'risk/dangerSource/index', 1, 0, 'C', '0', '0', 'risk:dangerSource:list', '#', 103, 1, NOW(), '危险源上报菜单');
-
-SET @danger_source_id = LAST_INSERT_ID();
-
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
-('危险源查询', @danger_source_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:dangerSource:query', '#', 103, 1, NOW(), ''),
-('危险源新增', @danger_source_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:dangerSource:add', '#', 103, 1, NOW(), ''),
-('危险源修改', @danger_source_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:dangerSource:edit', '#', 103, 1, NOW(), ''),
-('危险源删除', @danger_source_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:dangerSource:remove', '#', 103, 1, NOW(), ''),
-('危险源导出', @danger_source_id, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:dangerSource:export', '#', 103, 1, NOW(), '');
-
--- ============================================
--- 4. 作业上报
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('作业上报', @risk_parent_id, 3, 'workReport', 'risk/workReport/index', 1, 0, 'C', '0', '0', 'risk:workReport:list', '#', 103, 1, NOW(), '作业上报菜单');
-
-SET @work_report_id = LAST_INSERT_ID();
-
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
-('作业查询', @work_report_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:workReport:query', '#', 103, 1, NOW(), ''),
-('作业新增', @work_report_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:workReport:add', '#', 103, 1, NOW(), ''),
-('作业修改', @work_report_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:workReport:edit', '#', 103, 1, NOW(), ''),
-('作业删除', @work_report_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:workReport:remove', '#', 103, 1, NOW(), ''),
-('作业审核', @work_report_id, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:workReport:review', '#', 103, 1, NOW(), ''),
-('作业导出', @work_report_id, 6, '#', '', 1, 0, 'F', '0', '0', 'risk:workReport:export', '#', 103, 1, NOW(), '');
-
--- ============================================
--- 5. 人员信息
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('人员信息', @risk_parent_id, 4, 'personnel', 'risk/personnel/index', 1, 0, 'C', '0', '0', 'risk:personnel:list', '#', 103, 1, NOW(), '人员信息菜单');
-
-SET @personnel_id = LAST_INSERT_ID();
-
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
-('人员查询', @personnel_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:personnel:query', '#', 103, 1, NOW(), ''),
-('人员新增', @personnel_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:personnel:add', '#', 103, 1, NOW(), ''),
-('人员修改', @personnel_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:personnel:edit', '#', 103, 1, NOW(), ''),
-('人员删除', @personnel_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:personnel:remove', '#', 103, 1, NOW(), ''),
-('证书查询', @personnel_id, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelCert:list', '#', 103, 1, NOW(), ''),
-('证书新增', @personnel_id, 6, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelCert:add', '#', 103, 1, NOW(), ''),
-('证书修改', @personnel_id, 7, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelCert:edit', '#', 103, 1, NOW(), ''),
-('证书删除', @personnel_id, 8, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelCert:remove', '#', 103, 1, NOW(), '');
-
--- ============================================
--- 6. 审计工作
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('审计工作', @risk_parent_id, 5, 'personnelChange', 'risk/personnelChange/index', 1, 0, 'C', '0', '0', 'risk:personnelChange:list', '#', 103, 1, NOW(), '审计工作菜单');
-
-SET @personnel_change_id = LAST_INSERT_ID();
-
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
-('人员变动查询', @personnel_change_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelChange:query', '#', 103, 1, NOW(), ''),
-('人员变动新增', @personnel_change_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelChange:add', '#', 103, 1, NOW(), ''),
-('人员变动修改', @personnel_change_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelChange:edit', '#', 103, 1, NOW(), ''),
-('人员变动删除', @personnel_change_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:personnelChange:remove', '#', 103, 1, NOW(), ''),
-('审计文件查询', @personnel_change_id, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:auditFile:list', '#', 103, 1, NOW(), ''),
-('审计文件上传', @personnel_change_id, 6, '#', '', 1, 0, 'F', '0', '0', 'risk:auditFile:add', '#', 103, 1, NOW(), ''),
-('审计文件删除', @personnel_change_id, 7, '#', '', 1, 0, 'F', '0', '0', 'risk:auditFile:remove', '#', 103, 1, NOW(), '');
-
--- ============================================
--- 7. 风险警示
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('风险警示', @risk_parent_id, 6, 'warning', 'risk/warning/index', 1, 0, 'C', '0', '0', 'risk:warning:list', '#', 103, 1, NOW(), '风险警示菜单');
-
-SET @warning_id = LAST_INSERT_ID();
-
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
-('警示查询', @warning_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:warning:query', '#', 103, 1, NOW(), ''),
-('警示新增', @warning_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:warning:add', '#', 103, 1, NOW(), ''),
-('警示修改', @warning_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:warning:edit', '#', 103, 1, NOW(), ''),
-('警示删除', @warning_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:warning:remove', '#', 103, 1, NOW(), '');
-
--- ============================================
--- 8. 特种设备
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('特种设备', @risk_parent_id, 7, 'specialEquipment', 'risk/specialEquipment/index', 1, 0, 'C', '0', '0', 'risk:specialEquipment:list', '#', 103, 1, NOW(), '特种设备菜单');
-
-SET @special_equipment_id = LAST_INSERT_ID();
-
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
-('设备查询', @special_equipment_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:specialEquipment:query', '#', 103, 1, NOW(), ''),
-('设备新增', @special_equipment_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:specialEquipment:add', '#', 103, 1, NOW(), ''),
-('设备修改', @special_equipment_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:specialEquipment:edit', '#', 103, 1, NOW(), ''),
-('设备删除', @special_equipment_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:specialEquipment:remove', '#', 103, 1, NOW(), ''),
-('设备维护查询', @special_equipment_id, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:equipmentMaintenance:list', '#', 103, 1, NOW(), ''),
-('设备维护新增', @special_equipment_id, 6, '#', '', 1, 0, 'F', '0', '0', 'risk:equipmentMaintenance:add', '#', 103, 1, NOW(), ''),
-('设备维护修改', @special_equipment_id, 7, '#', '', 1, 0, 'F', '0', '0', 'risk:equipmentMaintenance:edit', '#', 103, 1, NOW(), ''),
-('设备维护删除', @special_equipment_id, 8, '#', '', 1, 0, 'F', '0', '0', 'risk:equipmentMaintenance:remove', '#', 103, 1, NOW(), '');
-
--- ============================================
--- 9. 舆情推送
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('舆情推送', @risk_parent_id, 8, 'publicOpinion', 'risk/publicOpinion/index', 1, 0, 'C', '0', '0', 'risk:publicOpinion:list', '#', 103, 1, NOW(), '舆情推送菜单');
-
-SET @public_opinion_id = LAST_INSERT_ID();
-
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
-('舆情查询', @public_opinion_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:publicOpinion:query', '#', 103, 1, NOW(), ''),
-('舆情新增', @public_opinion_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:publicOpinion:add', '#', 103, 1, NOW(), ''),
-('舆情修改', @public_opinion_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:publicOpinion:edit', '#', 103, 1, NOW(), ''),
-('舆情删除', @public_opinion_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:publicOpinion:remove', '#', 103, 1, NOW(), ''),
-('舆情处理', @public_opinion_id, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:publicOpinion:handle', '#', 103, 1, NOW(), '');
-
--- ============================================
--- 10. 事故上报
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('事故上报', @risk_parent_id, 9, 'accident', 'risk/accident/index', 1, 0, 'C', '0', '0', 'risk:accident:list', '#', 103, 1, NOW(), '事故上报菜单');
-
-SET @accident_id = LAST_INSERT_ID();
-
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
-('事故查询', @accident_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:accident:query', '#', 103, 1, NOW(), ''),
-('事故新增', @accident_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:accident:add', '#', 103, 1, NOW(), ''),
-('事故修改', @accident_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:accident:edit', '#', 103, 1, NOW(), ''),
-('事故删除', @accident_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:accident:remove', '#', 103, 1, NOW(), '');
-
--- ============================================
--- 11. 巡检管理
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('巡检任务', @risk_parent_id, 10, 'inspectionTask', 'risk/inspectionTask/index', 1, 0, 'C', '0', '0', 'risk:inspectionTask:list', '#', 103, 1, NOW(), '巡检任务菜单');
-
-SET @inspection_task_id = LAST_INSERT_ID();
-
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
-('巡检任务查询', @inspection_task_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionTask:query', '#', 103, 1, NOW(), ''),
-('巡检任务新增', @inspection_task_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionTask:add', '#', 103, 1, NOW(), ''),
-('巡检任务修改', @inspection_task_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionTask:edit', '#', 103, 1, NOW(), ''),
-('巡检任务删除', @inspection_task_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionTask:remove', '#', 103, 1, NOW(), ''),
-('巡检项目查询', @inspection_task_id, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionItem:list', '#', 103, 1, NOW(), ''),
-('巡检项目新增', @inspection_task_id, 6, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionItem:add', '#', 103, 1, NOW(), ''),
-('巡检项目修改', @inspection_task_id, 7, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionItem:edit', '#', 103, 1, NOW(), ''),
-('巡检项目删除', @inspection_task_id, 8, '#', '', 1, 0, 'F', '0', '0', 'risk:inspectionItem:remove', '#', 103, 1, NOW(), '');
-
--- ============================================
--- 12. 文件上传
--- ============================================
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('文件上传', @risk_parent_id, 11, '#', '', 1, 0, 'F', '0', '0', 'risk:file:upload', '#', 103, 1, NOW(), '文件上传');
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
-VALUES ('文件删除', @risk_parent_id, 12, '#', '', 1, 0, 'F', '0', '0', 'risk:file:remove', '#', 103, 1, NOW(), '文件删除');
-
--- ============================================
--- 13. 给超级管理员角色授权所有风控平台权限
--- ============================================
-INSERT INTO sys_role_menu (role_id, menu_id)
-SELECT 1, menu_id FROM sys_menu WHERE perms LIKE 'risk:%';
-
--- 完成提示
-SELECT '风控平台菜单权限初始化完成!' AS message,
-       COUNT(*) AS total_menus
-FROM sys_menu
-WHERE perms LIKE 'risk:%';
-
--- ============================================
--- 14. 舆情推送字典数据
--- ============================================
-
--- 14.1 舆情来源字典
-INSERT INTO sys_dict_type (dict_name, dict_type, create_dept, create_by, create_time, remark)
-VALUES ('舆情来源', 'opinion_source', 103, 1, NOW(), '舆情来源列表');
-SET @source_type_id = LAST_INSERT_ID();
-
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, remark) VALUES
-(1, '新浪微博', '新浪微博', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
-(2, '抖音', '抖音', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
-(3, '今日头条', '今日头条', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
-(4, '知乎', '知乎', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
-(5, '新闻媒体', '新闻媒体', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
-(6, '小红书', '小红书', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
-(7, '微信公众号', '微信公众号', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
-(8, '贴吧', '贴吧', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), ''),
-(9, '其他', '其他', 'opinion_source', '', 'default', 'N', 103, 1, NOW(), '');
-
--- 14.2 风险等级字典
-INSERT INTO sys_dict_type (dict_name, dict_type, create_dept, create_by, create_time, remark)
-VALUES ('风险等级', 'risk_opinion_level', 103, 1, NOW(), '风险等级列表');
-SET @level_type_id = LAST_INSERT_ID();
-
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, remark) VALUES
-(1, '重大', '重大', 'risk_opinion_level', '', 'danger', 'N', 103, 1, NOW(), '重大风险'),
-(2, '较大', '较大', 'risk_opinion_level', '', 'warning', 'N', 103, 1, NOW(), '较大风险'),
-(3, '一般', '一般', 'risk_opinion_level', '', 'success', 'N', 103, 1, NOW(), '一般风险');
-
--- 14.3 处置状态字典
-INSERT INTO sys_dict_type (dict_name, dict_type, create_dept, create_by, create_time, remark)
-VALUES ('处置状态', 'risk_opinion_status', 103, 1, NOW(), '处置状态列表');
-SET @status_type_id = LAST_INSERT_ID();
-
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, remark) VALUES
-(1, '待处理', '待处理', 'risk_opinion_status', '', 'danger', 'N', 103, 1, NOW(), '待处理'),
-(2, '处理中', '处理中', 'risk_opinion_status', '', 'warning', 'N', 103, 1, NOW(), '处理中'),
-(3, '已处理', '已处理', 'risk_opinion_status', '', 'success', 'N', 103, 1, NOW(), '已处理');

+ 2 - 2
sql/risk_quality_factory_visit_menu.sql

@@ -9,13 +9,13 @@
 SET @risk_parent_id = (SELECT menu_id FROM sys_menu WHERE menu_name = '风控平台' AND menu_type = 'M' LIMIT 1);
 
 -- 1. 品控访厂主菜单
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('品控访厂', @risk_parent_id, 14, 'factoryVisit', 'risk/factoryVisit/index', 1, 0, 'C', '0', '0', 'risk:factoryVisit:list', '#', 103, 1, NOW(), '品控访厂管理菜单');
 
 SET @factory_visit_id = LAST_INSERT_ID();
 
 -- 2. 品控访厂按钮权限(列表/新增/修改/删除/导出/填写报告/审核)
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('品控访厂查询', @factory_visit_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:factoryVisit:list', '#', 103, 1, NOW(), ''),
 ('品控访厂新增', @factory_visit_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:factoryVisit:add', '#', 103, 1, NOW(), ''),
 ('品控访厂修改', @factory_visit_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:factoryVisit:edit', '#', 103, 1, NOW(), ''),

+ 2 - 2
sql/risk_quality_menu.sql

@@ -8,12 +8,12 @@
 SET @risk_parent_id = (SELECT menu_id FROM sys_menu WHERE menu_name = '风控平台' AND menu_type = 'M' LIMIT 1);
 
 -- 1. 品控巡检
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark)
 VALUES ('品控巡检', @risk_parent_id, 13, 'qualityInspectionTask', 'risk/qualityInspectionTask/index', 1, 0, 'C', '0', '0', 'risk:qualityInspectionTask:list', '#', 103, 1, NOW(), '品控巡检任务菜单');
 
 SET @quality_inspection_task_id = LAST_INSERT_ID();
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, remark) VALUES
 ('品控巡检查询', @quality_inspection_task_id, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:qualityInspectionTask:query', '#', 103, 1, NOW(), ''),
 ('品控巡检新增', @quality_inspection_task_id, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:qualityInspectionTask:add', '#', 103, 1, NOW(), ''),
 ('品控巡检修改', @quality_inspection_task_id, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:qualityInspectionTask:edit', '#', 103, 1, NOW(), ''),

+ 25 - 0
sql/risk_role_menu.sql

@@ -0,0 +1,25 @@
+-- ============================================================
+-- 给"本部门及以下"角色(role_id=3)分配风控平台菜单权限
+-- 菜单 menu_id 11882(监督抽检/平台客诉)是父菜单,需要把它和它下面的所有子菜单都分配
+-- 同时把 test1 用户也改到 role_id=3,这样 test1 登录能直接看到风控菜单 + 数据权限
+-- ============================================================
+
+USE risk_control_platform;
+
+-- 1) 分配"监督抽检/平台客诉"父菜单 + 所有子菜单给"本部门及以下"角色(role_id=3)
+INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
+SELECT 3, menu_id FROM sys_menu
+WHERE (perms LIKE 'risk:%' OR menu_id = 11807)
+  AND status = '0';
+
+-- 2) 顺便把 test1 (user_id=4) 改到 role_id=3 (本部门及以下),这样登录就能测数据权限
+UPDATE sys_user_role SET role_id = 3 WHERE user_id = 4;
+
+-- 验证
+SELECT '--- role 3 现在的风控菜单数 ---' AS info;
+SELECT COUNT(*) AS risk_menu_count
+FROM sys_role_menu rm JOIN sys_menu m ON m.menu_id = rm.menu_id
+WHERE rm.role_id = 3 AND (m.perms LIKE 'risk:%' OR m.menu_id = 11807) AND m.status = '0';
+
+SELECT '--- test1 现在角色 ---' AS info;
+SELECT user_id, role_id FROM sys_user_role WHERE user_id = 4;

+ 42 - 42
sql/risk_supervision.sql

@@ -16,93 +16,93 @@ SET FOREIGN_KEY_CHECKS = 0;
 -- ============================================================
 
 -- 1.1 抽检结果
-INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
-SELECT '抽检结果', 'risk_supervision_inspection_result', '0', '门店抽检结果(合格/不合格)', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+SELECT '抽检结果', 'risk_supervision_inspection_result', '门店抽检结果(合格/不合格)', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_inspection_result');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 1, '抽检合格', '抽检合格', 'risk_supervision_inspection_result', '', 'success', 'Y', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '抽检合格', '抽检合格', 'risk_supervision_inspection_result', '', 'success', 'Y', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_inspection_result' AND dict_value = '抽检合格');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 2, '抽检不合格', '抽检不合格', 'risk_supervision_inspection_result', '', 'danger', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '抽检不合格', '抽检不合格', 'risk_supervision_inspection_result', '', 'danger', 'N', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_inspection_result' AND dict_value = '抽检不合格');
 
 -- 1.2 抽检审核状态
-INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
-SELECT '抽检审核状态', 'risk_supervision_inspection_status', '0', '门店抽检单审核状态', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+SELECT '抽检审核状态', 'risk_supervision_inspection_status', '门店抽检单审核状态', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_inspection_status');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 1, '待审核', '待审核', 'risk_supervision_inspection_status', '', 'warning', 'Y', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '待审核', '待审核', 'risk_supervision_inspection_status', '', 'warning', 'Y', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_inspection_status' AND dict_value = '待审核');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 2, '已审核', '已审核', 'risk_supervision_inspection_status', '', 'success', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '已审核', '已审核', 'risk_supervision_inspection_status', '', 'success', 'N', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_inspection_status' AND dict_value = '已审核');
 
 -- 1.3 风险等级
-INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
-SELECT '风险等级', 'risk_supervision_risk_level', '0', '案件风险等级', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+SELECT '风险等级', 'risk_supervision_risk_level', '案件风险等级', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_risk_level');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 1, '高风险', '高风险', 'risk_supervision_risk_level', '', 'danger', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '高风险', '高风险', 'risk_supervision_risk_level', '', 'danger', 'N', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_risk_level' AND dict_value = '高风险');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 2, '中风险', '中风险', 'risk_supervision_risk_level', '', 'warning', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '中风险', '中风险', 'risk_supervision_risk_level', '', 'warning', 'N', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_risk_level' AND dict_value = '中风险');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 3, '低风险', '低风险', 'risk_supervision_risk_level', '', 'info', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 3, '低风险', '低风险', 'risk_supervision_risk_level', '', 'info', 'N', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_risk_level' AND dict_value = '低风险');
 
 -- 1.4 客诉来源
-INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
-SELECT '客诉来源', 'risk_supervision_complaint_source', '0', '平台客诉来源', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+SELECT '客诉来源', 'risk_supervision_complaint_source', '平台客诉来源', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_complaint_source');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 1, '消费者投诉', '消费者投诉', 'risk_supervision_complaint_source', '', 'primary', 'Y', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '消费者投诉', '消费者投诉', 'risk_supervision_complaint_source', '', 'primary', 'Y', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_source' AND dict_value = '消费者投诉');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 2, '媒体舆情', '媒体舆情', 'risk_supervision_complaint_source', '', 'warning', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '媒体舆情', '媒体舆情', 'risk_supervision_complaint_source', '', 'warning', 'N', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_source' AND dict_value = '媒体舆情');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 3, '12315平台', '12315平台', 'risk_supervision_complaint_source', '', 'danger', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 3, '12315平台', '12315平台', 'risk_supervision_complaint_source', '', 'danger', 'N', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_source' AND dict_value = '12315平台');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 4, '其他来源', '其他来源', 'risk_supervision_complaint_source', '', 'info', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 4, '其他来源', '其他来源', 'risk_supervision_complaint_source', '', 'info', 'N', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_source' AND dict_value = '其他来源');
 
 -- 1.5 案件状态
-INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
-SELECT '案件状态', 'risk_supervision_case_status', '0', '不合格案件处理状态', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+SELECT '案件状态', 'risk_supervision_case_status', '不合格案件处理状态', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_case_status');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 1, '处置中', '处置中', 'risk_supervision_case_status', '', 'warning', 'Y', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '处置中', '处置中', 'risk_supervision_case_status', '', 'warning', 'Y', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_case_status' AND dict_value = '处置中');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 2, '已闭环', '已闭环', 'risk_supervision_case_status', '', 'success', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '已闭环', '已闭环', 'risk_supervision_case_status', '', 'success', 'N', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_case_status' AND dict_value = '已闭环');
 
 -- 1.6 客诉状态
-INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
-SELECT '客诉状态', 'risk_supervision_complaint_status', '0', '平台客诉处理状态', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+SELECT '客诉状态', 'risk_supervision_complaint_status', '平台客诉处理状态', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_complaint_status');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 1, '处置中', '处置中', 'risk_supervision_complaint_status', '', 'warning', 'Y', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '处置中', '处置中', 'risk_supervision_complaint_status', '', 'warning', 'Y', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_status' AND dict_value = '处置中');
 
-INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
-SELECT 2, '已闭环', '已闭环', 'risk_supervision_complaint_status', '', 'success', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '已闭环', '已闭环', 'risk_supervision_complaint_status', '', 'success', 'N', '', 1, NOW(), 1, NOW()
 WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_status' AND dict_value = '已闭环');
 
 -- ============================================================

+ 245 - 0
sql/risk_supervision.sql.bak

@@ -0,0 +1,245 @@
+-- ============================================================
+-- 监督抽检 / 平台客诉 模块 - 数据库升级脚本(幂等)
+-- 适用: MySQL 5.7+ / 8.0
+-- 表: 5 张(抽检单 / 案件主表 / 案件节点 / 客诉主表 / 客诉节点)
+-- 字典: 6 个(inspection_result / inspection_status / risk_level /
+--              complaint_source / case_status / complaint_status)
+-- 说明: 使用 INFORMATION_SCHEMA 兼容低版本 MySQL,所有 IF NOT EXISTS
+--       风格的 DDL,支持重复执行不报错
+-- ============================================================
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ============================================================
+-- 1. 字典(6 个)
+-- ============================================================
+
+-- 1.1 抽检结果
+INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
+SELECT '抽检结果', 'risk_supervision_inspection_result', '0', '门店抽检结果(合格/不合格)', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_inspection_result');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '抽检合格', '抽检合格', 'risk_supervision_inspection_result', '', 'success', 'Y', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_inspection_result' AND dict_value = '抽检合格');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '抽检不合格', '抽检不合格', 'risk_supervision_inspection_result', '', 'danger', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_inspection_result' AND dict_value = '抽检不合格');
+
+-- 1.2 抽检审核状态
+INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
+SELECT '抽检审核状态', 'risk_supervision_inspection_status', '0', '门店抽检单审核状态', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_inspection_status');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '待审核', '待审核', 'risk_supervision_inspection_status', '', 'warning', 'Y', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_inspection_status' AND dict_value = '待审核');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '已审核', '已审核', 'risk_supervision_inspection_status', '', 'success', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_inspection_status' AND dict_value = '已审核');
+
+-- 1.3 风险等级
+INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
+SELECT '风险等级', 'risk_supervision_risk_level', '0', '案件风险等级', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_risk_level');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '高风险', '高风险', 'risk_supervision_risk_level', '', 'danger', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_risk_level' AND dict_value = '高风险');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '中风险', '中风险', 'risk_supervision_risk_level', '', 'warning', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_risk_level' AND dict_value = '中风险');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 3, '低风险', '低风险', 'risk_supervision_risk_level', '', 'info', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_risk_level' AND dict_value = '低风险');
+
+-- 1.4 客诉来源
+INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
+SELECT '客诉来源', 'risk_supervision_complaint_source', '0', '平台客诉来源', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_complaint_source');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '消费者投诉', '消费者投诉', 'risk_supervision_complaint_source', '', 'primary', 'Y', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_source' AND dict_value = '消费者投诉');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '媒体舆情', '媒体舆情', 'risk_supervision_complaint_source', '', 'warning', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_source' AND dict_value = '媒体舆情');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 3, '12315平台', '12315平台', 'risk_supervision_complaint_source', '', 'danger', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_source' AND dict_value = '12315平台');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 4, '其他来源', '其他来源', 'risk_supervision_complaint_source', '', 'info', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_source' AND dict_value = '其他来源');
+
+-- 1.5 案件状态
+INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
+SELECT '案件状态', 'risk_supervision_case_status', '0', '不合格案件处理状态', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_case_status');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '处置中', '处置中', 'risk_supervision_case_status', '', 'warning', 'Y', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_case_status' AND dict_value = '处置中');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '已闭环', '已闭环', 'risk_supervision_case_status', '', 'success', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_case_status' AND dict_value = '已闭环');
+
+-- 1.6 客诉状态
+INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, create_time, update_by, update_time)
+SELECT '客诉状态', 'risk_supervision_complaint_status', '0', '平台客诉处理状态', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'risk_supervision_complaint_status');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 1, '处置中', '处置中', 'risk_supervision_complaint_status', '', 'warning', 'Y', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_status' AND dict_value = '处置中');
+
+INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time, update_by, update_time)
+SELECT 2, '已闭环', '已闭环', 'risk_supervision_complaint_status', '', 'success', 'N', '0', '', 'admin', NOW(), 'admin', NOW()
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'risk_supervision_complaint_status' AND dict_value = '已闭环');
+
+-- ============================================================
+-- 2. 业务表(5 张)
+-- ============================================================
+
+-- 2.1 门店抽检单
+CREATE TABLE IF NOT EXISTS `risk_supervision_inspection` (
+    `id`               BIGINT          NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `inspection_code`  VARCHAR(50)     NOT NULL COMMENT '抽检单号 JC+yyyyMMdd+4位序号',
+    `store_id`         BIGINT          DEFAULT NULL COMMENT '门店ID',
+    `store_name`       VARCHAR(200)    DEFAULT NULL COMMENT '门店名称',
+    `inspection_time`  DATETIME        DEFAULT NULL COMMENT '抽检时间',
+    `product_name`     VARCHAR(200)    DEFAULT NULL COMMENT '抽检商品',
+    `inspection_result` VARCHAR(20)    DEFAULT NULL COMMENT '抽检结果(字典:抽检合格/抽检不合格)',
+    `fail_reason`      VARCHAR(1000)   DEFAULT NULL COMMENT '不合格原因',
+    `status`           VARCHAR(20)     DEFAULT '待审核' COMMENT '状态(字典:待审核/已审核)',
+    `inspector_id`     BIGINT          DEFAULT NULL COMMENT '抽检人ID',
+    `inspector_name`   VARCHAR(50)     DEFAULT NULL COMMENT '抽检人姓名',
+    `remark`           VARCHAR(500)    DEFAULT NULL COMMENT '备注',
+    `tenant_id`        VARCHAR(20)     DEFAULT '000000' COMMENT '租户ID',
+    `create_dept`      BIGINT          DEFAULT NULL COMMENT '创建部门',
+    `create_by`        BIGINT          DEFAULT NULL COMMENT '创建人',
+    `create_time`      DATETIME        DEFAULT NULL COMMENT '创建时间',
+    `update_by`        BIGINT          DEFAULT NULL COMMENT '更新人',
+    `update_time`      DATETIME        DEFAULT NULL COMMENT '更新时间',
+    `del_flag`         CHAR(1)         DEFAULT '0' COMMENT '删除标志(0正常 1删除)',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_inspection_code` (`inspection_code`),
+    KEY `idx_inspection_time` (`inspection_time`),
+    KEY `idx_status_result` (`status`, `inspection_result`),
+    KEY `idx_store_id` (`store_id`)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='监督抽检 - 门店抽检单';
+
+-- 2.2 不合格案件主表
+CREATE TABLE IF NOT EXISTS `risk_supervision_case` (
+    `id`               BIGINT          NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `case_code`        VARCHAR(50)     NOT NULL COMMENT '案件编号 AJ+yyyyMMdd+4位序号',
+    `inspection_id`    BIGINT          DEFAULT NULL COMMENT '关联抽检单ID',
+    `inspection_code`  VARCHAR(50)     DEFAULT NULL COMMENT '冗余抽检单号',
+    `store_id`         BIGINT          DEFAULT NULL COMMENT '涉事门店ID',
+    `store_name`       VARCHAR(200)    DEFAULT NULL COMMENT '涉事门店',
+    `product_name`     VARCHAR(200)    DEFAULT NULL COMMENT '不合格产品',
+    `current_node`     INT             DEFAULT 1 COMMENT '当前节点(1-5)',
+    `node_name`        VARCHAR(50)     DEFAULT NULL COMMENT '当前节点名称(冗余)',
+    `risk_level`       VARCHAR(20)     DEFAULT NULL COMMENT '风险等级(字典:高/中/低风险)',
+    `risk_desc`        VARCHAR(2000)   DEFAULT NULL COMMENT '风险描述',
+    `control_measures` VARCHAR(2000)   DEFAULT NULL COMMENT '控制措施',
+    `handler_id`       BIGINT          DEFAULT NULL COMMENT '处理人ID',
+    `handler_name`     VARCHAR(50)     DEFAULT NULL COMMENT '处理人',
+    `status`           VARCHAR(20)     DEFAULT '处置中' COMMENT '状态(字典:处置中/已闭环)',
+    `remark`           VARCHAR(500)    DEFAULT NULL COMMENT '备注',
+    `tenant_id`        VARCHAR(20)     DEFAULT '000000' COMMENT '租户ID',
+    `create_dept`      BIGINT          DEFAULT NULL COMMENT '创建部门',
+    `create_by`        BIGINT          DEFAULT NULL COMMENT '创建人',
+    `create_time`      DATETIME        DEFAULT NULL COMMENT '创建时间',
+    `update_by`        BIGINT          DEFAULT NULL COMMENT '更新人',
+    `update_time`      DATETIME        DEFAULT NULL COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_case_code` (`case_code`),
+    KEY `idx_inspection_id` (`inspection_id`),
+    KEY `idx_status_node` (`status`, `current_node`),
+    KEY `idx_store_id` (`store_id`)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='监督抽检 - 不合格案件主表';
+
+-- 2.3 案件节点处理记录
+CREATE TABLE IF NOT EXISTS `risk_supervision_case_node` (
+    `id`               BIGINT          NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `case_id`          BIGINT          NOT NULL COMMENT '案件ID',
+    `node_code`        INT             NOT NULL COMMENT '节点编号 1-5',
+    `node_name`        VARCHAR(50)     DEFAULT NULL COMMENT '节点名称',
+    `opinion`          VARCHAR(2000)   DEFAULT NULL COMMENT '当前节点处理意见',
+    `attachment_url`   VARCHAR(500)    DEFAULT NULL COMMENT '凭证附件URL(MinIO)',
+    `attachment_name`  VARCHAR(200)    DEFAULT NULL COMMENT '凭证附件原始文件名',
+    `handler_id`       BIGINT          DEFAULT NULL COMMENT '处理人ID',
+    `handler_name`     VARCHAR(50)     DEFAULT NULL COMMENT '处理人',
+    `handle_time`      DATETIME        DEFAULT NULL COMMENT '处理时间',
+    `tenant_id`        VARCHAR(20)     DEFAULT '000000' COMMENT '租户ID',
+    `create_dept`      BIGINT          DEFAULT NULL COMMENT '创建部门',
+    `create_by`        BIGINT          DEFAULT NULL COMMENT '创建人',
+    `create_time`      DATETIME        DEFAULT NULL COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_case_id_node` (`case_id`, `node_code`)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='监督抽检 - 案件节点处理记录';
+
+-- 2.4 平台客诉主表
+CREATE TABLE IF NOT EXISTS `risk_supervision_complaint` (
+    `id`                BIGINT          NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `complaint_code`    VARCHAR(50)     NOT NULL COMMENT '客诉编号 TS+yyyyMMdd+4位序号',
+    `source`            VARCHAR(50)     DEFAULT NULL COMMENT '客诉来源(字典)',
+    `complainant_name`  VARCHAR(50)     DEFAULT NULL COMMENT '投诉人姓名',
+    `complainant_phone` VARCHAR(20)     DEFAULT NULL COMMENT '投诉人电话',
+    `store_id`          BIGINT          DEFAULT NULL COMMENT '涉事门店ID',
+    `store_name`        VARCHAR(200)    DEFAULT NULL COMMENT '涉事门店',
+    `transfer_time`     DATETIME        DEFAULT NULL COMMENT '移交时间',
+    `complaint_content` TEXT            COMMENT '客诉内容',
+    `attachment_url`    VARCHAR(500)    DEFAULT NULL COMMENT '附件URL(MinIO)',
+    `attachment_name`   VARCHAR(200)    DEFAULT NULL COMMENT '附件原始文件名',
+    `current_node`      INT             DEFAULT 1 COMMENT '当前节点(1-7)',
+    `node_name`         VARCHAR(50)     DEFAULT NULL COMMENT '当前节点名称',
+    `status`            VARCHAR(20)     DEFAULT '处置中' COMMENT '状态(字典:处置中/已闭环)',
+    `remark`            VARCHAR(500)    DEFAULT NULL COMMENT '备注',
+    `tenant_id`         VARCHAR(20)     DEFAULT '000000' COMMENT '租户ID',
+    `create_dept`       BIGINT          DEFAULT NULL COMMENT '创建部门',
+    `create_by`         BIGINT          DEFAULT NULL COMMENT '创建人',
+    `create_time`       DATETIME        DEFAULT NULL COMMENT '创建时间',
+    `update_by`         BIGINT          DEFAULT NULL COMMENT '更新人',
+    `update_time`       DATETIME        DEFAULT NULL COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_complaint_code` (`complaint_code`),
+    KEY `idx_status_node` (`status`, `current_node`),
+    KEY `idx_transfer_time` (`transfer_time`),
+    KEY `idx_store_id` (`store_id`)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='监督抽检 - 平台客诉主表';
+
+-- 2.5 客诉节点处理记录
+CREATE TABLE IF NOT EXISTS `risk_supervision_complaint_node` (
+    `id`               BIGINT          NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `complaint_id`     BIGINT          NOT NULL COMMENT '客诉ID',
+    `node_code`        INT             NOT NULL COMMENT '节点编号 1-7',
+    `node_name`        VARCHAR(50)     DEFAULT NULL COMMENT '节点名称',
+    `opinion`          VARCHAR(2000)   DEFAULT NULL COMMENT '当前环节处理意见',
+    `attachment_url`   VARCHAR(500)    DEFAULT NULL COMMENT '凭证附件URL(MinIO)',
+    `attachment_name`  VARCHAR(200)    DEFAULT NULL COMMENT '凭证附件原始文件名',
+    `handler_id`       BIGINT          DEFAULT NULL COMMENT '处理人ID',
+    `handler_name`     VARCHAR(50)     DEFAULT NULL COMMENT '处理人',
+    `handle_time`      DATETIME        DEFAULT NULL COMMENT '处理时间',
+    `tenant_id`        VARCHAR(20)     DEFAULT '000000' COMMENT '租户ID',
+    `create_dept`      BIGINT          DEFAULT NULL COMMENT '创建部门',
+    `create_by`        BIGINT          DEFAULT NULL COMMENT '创建人',
+    `create_time`      DATETIME        DEFAULT NULL COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_complaint_id_node` (`complaint_id`, `node_code`)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='监督抽检 - 客诉节点处理记录';
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- ============================================================
+-- 完成 - 监督抽检 / 平台客诉 模块升级
+-- ============================================================

+ 22 - 22
sql/risk_supervision_menu.sql

@@ -13,10 +13,10 @@ SET NAMES utf8mb4;
 -- ----------------------------------------------------------
 -- 1. 父菜单(插入到 风控平台 菜单下)
 -- ----------------------------------------------------------
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
 SELECT '监督抽检/平台客诉', (
         SELECT menu_id FROM sys_menu WHERE menu_name = '风控平台' AND menu_type = 'M' LIMIT 1
-    ), 12, 'supervision', 'risk/supervision/index', 1, 0, 'C', '0', '0', 'risk:supervision:list', 'monitor', 'admin', NOW(), 'admin', NOW(), '监督抽检与平台客诉目录'
+    ), 12, 'supervision', 'risk/supervision/index', 1, 0, 'C', '0', '0', 'risk:supervision:list', 'monitor', 1, NOW(), 1, NOW(), '监督抽检与平台客诉目录'
 WHERE NOT EXISTS (
     SELECT 1 FROM sys_menu WHERE menu_name = '监督抽检/平台客诉' AND menu_type = 'C'
 );
@@ -32,50 +32,50 @@ SET @supervisionMenuId = COALESCE(
 -- ----------------------------------------------------------
 -- 2. 抽检单按钮权限(5 个)
 -- ----------------------------------------------------------
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-SELECT '抽检登记查询', @supervisionMenuId, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:inspection:list', '#', 'admin', NOW(), 'admin', NOW(), '门店抽检当日审核登记查询'
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT '抽检登记查询', @supervisionMenuId, 1, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:inspection:list', '#', 1, NOW(), 1, NOW(), '门店抽检当日审核登记查询'
 WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'risk:supervision:inspection:list');
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-SELECT '抽检登记新增', @supervisionMenuId, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:inspection:add', '#', 'admin', NOW(), 'admin', NOW(), '新增门店抽检登记'
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT '抽检登记新增', @supervisionMenuId, 2, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:inspection:add', '#', 1, NOW(), 1, NOW(), '新增门店抽检登记'
 WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'risk:supervision:inspection:add');
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-SELECT '抽检登记编辑', @supervisionMenuId, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:inspection:edit', '#', 'admin', NOW(), 'admin', NOW(), '编辑门店抽检登记'
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT '抽检登记编辑', @supervisionMenuId, 3, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:inspection:edit', '#', 1, NOW(), 1, NOW(), '编辑门店抽检登记'
 WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'risk:supervision:inspection:edit');
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-SELECT '抽检登记删除', @supervisionMenuId, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:inspection:remove', '#', 'admin', NOW(), 'admin', NOW(), '删除门店抽检登记'
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT '抽检登记删除', @supervisionMenuId, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:inspection:remove', '#', 1, NOW(), 1, NOW(), '删除门店抽检登记'
 WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'risk:supervision:inspection:remove');
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-SELECT '抽检登记导出', @supervisionMenuId, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:inspection:export', '#', 'admin', NOW(), 'admin', NOW(), '导出抽检登记'
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT '抽检登记导出', @supervisionMenuId, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:inspection:export', '#', 1, NOW(), 1, NOW(), '导出抽检登记'
 WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'risk:supervision:inspection:export');
 
 -- ----------------------------------------------------------
 -- 3. 案件按钮权限(2 个)
 -- ----------------------------------------------------------
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-SELECT '案件查询', @supervisionMenuId, 6, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:case:list', '#', 'admin', NOW(), 'admin', NOW(), '不合格案件查询'
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT '案件查询', @supervisionMenuId, 6, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:case:list', '#', 1, NOW(), 1, NOW(), '不合格案件查询'
 WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'risk:supervision:case:list');
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-SELECT '案件处理', @supervisionMenuId, 7, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:case:process', '#', 'admin', NOW(), 'admin', NOW(), '处理不合格案件各节点'
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT '案件处理', @supervisionMenuId, 7, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:case:process', '#', 1, NOW(), 1, NOW(), '处理不合格案件各节点'
 WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'risk:supervision:case:process');
 
 -- ----------------------------------------------------------
 -- 4. 客诉按钮权限(3 个)
 -- ----------------------------------------------------------
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-SELECT '客诉查询', @supervisionMenuId, 8, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:complaint:list', '#', 'admin', NOW(), 'admin', NOW(), '平台客诉查询'
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT '客诉查询', @supervisionMenuId, 8, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:complaint:list', '#', 1, NOW(), 1, NOW(), '平台客诉查询'
 WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'risk:supervision:complaint:list');
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-SELECT '客诉新增', @supervisionMenuId, 9, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:complaint:add', '#', 'admin', NOW(), 'admin', NOW(), '新增平台客诉登记'
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT '客诉新增', @supervisionMenuId, 9, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:complaint:add', '#', 1, NOW(), 1, NOW(), '新增平台客诉登记'
 WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'risk:supervision:complaint:add');
 
-INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
-SELECT '客诉处理', @supervisionMenuId, 10, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:complaint:process', '#', 'admin', NOW(), 'admin', NOW(), '处理平台客诉各环节'
+INSERT IGNORE INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT '客诉处理', @supervisionMenuId, 10, '#', '', 1, 0, 'F', '0', '0', 'risk:supervision:complaint:process', '#', 1, NOW(), 1, NOW(), '处理平台客诉各环节'
 WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'risk:supervision:complaint:process');
 
 -- ============================================================

+ 82 - 0
sql/risk_supervision_v2.sql

@@ -0,0 +1,82 @@
+-- ============================================================
+-- 监督抽检 / 平台客诉 模块升级 v2
+-- 1) 抽检表 +8 字段
+-- 2) 客诉表 +6 字段
+-- 3) 4 个新字典
+-- 4) 案件状态:已闭环 -> 归档
+-- ============================================================
+
+USE risk_control_platform;
+
+-- ============== 1. 抽检表加字段 ==============
+ALTER TABLE `risk_supervision_inspection`
+    ADD COLUMN `product_barcode`   VARCHAR(100) DEFAULT NULL COMMENT '抽检商品条码'        AFTER `product_name`,
+    ADD COLUMN `lot_no`            VARCHAR(100) DEFAULT NULL COMMENT '抽检商品批号'        AFTER `product_barcode`,
+    ADD COLUMN `production_date`   DATE         DEFAULT NULL COMMENT '抽检商品生产日期'     AFTER `lot_no`,
+    ADD COLUMN `supplier`          VARCHAR(200) DEFAULT NULL COMMENT '抽检商品供应商'       AFTER `production_date`,
+    ADD COLUMN `sample_quantity`   INT          DEFAULT NULL COMMENT '抽样数量'             AFTER `supplier`,
+    ADD COLUMN `detect_org`        VARCHAR(200) DEFAULT NULL COMMENT '检测机构'             AFTER `sample_quantity`,
+    ADD COLUMN `fail_item`         VARCHAR(50)  DEFAULT NULL COMMENT '不合格项目(字典)'     AFTER `detect_org`,
+    ADD COLUMN `fail_value`        VARCHAR(200) DEFAULT NULL COMMENT '不合格指标数值'       AFTER `fail_item`;
+
+-- ============== 2. 客诉表加字段 ==============
+ALTER TABLE `risk_supervision_complaint`
+    ADD COLUMN `product_barcode`   VARCHAR(100) DEFAULT NULL COMMENT '客诉商品条码'        AFTER `complainant_phone`,
+    ADD COLUMN `product_name`      VARCHAR(200) DEFAULT NULL COMMENT '客诉商品名称'        AFTER `product_barcode`,
+    ADD COLUMN `lot_no`            VARCHAR(100) DEFAULT NULL COMMENT '客诉商品批号'        AFTER `product_name`,
+    ADD COLUMN `production_date`   DATE         DEFAULT NULL COMMENT '客诉商品生产日期'     AFTER `lot_no`,
+    ADD COLUMN `supplier`          VARCHAR(200) DEFAULT NULL COMMENT '客诉商品供应商'       AFTER `production_date`,
+    ADD COLUMN `quantity`          INT          DEFAULT NULL COMMENT '客诉数量'             AFTER `supplier`;
+
+-- ============== 3. 字典:不合格项目 ==============
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+VALUES ('不合格项目', 'risk_supervision_fail_item', '抽检不合格项目字典', 1, NOW(), 1, NOW());
+
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time) VALUES
+(1, '农药残留',  '农药残留',  'risk_supervision_fail_item', '', 'danger', 'N', '', 1, NOW(), 1, NOW()),
+(2, '兽药残留',  '兽药残留',  'risk_supervision_fail_item', '', 'danger', 'N', '', 1, NOW(), 1, NOW()),
+(3, '重金属超标','重金属超标','risk_supervision_fail_item', '', 'danger', 'N', '', 1, NOW(), 1, NOW()),
+(4, '微生物超标','微生物超标','risk_supervision_fail_item', '', 'danger', 'N', '', 1, NOW(), 1, NOW()),
+(5, '添加剂超标','添加剂超标','risk_supervision_fail_item', '', 'danger', 'N', '', 1, NOW(), 1, NOW()),
+(6, '感官指标',  '感官指标',  'risk_supervision_fail_item', '', 'warning','N', '', 1, NOW(), 1, NOW()),
+(7, '标签标识',  '标签标识',  'risk_supervision_fail_item', '', 'warning','N', '', 1, NOW(), 1, NOW()),
+(8, '其他',      '其他',      'risk_supervision_fail_item', '', 'info',   'N', '', 1, NOW(), 1, NOW());
+
+-- ============== 4. 字典:控制措施 ==============
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+VALUES ('控制措施', 'risk_supervision_control_measure', '风险控制措施字典', 1, NOW(), 1, NOW());
+
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time) VALUES
+(1, '立即下架涉事产品、暂停销售',     '立即下架涉事产品、暂停销售',     'risk_supervision_control_measure', '', 'danger',  'N', '', 1, NOW(), 1, NOW()),
+(2, '通知门店停止销售并下架',         '通知门店停止销售并下架',         'risk_supervision_control_measure', '', 'danger',  'N', '', 1, NOW(), 1, NOW()),
+(3, '暂停同批次产品销售',             '暂停同批次产品销售',             'risk_supervision_control_measure', '', 'warning',  'N', '', 1, NOW(), 1, NOW()),
+(4, '通知供应商召回同批次产品',       '通知供应商召回同批次产品',       'risk_supervision_control_measure', '', 'warning',  'N', '', 1, NOW(), 1, NOW()),
+(5, '开展门店全面排查',               '开展门店全面排查',               'risk_supervision_control_measure', '', 'primary',  'N', '', 1, NOW(), 1, NOW()),
+(6, '暂不处置,继续观察',             '暂不处置,继续观察',             'risk_supervision_control_measure', '', 'info',     'N', '', 1, NOW(), 1, NOW());
+
+-- ============== 5. 字典:复检结果 ==============
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+VALUES ('复检结果', 'risk_supervision_review_result', '异议复检结果', 1, NOW(), 1, NOW());
+
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time) VALUES
+(1, '合格',    '合格',    'risk_supervision_review_result', '', 'success', 'N', '', 1, NOW(), 1, NOW()),
+(2, '仍不合格','仍不合格','risk_supervision_review_result', '', 'danger',  'N', '', 1, NOW(), 1, NOW());
+
+-- ============== 6. 字典:调查报告原因 ==============
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+VALUES ('调查报告原因', 'risk_supervision_investigate_reason', '调查报告原因字典', 1, NOW(), 1, NOW());
+
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time) VALUES
+(1, '生产环节',     '生产环节',     'risk_supervision_investigate_reason', '', 'primary', 'N', '', 1, NOW(), 1, NOW()),
+(2, '储存环节',     '储存环节',     'risk_supervision_investigate_reason', '', 'primary', 'N', '', 1, NOW(), 1, NOW()),
+(3, '运输环节',     '运输环节',     'risk_supervision_investigate_reason', '', 'primary', 'N', '', 1, NOW(), 1, NOW()),
+(4, '门店陈列储存', '门店陈列储存', 'risk_supervision_investigate_reason', '', 'primary', 'N', '', 1, NOW(), 1, NOW()),
+(5, '其他环节',     '其他环节',     'risk_supervision_investigate_reason', '', 'info',    'N', '', 1, NOW(), 1, NOW());
+
+-- ============== 7. 案件状态字典:补 "归档" ==============
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+VALUES (3, '归档', '归档', 'risk_supervision_case_status', '', 'success', 'N', '案件闭环后归档', 1, NOW(), 1, NOW());
+
+-- ============================================================
+-- 完成
+-- ============================================================

+ 70 - 0
sql/risk_supervision_v2_part2.sql

@@ -0,0 +1,70 @@
+-- ============================================================
+-- 监督抽检 / 平台客诉 v2 第二部分
+-- 1) 客诉表 +6 字段(抽检表字段已存在,跳过)
+-- 2) 4 个新字典(用 IGNORE 跳过已存在)
+-- 3) 案件状态字典:补 "归档"
+-- ============================================================
+
+USE risk_control_platform;
+
+-- ============== 1. 客诉表加字段 ==============
+ALTER TABLE `risk_supervision_complaint`
+    ADD COLUMN `product_barcode`   VARCHAR(100) DEFAULT NULL COMMENT '客诉商品条码'        AFTER `complainant_phone`,
+    ADD COLUMN `product_name`      VARCHAR(200) DEFAULT NULL COMMENT '客诉商品名称'        AFTER `product_barcode`,
+    ADD COLUMN `lot_no`            VARCHAR(100) DEFAULT NULL COMMENT '客诉商品批号'        AFTER `product_name`,
+    ADD COLUMN `production_date`   DATE         DEFAULT NULL COMMENT '客诉商品生产日期'     AFTER `lot_no`,
+    ADD COLUMN `supplier`          VARCHAR(200) DEFAULT NULL COMMENT '客诉商品供应商'       AFTER `production_date`,
+    ADD COLUMN `quantity`          INT          DEFAULT NULL COMMENT '客诉数量'             AFTER `supplier`;
+
+-- ============== 2. 字典:不合格项目 ==============
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+VALUES ('不合格项目', 'risk_supervision_fail_item', '抽检不合格项目字典', 1, NOW(), 1, NOW());
+
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time) VALUES
+(1, '农药残留',  '农药残留',  'risk_supervision_fail_item', '', 'danger', 'N', '', 1, NOW(), 1, NOW()),
+(2, '兽药残留',  '兽药残留',  'risk_supervision_fail_item', '', 'danger', 'N', '', 1, NOW(), 1, NOW()),
+(3, '重金属超标','重金属超标','risk_supervision_fail_item', '', 'danger', 'N', '', 1, NOW(), 1, NOW()),
+(4, '微生物超标','微生物超标','risk_supervision_fail_item', '', 'danger', 'N', '', 1, NOW(), 1, NOW()),
+(5, '添加剂超标','添加剂超标','risk_supervision_fail_item', '', 'danger', 'N', '', 1, NOW(), 1, NOW()),
+(6, '感官指标',  '感官指标',  'risk_supervision_fail_item', '', 'warning','N', '', 1, NOW(), 1, NOW()),
+(7, '标签标识',  '标签标识',  'risk_supervision_fail_item', '', 'warning','N', '', 1, NOW(), 1, NOW()),
+(8, '其他',      '其他',      'risk_supervision_fail_item', '', 'info',   'N', '', 1, NOW(), 1, NOW());
+
+-- ============== 3. 字典:控制措施 ==============
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+VALUES ('控制措施', 'risk_supervision_control_measure', '风险控制措施字典', 1, NOW(), 1, NOW());
+
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time) VALUES
+(1, '立即下架涉事产品、暂停销售',     '立即下架涉事产品、暂停销售',     'risk_supervision_control_measure', '', 'danger',  'N', '', 1, NOW(), 1, NOW()),
+(2, '通知门店停止销售并下架',         '通知门店停止销售并下架',         'risk_supervision_control_measure', '', 'danger',  'N', '', 1, NOW(), 1, NOW()),
+(3, '暂停同批次产品销售',             '暂停同批次产品销售',             'risk_supervision_control_measure', '', 'warning',  'N', '', 1, NOW(), 1, NOW()),
+(4, '通知供应商召回同批次产品',       '通知供应商召回同批次产品',       'risk_supervision_control_measure', '', 'warning',  'N', '', 1, NOW(), 1, NOW()),
+(5, '开展门店全面排查',               '开展门店全面排查',               'risk_supervision_control_measure', '', 'primary',  'N', '', 1, NOW(), 1, NOW()),
+(6, '暂不处置,继续观察',             '暂不处置,继续观察',             'risk_supervision_control_measure', '', 'info',     'N', '', 1, NOW(), 1, NOW());
+
+-- ============== 4. 字典:复检结果 ==============
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+VALUES ('复检结果', 'risk_supervision_review_result', '异议复检结果', 1, NOW(), 1, NOW());
+
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time) VALUES
+(1, '合格',    '合格',    'risk_supervision_review_result', '', 'success', 'N', '', 1, NOW(), 1, NOW()),
+(2, '仍不合格','仍不合格','risk_supervision_review_result', '', 'danger',  'N', '', 1, NOW(), 1, NOW());
+
+-- ============== 5. 字典:调查报告原因 ==============
+INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, remark, create_by, create_time, update_by, update_time)
+VALUES ('调查报告原因', 'risk_supervision_investigate_reason', '调查报告原因字典', 1, NOW(), 1, NOW());
+
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time) VALUES
+(1, '生产环节',     '生产环节',     'risk_supervision_investigate_reason', '', 'primary', 'N', '', 1, NOW(), 1, NOW()),
+(2, '储存环节',     '储存环节',     'risk_supervision_investigate_reason', '', 'primary', 'N', '', 1, NOW(), 1, NOW()),
+(3, '运输环节',     '运输环节',     'risk_supervision_investigate_reason', '', 'primary', 'N', '', 1, NOW(), 1, NOW()),
+(4, '门店陈列储存', '门店陈列储存', 'risk_supervision_investigate_reason', '', 'primary', 'N', '', 1, NOW(), 1, NOW()),
+(5, '其他环节',     '其他环节',     'risk_supervision_investigate_reason', '', 'info',    'N', '', 1, NOW(), 1, NOW());
+
+-- ============== 6. 案件状态字典:补 "归档" ==============
+INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, remark, create_by, create_time, update_by, update_time)
+VALUES (3, '归档', '归档', 'risk_supervision_case_status', '', 'success', 'N', '案件闭环后归档', 1, NOW(), 1, NOW());
+
+-- ============================================================
+-- 完成
+-- ============================================================

+ 57 - 0
sql/risk_tables.sql

@@ -397,3 +397,60 @@ CREATE TABLE `risk_public_opinion` (
   KEY `idx_publish_time` (`publish_time`),
   KEY `idx_responsible_person` (`responsible_person`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='舆情推送表';
+
+-- 12. 监督抽检案件表 risk_supervision_case
+DROP TABLE IF EXISTS `risk_supervision_case`;
+CREATE TABLE `risk_supervision_case` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `case_code` VARCHAR(50) NOT NULL COMMENT '案件编号(业务唯一)',
+  `inspection_id` BIGINT COMMENT '关联抽检单ID',
+  `inspection_code` VARCHAR(50) COMMENT '冗余抽检单号',
+  `store_id` BIGINT COMMENT '门店ID',
+  `store_name` VARCHAR(200) COMMENT '冗余门店名称',
+  `product_name` VARCHAR(200) COMMENT '产品名称',
+  `current_node` INT DEFAULT 1 COMMENT '当前节点 1-5',
+  `node_name` VARCHAR(50) COMMENT '当前节点名称',
+  `risk_level` VARCHAR(20) COMMENT '风险等级',
+  `risk_desc` VARCHAR(2000) COMMENT '风险描述',
+  `control_measures` VARCHAR(2000) COMMENT '控制措施',
+  `handler_id` BIGINT COMMENT '处理人ID',
+  `handler_name` VARCHAR(50) COMMENT '处理人',
+  `status` VARCHAR(20) DEFAULT '处置中' COMMENT '案件状态(处置中/已闭环)',
+  `remark` VARCHAR(500) COMMENT '备注',
+  `tenant_id` VARCHAR(20) DEFAULT '000000' COMMENT '租户编号',
+  `create_dept` BIGINT COMMENT '创建部门',
+  `create_by` BIGINT COMMENT '创建者',
+  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_by` BIGINT COMMENT '更新者',
+  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_case_code` (`case_code`),
+  KEY `idx_inspection_id` (`inspection_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='监督抽检-不合格案件';
+
+-- 13. 监督抽检案件节点处理记录表 risk_supervision_case_node
+DROP TABLE IF EXISTS `risk_supervision_case_node`;
+CREATE TABLE `risk_supervision_case_node` (
+  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `case_id` BIGINT NOT NULL COMMENT '案件ID',
+  `node_code` INT NOT NULL COMMENT '节点编号 1-5',
+  `node_name` VARCHAR(50) COMMENT '节点名称',
+  `opinion` VARCHAR(2000) COMMENT '当前节点处理意见',
+  `extra_data` TEXT COMMENT '节点扩展数据(JSON,各节点动态字段)',
+  `attachment_url` VARCHAR(500) COMMENT '凭证附件URL(MinIO)',
+  `attachment_name` VARCHAR(200) COMMENT '凭证附件原始文件名',
+  `handler_id` BIGINT COMMENT '处理人ID',
+  `handler_name` VARCHAR(50) COMMENT '处理人',
+  `handle_time` DATETIME COMMENT '处理时间',
+  `tenant_id` VARCHAR(20) DEFAULT '000000' COMMENT '租户编号',
+  `create_dept` BIGINT COMMENT '创建部门',
+  `create_by` BIGINT COMMENT '创建者',
+  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_by` BIGINT COMMENT '更新者',
+  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志',
+  PRIMARY KEY (`id`),
+  KEY `idx_case_id_node` (`case_id`, `node_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='监督抽检-案件节点处理记录';