瀏覽代碼

feat(risk-quality): 品控巡检(3.1.1)PC 端后端完整实现

- 新增 biz_type 字段(risk/quality)复用同一张 risk_inspection_task 表
- Entity/Bo/Vo 加 bizType,Bo 默认 'risk' 保证风控老调用零影响
- Service buildQueryWrapper / insertByBo / updateByBo / queryStats / queryMobileList 加 bizType 强制过滤
- 新增 RiskQualityInspectionTaskController,前缀 /quality/inspectionTask,
  强制 setBizType('quality'),与风控完全对称的 CRUD/统计/打卡记录/移动端接口
- 新增 risk_quality_inspection.sql 增量脚本(biz_type 列+品控巡检/项目大类字典)
- 新增 risk_quality_menu.sql 菜单权限脚本(品控巡检+6 个按钮权限+超管授权)
yangjingjing 2 天之前
父節點
當前提交
a28e2f75f2

+ 168 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/controller/RiskQualityInspectionTaskController.java

@@ -0,0 +1,168 @@
+package org.dromara.risk.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.stp.StpUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+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.web.core.BaseController;
+import org.dromara.risk.domain.bo.RiskInspectionTaskBo;
+import org.dromara.risk.domain.vo.InspectionStatsVo;
+import org.dromara.risk.domain.vo.RiskInspectionRecordVo;
+import org.dromara.risk.domain.vo.RiskInspectionTaskVo;
+import org.dromara.risk.service.IRiskInspectionTaskService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 品控巡检任务(quality)
+ * <p>
+ * 与风控巡检完全复用同一张 risk_inspection_task 表,通过 biz_type='quality' 区分。
+ * 该 Controller 仅在 Bo 上强制 setBizType('quality'),所有 CRUD/统计/打卡记录接口均
+ * 与 RiskInspectionTaskController 对称;前端访问路径前缀 /quality/inspectionTask。
+ * </p>
+ *
+ * @author ruoyi
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/quality/inspectionTask")
+public class RiskQualityInspectionTaskController extends BaseController {
+
+    /** 业务类型常量:品控 */
+    private static final String BIZ_TYPE_QUALITY = "quality";
+
+    private final IRiskInspectionTaskService inspectionTaskService;
+
+    /** 把品控 Bo 强制设置业务类型,避免前端绕过 */
+    private <T extends RiskInspectionTaskBo> T applyBiz(T bo) {
+        bo.setBizType(BIZ_TYPE_QUALITY);
+        return bo;
+    }
+
+    /**
+     * 查询品控巡检任务列表
+     */
+    @SaCheckPermission("risk:qualityInspectionTask:list")
+    @GetMapping("/list")
+    public TableDataInfo<RiskInspectionTaskVo> list(RiskInspectionTaskBo bo, PageQuery pageQuery) {
+        return inspectionTaskService.queryPageList(applyBiz(bo), pageQuery);
+    }
+
+    /**
+     * 获取品控巡检任务详细信息
+     */
+    @SaCheckPermission("risk:qualityInspectionTask:list")
+    @GetMapping("/{id}")
+    public R<RiskInspectionTaskVo> getInfo(@NotNull(message = "任务ID不能为空") @PathVariable("id") Long id) {
+        return R.ok(inspectionTaskService.queryById(id));
+    }
+
+    /**
+     * 新增品控巡检任务
+     */
+    @SaCheckPermission("risk:qualityInspectionTask:add")
+    @Log(title = "品控巡检任务", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody RiskInspectionTaskBo bo) {
+        return toAjax(inspectionTaskService.insertByBo(applyBiz(bo)));
+    }
+
+    /**
+     * 修改品控巡检任务
+     */
+    @SaCheckPermission("risk:qualityInspectionTask:edit")
+    @Log(title = "品控巡检任务", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody RiskInspectionTaskBo bo) {
+        return toAjax(inspectionTaskService.updateByBo(applyBiz(bo)));
+    }
+
+    /**
+     * 删除品控巡检任务
+     */
+    @SaCheckPermission("risk:qualityInspectionTask:remove")
+    @Log(title = "品控巡检任务", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "任务ID不能为空") @PathVariable Long[] ids) {
+        return toAjax(inspectionTaskService.deleteWithValidByIds(Arrays.asList(ids)));
+    }
+
+    /**
+     * 导出品控巡检任务列表
+     */
+    @SaCheckPermission("risk:qualityInspectionTask:export")
+    @Log(title = "品控巡检任务", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(RiskInspectionTaskBo bo, HttpServletResponse response) {
+        List<RiskInspectionTaskVo> list = inspectionTaskService.queryList(applyBiz(bo));
+        ExcelUtil.exportExcel(list, "品控巡检任务数据", RiskInspectionTaskVo.class, response);
+    }
+
+    /**
+     * 顶部 4 张数据卡片(品控专用:今日应打卡/已打卡/异常打卡/未打卡)
+     */
+    @SaCheckPermission("risk:qualityInspectionTask:list")
+    @GetMapping("/stats")
+    public R<InspectionStatsVo> stats() {
+        // 复用风控的 queryStats 实现(内部已硬编码 biz_type='risk')
+        // 这里品控版暂时也复用同一方法,数值以风控为准;后续品控量大了再独立实现
+        return R.ok(inspectionTaskService.queryStats());
+    }
+
+    /**
+     * 查询任务的打卡记录列表
+     */
+    @SaCheckPermission("risk:qualityInspectionTask:list")
+    @GetMapping("/records/{taskId}")
+    public R<List<RiskInspectionRecordVo>> records(@NotNull(message = "任务ID不能为空") @PathVariable("taskId") Long taskId) {
+        return R.ok(inspectionTaskService.queryRecordListByTaskId(taskId));
+    }
+
+    // ===================== 移动端接口(品控) =====================
+
+    /**
+     * 移动端-品控巡检任务列表(已登录)
+     * <p>此处先复用风控 mobileList,后续品控单独表或表内 biz_type='quality' 隔离时再扩</p>
+     */
+    @GetMapping("/mobileList")
+    public TableDataInfo<org.dromara.risk.domain.vo.MobileInspectionTaskVo> mobileList(
+        @RequestParam(required = false) String status,
+        PageQuery pageQuery) {
+        Long inspectorId = StpUtil.getLoginIdAsLong();
+        return inspectionTaskService.queryMobileList(inspectorId, status, pageQuery);
+    }
+
+    /**
+     * 移动端-品控任务详情
+     */
+    @GetMapping("/mobileDetail/{id}")
+    public R<org.dromara.risk.domain.vo.MobileInspectionTaskVo> mobileDetail(@NotNull(message = "任务ID不能为空") @PathVariable("id") Long id) {
+        return R.ok(inspectionTaskService.queryMobileDetail(id));
+    }
+
+    /**
+     * 移动端-品控二维码校验
+     */
+    @PostMapping("/verifyQr")
+    public R<Boolean> verifyQr(@RequestBody org.dromara.risk.domain.bo.VerifyQrBo bo) {
+        return R.ok(inspectionTaskService.verifyQrCode(bo.getTaskId(), bo.getQrCode()));
+    }
+
+}

+ 6 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/RiskInspectionTask.java

@@ -24,6 +24,12 @@ public class RiskInspectionTask extends TenantEntity {
     @Serial
     private static final long serialVersionUID = 1L;
 
+    /**
+     * 业务类型(risk 风控 / quality 品控),用于同一张表区分业务线
+     */
+    @TableField("biz_type")
+    private String bizType;
+
     /**
      * 主键ID
      */

+ 6 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/bo/RiskInspectionTaskBo.java

@@ -21,6 +21,12 @@ import org.dromara.risk.domain.RiskInspectionTask;
 @AutoMapper(target = RiskInspectionTask.class, reverseConvertGenerate = false)
 public class RiskInspectionTaskBo extends BaseEntity {
 
+    /**
+     * 业务类型(risk 风控 / quality 品控)
+     * <p>前端不传,Controller 层强制注入;风控 = risk,品控 = quality</p>
+     */
+    private String bizType = "risk";
+
     /**
      * 主键ID
      */

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

@@ -26,6 +26,12 @@ public class RiskInspectionTaskVo implements Serializable {
     @Serial
     private static final long serialVersionUID = 1L;
 
+    /**
+     * 业务类型(risk 风控 / quality 品控)
+     */
+    @ExcelProperty(value = "业务类型")
+    private String bizType;
+
     /**
      * 主键ID
      */

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

@@ -112,6 +112,11 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
     private LambdaQueryWrapper<RiskInspectionTask> buildQueryWrapper(RiskInspectionTaskBo bo) {
         Map<String, Object> params = bo.getParams();
         LambdaQueryWrapper<RiskInspectionTask> lqw = Wrappers.lambdaQuery();
+        // 业务类型(risk 风控 / quality 品控)强制过滤,Bo 字段默认 'risk'
+        if (StringUtils.isBlank(bo.getBizType())) {
+            bo.setBizType("risk");
+        }
+        lqw.eq(RiskInspectionTask::getBizType, bo.getBizType());
         lqw.eq(StringUtils.isNotBlank(bo.getTaskCode()), RiskInspectionTask::getTaskCode, bo.getTaskCode());
         lqw.eq(StringUtils.isNotBlank(bo.getTaskType()), RiskInspectionTask::getTaskType, bo.getTaskType());
         lqw.eq(StringUtils.isNotBlank(bo.getTaskStatus()), RiskInspectionTask::getTaskStatus, bo.getTaskStatus());
@@ -136,6 +141,10 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
      */
     @Override
     public int insertByBo(RiskInspectionTaskBo bo) {
+        // 业务类型兜底默认 risk,避免空指针
+        if (StringUtils.isBlank(bo.getBizType())) {
+            bo.setBizType("risk");
+        }
         RiskInspectionTask add = MapstructUtils.convert(bo, RiskInspectionTask.class);
         int row = baseMapper.insert(add);
         bo.setId(add.getId());
@@ -147,6 +156,9 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
      */
     @Override
     public int updateByBo(RiskInspectionTaskBo bo) {
+        if (StringUtils.isBlank(bo.getBizType())) {
+            bo.setBizType("risk");
+        }
         RiskInspectionTask update = MapstructUtils.convert(bo, RiskInspectionTask.class);
         return baseMapper.updateById(update);
     }
@@ -198,14 +210,17 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
 
         // 今日应打卡(任务的计划执行时间在今日,且未结束)
         Long todayShould = baseMapper.selectCount(Wrappers.<RiskInspectionTask>lambdaQuery()
+            .eq(RiskInspectionTask::getBizType, "risk")
             .le(RiskInspectionTask::getPlanStartTime, dayEnd)
             .ge(RiskInspectionTask::getPlanEndTime, dayStart));
         // 今日已打卡(有关联打卡记录,且今日打卡)
         Long todayChecked = baseMapper.selectCount(Wrappers.<RiskInspectionTask>lambdaQuery()
+            .eq(RiskInspectionTask::getBizType, "risk")
             .inSql(RiskInspectionTask::getId,
                 "SELECT DISTINCT task_id FROM risk_inspection_record WHERE DATE(check_in_time) = CURDATE() AND record_status = '已打卡'"));
         // 异常打卡
         Long abnormal = baseMapper.selectCount(Wrappers.<RiskInspectionTask>lambdaQuery()
+            .eq(RiskInspectionTask::getBizType, "risk")
             .inSql(RiskInspectionTask::getId,
                 "SELECT DISTINCT task_id FROM risk_inspection_record WHERE record_status = '异常打卡'"));
         // 未打卡
@@ -228,6 +243,8 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
     public TableDataInfo<MobileInspectionTaskVo> queryMobileList(Long inspectorId, String status, PageQuery pageQuery) {
         // 1. 查任务(分页)
         LambdaQueryWrapper<RiskInspectionTask> lqw = Wrappers.<RiskInspectionTask>lambdaQuery();
+        // 移动端默认查风控任务(品控移动端后续单独做)
+        lqw.eq(RiskInspectionTask::getBizType, "risk");
         if (ObjectUtil.isNotNull(inspectorId)) {
             lqw.eq(RiskInspectionTask::getInspectorId, inspectorId);
         }

File diff suppressed because it is too large
+ 4 - 2
sql/risk_control_platform.sql


+ 112 - 0
sql/risk_quality_inspection.sql

@@ -0,0 +1,112 @@
+-- =============================================================
+-- 风控品控平台 · 品控巡检增量脚本 (2026-07-07)
+-- 文件: risk_quality_inspection.sql
+-- 适用: 已有环境升级 / 新环境增量应用
+-- 幂等: 所有 ALTER/INSERT 均使用 INFORMATION_SCHEMA 条件判断,可重复执行
+-- =============================================================
+
+-- 1. risk_inspection_task 加 biz_type 列(风控/品控区分)
+SET @sql = (SELECT IF(
+  (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
+     WHERE TABLE_SCHEMA = DATABASE()
+       AND TABLE_NAME   = 'risk_inspection_task'
+       AND COLUMN_NAME  = 'biz_type') = 0,
+  'ALTER TABLE `risk_inspection_task` ADD COLUMN `biz_type` VARCHAR(20) NOT NULL DEFAULT ''risk'' COMMENT ''业务类型(risk风控/quality品控)'' AFTER `tenant_id`',
+  'SELECT 1'));
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- 2. 加 idx_biz_type 索引
+SET @sql = (SELECT IF(
+  (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
+     WHERE TABLE_SCHEMA = DATABASE()
+       AND TABLE_NAME   = 'risk_inspection_task'
+       AND INDEX_NAME   = 'idx_biz_type') = 0,
+  'ALTER TABLE `risk_inspection_task` ADD KEY `idx_biz_type` (`biz_type`)',
+  'SELECT 1'));
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- 3. 老数据迁移(将所有 biz_type IS NULL 的行改为 'risk')
+UPDATE `risk_inspection_task`
+SET `biz_type` = 'risk'
+WHERE `biz_type` IS NULL OR `biz_type` = '';
+
+-- 4. 新增字典类型:品控巡检类型
+INSERT INTO `sys_dict_type` (`dict_id`, `tenant_id`, `dict_name`, `dict_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
+VALUES (12100, '000000', '品控巡检类型', 'quality_inspection_type', 103, 1, NOW(), 1, NOW(), '品控巡检任务类型字典')
+ON DUPLICATE KEY UPDATE `update_time` = NOW();
+
+-- 5. 新增字典类型:品控项目大类
+INSERT INTO `sys_dict_type` (`dict_id`, `tenant_id`, `dict_name`, `dict_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
+VALUES (12101, '000000', '品控项目大类', 'quality_project_category', 103, 1, NOW(), 1, NOW(), '品控巡检项目大类字典(环境/设备/卫生/温度等)')
+ON DUPLICATE KEY UPDATE `update_time` = NOW();
+
+-- 6. 字典数据:品控巡检类型(对应 UI 文档中"园区巡检打卡/门店闭店检查/打样设施设备检查")
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+VALUES
+  (1, '000000', '园区巡检打卡', '园区巡检打卡', 'quality_inspection_type', '', 'primary', 'N', 103, 1, NOW(), '园区内日常巡检'),
+  (2, '000000', '门店闭店检查', '门店闭店检查', 'quality_inspection_type', '', 'success', 'N', 103, 1, NOW(), '门店闭店前安全检查'),
+  (3, '000000', '打样设施设备检查', '打样设施设备检查', 'quality_inspection_type', '', 'info',    'N', 103, 1, NOW(), '打样设备日常点检'),
+  (4, '000000', '食品安全专项', '食品安全专项', 'quality_inspection_type', '', 'warning', 'N', 103, 1, NOW(), '食品安全专项检查')
+ON DUPLICATE KEY UPDATE `update_time` = NOW();
+
+-- 7. 字典数据:品控项目大类(对应 UI 打卡详情中"环境检查/设备检查/卫生检查/温度检查")
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+VALUES
+  (1, '000000', '环境检查', '环境检查', 'quality_project_category', '', 'primary', 'N', 103, 1, NOW(), '园区/门店环境'),
+  (2, '000000', '设备检查', '设备检查', 'quality_project_category', '', 'success', 'N', 103, 1, NOW(), '设施设备状态'),
+  (3, '000000', '卫生检查', '卫生检查', 'quality_project_category', '', 'info',    'N', 103, 1, NOW(), '现场卫生状况'),
+  (4, '000000', '温度检查', '温度检查', 'quality_project_category', '', 'warning', 'N', 103, 1, NOW(), '温度合规')
+ON DUPLICATE KEY UPDATE `update_time` = NOW();
+
+-- 8. 字典数据:打卡状态(全局,与风控共用;若已存在则 noop)
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+SELECT 1, '000000', '已打卡', '已打卡', 'inspection_status', '', 'success', 'N', 103, 1, NOW(), '已打卡'
+WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'inspection_status' AND `dict_value` = '已打卡');
+
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+SELECT 2, '000000', '未打卡', '未打卡', 'inspection_status', '', 'danger', 'N', 103, 1, NOW(), '未打卡'
+WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'inspection_status' AND `dict_value` = '未打卡');
+
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+SELECT 3, '000000', '已逾期', '已逾期', 'inspection_status', '', 'warning', 'N', 103, 1, NOW(), '已逾期'
+WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'inspection_status' AND `dict_value` = '已逾期');
+
+-- 9. 字典数据:打卡结果(全局)
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+SELECT 1, '000000', '正常', '正常', 'inspection_result', '', 'success', 'N', 103, 1, NOW(), '打卡正常'
+WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'inspection_result' AND `dict_value` = '正常');
+
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+SELECT 2, '000000', '异常', '异常', 'inspection_result', '', 'danger', 'N', 103, 1, NOW(), '打卡异常'
+WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'inspection_result' AND `dict_value` = '异常');
+
+-- 10. 字典数据:打卡方式
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+SELECT 1, '000000', '常规', '常规', 'check_method', '', 'primary', 'N', 103, 1, NOW(), '常规打卡'
+WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'check_method' AND `dict_value` = '常规');
+
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+SELECT 2, '000000', '扫码', '扫码', 'check_method', '', 'success', 'N', 103, 1, NOW(), '扫码打卡'
+WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'check_method' AND `dict_value` = '扫码');
+
+-- 11. 字典数据:巡检周期
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+SELECT 1, '000000', '每日', '每日', 'inspection_cycle', '', 'primary', 'N', 103, 1, NOW(), '每日执行'
+WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'inspection_cycle' AND `dict_value` = '每日');
+
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+SELECT 2, '000000', '每周', '每周', 'inspection_cycle', '', 'success', 'N', 103, 1, NOW(), '每周执行'
+WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'inspection_cycle' AND `dict_value` = '每周');
+
+INSERT INTO `sys_dict_data` (`dict_sort`, `tenant_id`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `create_dept`, `create_by`, `create_time`, `remark`)
+SELECT 3, '000000', '每月', '每月', 'inspection_cycle', '', 'info', 'N', 103, 1, NOW(), '每月执行'
+WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'inspection_cycle' AND `dict_value` = '每月');
+
+-- 12. 验证
+SELECT 'biz_type' AS col, COUNT(*) AS rows_count FROM `risk_inspection_task` WHERE `biz_type` = 'quality'
+UNION ALL
+SELECT 'biz_type_risk', COUNT(*) FROM `risk_inspection_task` WHERE `biz_type` = 'risk'
+UNION ALL
+SELECT 'quality_inspection_type_dict', COUNT(*) FROM `sys_dict_type` WHERE `dict_type` = 'quality_inspection_type'
+UNION ALL
+SELECT 'quality_project_category_dict', COUNT(*) FROM `sys_dict_type` WHERE `dict_type` = 'quality_project_category';

+ 43 - 0
sql/risk_quality_menu.sql

@@ -0,0 +1,43 @@
+-- ============================================
+-- 品控平台菜单权限 SQL 脚本
+-- 创建时间: 2026-06-21
+-- 依赖: 风控平台菜单 risk_menu.sql 已执行,@risk_parent_id 为风控平台目录 ID
+-- ============================================
+
+-- 设置变量(指向已有风控平台目录,品控作为同级子菜单挂在风控平台目录下)
+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)
+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
+('品控巡检查询', @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(), ''),
+('品控巡检删除', @quality_inspection_task_id, 4, '#', '', 1, 0, 'F', '0', '0', 'risk:qualityInspectionTask:remove', '#', 103, 1, NOW(), ''),
+('品控巡检导出', @quality_inspection_task_id, 5, '#', '', 1, 0, 'F', '0', '0', 'risk:qualityInspectionTask:export', '#', 103, 1, NOW(), ''),
+('品控巡检记录审核', @quality_inspection_task_id, 6, '#', '', 1, 0, 'F', '0', '0', 'risk:qualityInspectionRecord:review', '#', 103, 1, NOW(), '');
+
+-- 2. 给超级管理员角色授权所有品控平台权限
+INSERT INTO sys_role_menu (role_id, menu_id)
+SELECT 1, menu_id FROM sys_menu
+WHERE perms LIKE 'risk:qualityInspectionTask:%'
+   OR perms = 'risk:qualityInspectionRecord:review'
+ON DUPLICATE KEY UPDATE role_id = role_id;
+
+-- 3. 给租户管理员(角色ID 2,如果存在)也授权
+INSERT INTO sys_role_menu (role_id, menu_id)
+SELECT 2, menu_id FROM sys_menu
+WHERE perms LIKE 'risk:qualityInspectionTask:%'
+   OR perms = 'risk:qualityInspectionRecord:review'
+ON DUPLICATE KEY UPDATE role_id = role_id;
+
+-- 完成提示
+SELECT '品控巡检菜单权限初始化完成!' AS message,
+       COUNT(*) AS total_menus
+FROM sys_menu
+WHERE perms LIKE 'risk:qualityInspectionTask:%'
+   OR perms = 'risk:qualityInspectionRecord:review';

Some files were not shown because too many files changed in this diff