Quellcode durchsuchen

feat: 添加巡检模块(controllers/services/entity/search)及相关SQL脚本和前端页面

admin vor 3 Wochen
Ursprung
Commit
1ebff17d58
49 geänderte Dateien mit 6576 neuen und 5 gelöschten Zeilen
  1. 240 0
      client-app/src/main/java/com/sapit/client/app/manage/controller/inspect/AssessResultManageController.java
  2. 146 0
      client-app/src/main/java/com/sapit/client/app/manage/controller/inspect/InspectIndicatorManageController.java
  3. 277 0
      client-app/src/main/java/com/sapit/client/app/manage/controller/inspect/InspectPlanManageController.java
  4. 269 0
      client-app/src/main/java/com/sapit/client/app/manage/controller/inspect/InspectTaskManageController.java
  5. 191 0
      client-app/src/main/java/com/sapit/client/app/manage/controller/inspect/RectificationManageController.java
  6. 16 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuAssessResultDao.java
  7. 16 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectIndicatorDao.java
  8. 16 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectPlanDao.java
  9. 16 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectPlanIndicatorDao.java
  10. 16 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectPlanStoreDao.java
  11. 16 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectTaskDao.java
  12. 16 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectTaskItemDao.java
  13. 16 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuRectificationDao.java
  14. 336 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/service/AssessResultService.java
  15. 189 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectAutoScoreService.java
  16. 186 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectCommonUtils.java
  17. 121 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectIndicatorService.java
  18. 232 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectPlanService.java
  19. 256 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectScheduleService.java
  20. 203 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectTaskService.java
  21. 197 0
      client-app/src/main/java/com/sapit/client/app/manage/inspect/service/RectificationService.java
  22. 40 0
      client-app/src/main/java/com/sapit/client/app/scheduled/ctl/InspectScheduledHandle.java
  23. 132 0
      client-app/src/main/java/com/sapit/client/app/store/controller/inspect/RectificationApiController.java
  24. 4 2
      client-app/src/main/resources/application-dev.yml
  25. 38 1
      common/src/main/java/com/sapit/common/config/WebAppConfiguration.java
  26. 255 0
      common/src/main/java/com/sapit/common/entity/inspect/BuAssessResult.java
  27. 178 0
      common/src/main/java/com/sapit/common/entity/inspect/BuInspectIndicator.java
  28. 195 0
      common/src/main/java/com/sapit/common/entity/inspect/BuInspectPlan.java
  29. 154 0
      common/src/main/java/com/sapit/common/entity/inspect/BuInspectPlanIndicator.java
  30. 116 0
      common/src/main/java/com/sapit/common/entity/inspect/BuInspectPlanStore.java
  31. 225 0
      common/src/main/java/com/sapit/common/entity/inspect/BuInspectTask.java
  32. 274 0
      common/src/main/java/com/sapit/common/entity/inspect/BuInspectTaskItem.java
  33. 324 0
      common/src/main/java/com/sapit/common/entity/inspect/BuRectification.java
  34. 8 0
      common/src/main/java/com/sapit/common/framwork/BaseDao.java
  35. 4 2
      common/src/main/java/com/sapit/common/framwork/BaseSearchBean.java
  36. 134 0
      common/src/main/java/com/sapit/common/search/inspect/BuAssessResultSearchBean.java
  37. 54 0
      common/src/main/java/com/sapit/common/search/inspect/BuInspectIndicatorSearchBean.java
  38. 43 0
      common/src/main/java/com/sapit/common/search/inspect/BuInspectPlanIndicatorSearchBean.java
  39. 112 0
      common/src/main/java/com/sapit/common/search/inspect/BuInspectPlanSearchBean.java
  40. 76 0
      common/src/main/java/com/sapit/common/search/inspect/BuInspectPlanStoreSearchBean.java
  41. 76 0
      common/src/main/java/com/sapit/common/search/inspect/BuInspectTaskItemSearchBean.java
  42. 101 0
      common/src/main/java/com/sapit/common/search/inspect/BuInspectTaskSearchBean.java
  43. 112 0
      common/src/main/java/com/sapit/common/search/inspect/BuRectificationSearchBean.java
  44. 144 0
      docs/inspect-permission.sql
  45. 227 0
      docs/inspect-schema-optimized.sql
  46. 235 0
      docs/inspect-schema.sql
  47. 23 0
      docs/inspect-seed-indicators.sql
  48. 300 0
      docs/inspect-test-data.sql
  49. 21 0
      frontend-inspect-portal.vue

+ 240 - 0
client-app/src/main/java/com/sapit/client/app/manage/controller/inspect/AssessResultManageController.java

@@ -0,0 +1,240 @@
+package com.sapit.client.app.manage.controller.inspect;
+
+import com.sapit.client.app.manage.inspect.service.AssessResultService;
+import com.sapit.common.constant.ReturnCode;
+import com.sapit.common.framwork.AuthToken;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.framwork.ResultBean;
+import com.sapit.common.entity.inspect.BuAssessResult;
+import com.sapit.common.search.inspect.BuAssessResultSearchBean;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping(value = "inspectManage", produces = "application/json")
+public class AssessResultManageController {
+    @Autowired
+    private AssessResultService assessResultService;
+
+    @PostMapping("/assess/page")
+    @AuthToken(type = "admin")
+    public ResultBean assessPage(int pageSize, int currentPage, BuAssessResultSearchBean searchBean) {
+        PageControlData page = buildPage(pageSize, currentPage);
+        List<?> list = assessResultService.getAssessPage(page, searchBean);
+        return getPageResult(page, list);
+    }
+
+    @PostMapping("/assess/rankPage")
+    @AuthToken(type = "admin")
+    public ResultBean assessRankPage(int pageSize, int currentPage, BuAssessResultSearchBean searchBean) {
+        PageControlData page = buildPage(pageSize, currentPage);
+        List<?> list = assessResultService.getAssessPage(page, searchBean);
+        return getPageResult(page, list);
+    }
+
+    @PostMapping("/assess/detail")
+    @AuthToken(type = "admin")
+    public ResultBean assessDetail(Long id, Long storeId, Integer periodType, String periodCode) {
+        BuAssessResult result;
+        if (id != null) {
+            result = assessResultService.getAssessDetail(id);
+        } else if (storeId != null && periodType != null && periodCode != null) {
+            result = assessResultService.getAssessDetailByPeriod(storeId, periodType, periodCode);
+        } else {
+            return paramError();
+        }
+        return objectResult(result);
+    }
+
+    @PostMapping("/assess/trend")
+    @AuthToken(type = "admin")
+    public ResultBean assessTrend(@RequestParam("storeIds") List<Long> storeIds, @RequestParam(value = "periodType", required = false) Integer periodType) {
+        if (storeIds == null || storeIds.isEmpty()) {
+            return listResult(new ArrayList<>());
+        }
+        List<Map<String, Object>> trend = assessResultService.getAssessTrend(storeIds.toArray(new Long[0]), periodType);
+        return listResult(trend);
+    }
+
+    @PostMapping("/assess/update")
+    @AuthToken(type = "admin")
+    public ResultBean updateAssessResult(BuAssessResult assessResult) {
+        ResultBean bean = new ResultBean();
+        if (assessResult == null || assessResult.getAssessResultId() == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(assessResultService.updateAssessResult(assessResult) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("更新完成");
+        return bean;
+    }
+
+    @PostMapping("/assess/delete")
+    @AuthToken(type = "admin")
+    public ResultBean deleteAssessResult(Long id) {
+        ResultBean bean = new ResultBean();
+        if (id == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(assessResultService.deleteAssessResult(id) > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("删除完成");
+        return bean;
+    }
+
+    @PostMapping("/assess/trigger")
+    @AuthToken(type = "admin")
+    public ResultBean triggerAssess(Integer periodType, String periodCode,
+                                     @DateTimeFormat(pattern = "yyyy-MM-dd") Date periodStartDate,
+                                     @DateTimeFormat(pattern = "yyyy-MM-dd") Date periodEndDate) {
+        if (periodType == null || periodCode == null || periodStartDate == null || periodEndDate == null) {
+            return paramError();
+        }
+        int count = assessResultService.triggerAssess(periodType, periodCode, periodStartDate, periodEndDate);
+        return statusResult(count > 0 ? 1 : 0, "考核初始化完成,共初始化 " + count + " 条记录");
+    }
+
+    @PostMapping("/assess/summarize")
+    @AuthToken(type = "admin")
+    public ResultBean summarizeAssessResult(Long storeId, Integer periodType, String periodCode,
+                                             @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+                                             @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
+        if (storeId == null || periodType == null || periodCode == null
+                || startDate == null || endDate == null) {
+            return paramError();
+        }
+        return statusResult(assessResultService.summarizeAssessResult(storeId, periodType, periodCode,
+                startDate, endDate), "考核汇总完成");
+    }
+
+    @PostMapping("/assess/rank")
+    @AuthToken(type = "admin")
+    public ResultBean updateAssessRank(Integer periodType, String periodCode) {
+        if (periodType == null || periodCode == null) {
+            return paramError();
+        }
+        return statusResult(assessResultService.updateAssessRank(periodType, periodCode), "排名更新完成");
+    }
+
+    @PostMapping("/assess/export")
+    @AuthToken(type = "admin")
+    public ResultBean exportAssessReport(Integer periodType, String periodCode) {
+        if (periodType == null || periodCode == null) {
+            return paramError();
+        }
+        Map<String, Object> report = assessResultService.exportAssessReport(periodType, periodCode);
+        if (report == null || report.isEmpty()) {
+            return objectResult(null);
+        }
+        return objectResult(report);
+    }
+
+    @PostMapping("/assess/levelDistribution")
+    @AuthToken(type = "admin")
+    public ResultBean getLevelDistribution(Integer periodType, String periodCode) {
+        if (periodType == null || periodCode == null) {
+            return paramError();
+        }
+        return listResult(assessResultService.getLevelDistribution(periodType, periodCode));
+    }
+
+    @PostMapping("/assess/exportExcel")
+    @AuthToken(type = "admin")
+    public void exportAssessExcel(HttpServletResponse response, Integer periodType, String periodCode) throws Exception {
+        if (periodType == null || periodCode == null) {
+            return;
+        }
+        Map<String, Object> report = assessResultService.exportAssessReport(periodType, periodCode);
+        List<?> list = (List<?>) report.get("assessResults");
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        XSSFSheet sheet = workbook.createSheet("考核排名");
+        XSSFRow header = sheet.createRow(0);
+        String[] titles = {"排名", "门店ID", "门店名称", "综合得分", "考核日期"};
+        for (int i = 0; i < titles.length; i++) {
+            header.createCell(i).setCellValue(titles[i]);
+        }
+        if (list != null) {
+            int rowIdx = 1;
+            for (Object obj : list) {
+                Map<?, ?> item = (Map<?, ?>) obj;
+                XSSFRow row = sheet.createRow(rowIdx++);
+                row.createCell(0).setCellValue(item.get("ranking") != null ? item.get("ranking").toString() : "");
+                row.createCell(1).setCellValue(item.get("storeId") != null ? item.get("storeId").toString() : "");
+                row.createCell(2).setCellValue(item.get("storeName") != null ? item.get("storeName").toString() : "");
+                row.createCell(3).setCellValue(item.get("totalScore") != null ? item.get("totalScore").toString() : "");
+                row.createCell(4).setCellValue(item.get("assessDate") != null ? item.get("assessDate").toString() : "");
+            }
+        }
+        String fileName = "考核排名_" + periodCode + ".xlsx";
+        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
+        OutputStream os = response.getOutputStream();
+        workbook.write(os);
+        os.flush();
+        os.close();
+    }
+
+    private ResultBean statusResult(int result, String message) {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(result > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(message);
+        return bean;
+    }
+
+    private ResultBean paramError() {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+        bean.getRtnBean().setRtnMsg("参数错误");
+        return bean;
+    }
+
+    private ResultBean objectResult(Object object) {
+        ResultBean bean = new ResultBean();
+        if (object == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.FAILED);
+            bean.getRtnBean().setRtnMsg("数据不存在");
+            return bean;
+        }
+        bean.setObj(object);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+    private ResultBean listResult(List<?> list) {
+        ResultBean bean = new ResultBean();
+        bean.setList(list == null ? new ArrayList<>() : list);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+    private PageControlData buildPage(int pageSize, int currentPage) {
+        PageControlData page = new PageControlData();
+        page.setPageSize(pageSize);
+        page.setCurrentPage(currentPage);
+        return page;
+    }
+
+    private ResultBean getPageResult(PageControlData page, List<?> list) {
+        ResultBean bean = new ResultBean();
+        bean.setPage(page);
+        bean.setList(list);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+}

+ 146 - 0
client-app/src/main/java/com/sapit/client/app/manage/controller/inspect/InspectIndicatorManageController.java

@@ -0,0 +1,146 @@
+package com.sapit.client.app.manage.controller.inspect;
+
+import com.sapit.client.app.manage.inspect.service.InspectIndicatorService;
+import com.sapit.common.constant.ReturnCode;
+import com.sapit.common.framwork.AuthToken;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.framwork.ResultBean;
+import com.sapit.common.search.inspect.BuInspectIndicatorSearchBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RestController
+@RequestMapping(value = "inspectManage", produces = "application/json")
+public class InspectIndicatorManageController {
+    @Autowired
+    private InspectIndicatorService inspectIndicatorService;
+
+    /**
+     * 分页查询指标列表
+     * @param pageSize 每页数量
+     * @param currentPage 当前页
+     * @param searchBean 搜索条件
+     * @return 指标列表分页结果
+     */
+    @PostMapping("/indicator/page")
+    @AuthToken(type = "admin")
+    public ResultBean indicatorPage(int pageSize, int currentPage, BuInspectIndicatorSearchBean searchBean) {
+        PageControlData page = new PageControlData();
+        page.setPageSize(pageSize);
+        page.setCurrentPage(currentPage);
+        List<?> list = inspectIndicatorService.getIndicatorList(page, searchBean);
+        return getPageResult(page, list);
+    }
+
+    @PostMapping("/indicator/detail")
+    @AuthToken(type = "admin")
+    public ResultBean indicatorDetail(Long id) {
+        return objectResult(id == null ? null : inspectIndicatorService.getIndicatorById(id));
+    }
+
+    @PostMapping("/indicator/save")
+    @AuthToken(type = "admin")
+    public ResultBean saveIndicator(com.sapit.common.entity.inspect.BuInspectIndicator indicator) {
+        ResultBean bean = new ResultBean();
+        if (indicator == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectIndicatorService.save(indicator) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(indicator.getIndicatorId() == null ? "新增完成" : "保存完成");
+        return bean;
+    }
+
+    @PostMapping("/indicator/update")
+    @AuthToken(type = "admin")
+    public ResultBean updateIndicator(com.sapit.common.entity.inspect.BuInspectIndicator indicator) {
+        ResultBean bean = new ResultBean();
+        if (indicator == null || indicator.getIndicatorId() == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        boolean referenced = inspectIndicatorService.isReferencedByPlan(indicator.getIndicatorId());
+        bean.getRtnBean().setRtnCode(inspectIndicatorService.update(indicator) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        if (referenced) {
+            bean.getRtnBean().setRtnMsg("更新完成。提示:该指标已被巡检计划引用,编码、类型、数据来源、评分规则、排序、状态不可修改");
+        } else {
+            bean.getRtnBean().setRtnMsg("更新完成");
+        }
+        return bean;
+    }
+
+    @PostMapping("/indicator/delete")
+    @AuthToken(type = "admin")
+    public ResultBean deleteIndicator(Long id) {
+        ResultBean bean = new ResultBean();
+        if (id == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectIndicatorService.delete(id) > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("删除完成");
+        return bean;
+    }
+
+    @PostMapping("/indicator/changeStatus")
+    @AuthToken(type = "admin")
+    public ResultBean changeIndicatorStatus(Long id, Integer status) {
+        if (id == null || status == null) {
+            return paramError();
+        }
+        return statusResult(inspectIndicatorService.changeStatus(id, status), "指标状态更新完成");
+    }
+
+    /**
+     *
+     */
+    @PostMapping("/indicator/checkWeight")
+    @AuthToken(type = "admin")
+    public ResultBean checkIndicatorWeight() {
+        return objectResult(inspectIndicatorService.checkWeight());
+    }
+
+    private ResultBean statusResult(int result, String message) {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(result > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(message);
+        return bean;
+    }
+
+    private ResultBean paramError() {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+        bean.getRtnBean().setRtnMsg("参数错误");
+        return bean;
+    }
+
+    private ResultBean objectResult(Object object) {
+        ResultBean bean = new ResultBean();
+        if (object == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.FAILED);
+            bean.getRtnBean().setRtnMsg("数据不存在");
+            return bean;
+        }
+        bean.setObj(object);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+
+
+    private ResultBean getPageResult(PageControlData page, List<?> list) {
+        ResultBean bean = new ResultBean();
+        bean.setPage(page);
+        bean.setList(list);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+}

+ 277 - 0
client-app/src/main/java/com/sapit/client/app/manage/controller/inspect/InspectPlanManageController.java

@@ -0,0 +1,277 @@
+package com.sapit.client.app.manage.controller.inspect;
+
+import com.sapit.client.app.manage.inspect.service.InspectPlanService;
+import com.sapit.common.constant.ReturnCode;
+import com.sapit.common.framwork.AuthToken;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.framwork.ResultBean;
+import com.sapit.common.entity.inspect.BuInspectPlanIndicator;
+import com.sapit.common.entity.inspect.BuInspectPlanStore;
+import com.sapit.common.search.inspect.BuInspectPlanSearchBean;
+import com.sapit.common.search.inspect.BuInspectPlanIndicatorSearchBean;
+import com.sapit.common.search.inspect.BuInspectPlanStoreSearchBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RestController
+@RequestMapping(value = "inspectManage", produces = "application/json")
+public class InspectPlanManageController {
+    @Autowired
+    private InspectPlanService inspectPlanService;
+
+    @PostMapping("/plan/page")
+    @AuthToken(type = "admin")
+    public ResultBean planPage(int pageSize, int currentPage, BuInspectPlanSearchBean searchBean) {
+        PageControlData page = buildPage(pageSize, currentPage);
+        List<?> list = inspectPlanService.getPlanPage(page, searchBean);
+        return getPageResult(page, list);
+    }
+
+    @PostMapping("/plan/detail")
+    @AuthToken(type = "admin")
+    public ResultBean planDetail(Long id) {
+        return objectResult(id == null ? null : inspectPlanService.getPlanDetail(id));
+    }
+
+    @PostMapping("/plan/indicators")
+    @AuthToken(type = "admin")
+    public ResultBean planIndicators(Long planId) {
+        if (planId == null) {
+            return paramError();
+        }
+        return listResult(inspectPlanService.getPlanIndicators(planId));
+    }
+
+    @PostMapping("/plan/stores")
+    @AuthToken(type = "admin")
+    public ResultBean planStores(Long planId) {
+        if (planId == null) {
+            return paramError();
+        }
+        return listResult(inspectPlanService.getPlanStores(planId));
+    }
+
+    @PostMapping("/plan/save")
+    @AuthToken(type = "admin")
+    public ResultBean savePlan(com.sapit.common.entity.inspect.BuInspectPlan plan) {
+        ResultBean bean = new ResultBean();
+        if (plan == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectPlanService.save(plan) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.setObj(plan.getPlanId());
+        bean.getRtnBean().setRtnMsg(plan.getPlanId() == null ? "新增完成" : "保存完成");
+        return bean;
+    }
+
+    @PostMapping("/plan/update")
+    @AuthToken(type = "admin")
+    public ResultBean updatePlan(com.sapit.common.entity.inspect.BuInspectPlan plan) {
+        ResultBean bean = new ResultBean();
+        if (plan == null || plan.getPlanId() == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectPlanService.update(plan) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.setObj(plan.getPlanId());
+        bean.getRtnBean().setRtnMsg("更新完成");
+        return bean;
+    }
+
+    @PostMapping("/plan/delete")
+    @AuthToken(type = "admin")
+    public ResultBean deletePlan(Long id) {
+        ResultBean bean = new ResultBean();
+        if (id == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectPlanService.delete(id) > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("删除完成");
+        return bean;
+    }
+
+    @PostMapping("/plan/publish")
+    @AuthToken(type = "admin")
+    public ResultBean publishPlan(Long id) {
+        if (id == null) {
+            return paramError();
+        }
+        return statusResult(inspectPlanService.publish(id), "发布完成");
+    }
+
+    @PostMapping("/plan/pause")
+    @AuthToken(type = "admin")
+    public ResultBean pausePlan(Long id) {
+        if (id == null) {
+            return paramError();
+        }
+        return statusResult(inspectPlanService.changeStatus(id, 2), "暂停完成");
+    }
+
+    @PostMapping("/plan/finish")
+    @AuthToken(type = "admin")
+    public ResultBean finishPlan(Long id) {
+        if (id == null) {
+            return paramError();
+        }
+        return statusResult(inspectPlanService.finish(id), "结束完成");
+    }
+
+    @PostMapping("/planIndicator/page")
+    @AuthToken(type = "admin")
+    public ResultBean planIndicatorPage(int pageSize, int currentPage, BuInspectPlanIndicatorSearchBean searchBean) {
+        PageControlData page = buildPage(pageSize, currentPage);
+        List<?> list = inspectPlanService.getPlanIndicatorPage(page, searchBean);
+        return getPageResult(page, list);
+    }
+
+    @PostMapping("/planIndicator/save")
+    @AuthToken(type = "admin")
+    public ResultBean savePlanIndicator(BuInspectPlanIndicator planIndicator) {
+        ResultBean bean = new ResultBean();
+        if (planIndicator == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectPlanService.savePlanIndicator(planIndicator) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(planIndicator.getPlanIndicatorId() == null ? "新增完成" : "保存完成");
+        return bean;
+    }
+
+    @PostMapping("/planIndicator/update")
+    @AuthToken(type = "admin")
+    public ResultBean updatePlanIndicator(BuInspectPlanIndicator planIndicator) {
+        ResultBean bean = new ResultBean();
+        if (planIndicator == null || planIndicator.getPlanIndicatorId() == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectPlanService.updatePlanIndicator(planIndicator) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("更新完成");
+        return bean;
+    }
+
+    @PostMapping("/planIndicator/delete")
+    @AuthToken(type = "admin")
+    public ResultBean deletePlanIndicator(Long id) {
+        ResultBean bean = new ResultBean();
+        if (id == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectPlanService.deletePlanIndicator(id) > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("删除完成");
+        return bean;
+    }
+
+    @PostMapping("/planStore/page")
+    @AuthToken(type = "admin")
+    public ResultBean planStorePage(int pageSize, int currentPage, BuInspectPlanStoreSearchBean searchBean) {
+        PageControlData page = buildPage(pageSize, currentPage);
+        List<?> list = inspectPlanService.getPlanStorePage(page, searchBean);
+        return getPageResult(page, list);
+    }
+
+    @PostMapping("/planStore/save")
+    @AuthToken(type = "admin")
+    public ResultBean savePlanStore(BuInspectPlanStore planStore) {
+        ResultBean bean = new ResultBean();
+        if (planStore == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectPlanService.savePlanStore(planStore) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(planStore.getPlanStoreId() == null ? "新增完成" : "保存完成");
+        return bean;
+    }
+
+    @PostMapping("/planStore/update")
+    @AuthToken(type = "admin")
+    public ResultBean updatePlanStore(BuInspectPlanStore planStore) {
+        ResultBean bean = new ResultBean();
+        if (planStore == null || planStore.getPlanStoreId() == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectPlanService.updatePlanStore(planStore) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("更新完成");
+        return bean;
+    }
+
+    @PostMapping("/planStore/delete")
+    @AuthToken(type = "admin")
+    public ResultBean deletePlanStore(Long id) {
+        ResultBean bean = new ResultBean();
+        if (id == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectPlanService.deletePlanStore(id) > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("删除完成");
+        return bean;
+    }
+
+    private ResultBean statusResult(int result, String message) {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(result > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(message);
+        return bean;
+    }
+
+    private ResultBean paramError() {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+        bean.getRtnBean().setRtnMsg("参数错误");
+        return bean;
+    }
+
+    private ResultBean objectResult(Object object) {
+        ResultBean bean = new ResultBean();
+        if (object == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.FAILED);
+            bean.getRtnBean().setRtnMsg("数据不存在");
+            return bean;
+        }
+        bean.setObj(object);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+    private ResultBean listResult(List<?> list) {
+        ResultBean bean = new ResultBean();
+        bean.setList(list == null ? new ArrayList<>() : list);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+    private PageControlData buildPage(int pageSize, int currentPage) {
+        PageControlData page = new PageControlData();
+        page.setPageSize(pageSize);
+        page.setCurrentPage(currentPage);
+        return page;
+    }
+
+    private ResultBean getPageResult(PageControlData page, List<?> list) {
+        ResultBean bean = new ResultBean();
+        bean.setPage(page);
+        bean.setList(list);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+}

+ 269 - 0
client-app/src/main/java/com/sapit/client/app/manage/controller/inspect/InspectTaskManageController.java

@@ -0,0 +1,269 @@
+package com.sapit.client.app.manage.controller.inspect;
+
+import com.sapit.client.app.manage.inspect.service.InspectTaskService;
+import com.sapit.client.app.manage.inspect.service.InspectScheduleService;
+import com.sapit.common.constant.ReturnCode;
+import com.sapit.common.framwork.AuthToken;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.framwork.ResultBean;
+import com.sapit.common.entity.inspect.BuInspectTask;
+import com.sapit.common.entity.inspect.BuInspectTaskItem;
+import com.sapit.common.search.inspect.BuInspectTaskSearchBean;
+import com.sapit.common.search.inspect.BuInspectTaskItemSearchBean;
+import com.sapit.common.util.DateUtil;
+import com.sapit.common.util.FileFtpUploadUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@RestController
+@RequestMapping(value = "inspectManage", produces = "application/json")
+public class InspectTaskManageController {
+    @Autowired
+    private InspectTaskService inspectTaskService;
+    @Autowired
+    private InspectScheduleService inspectScheduleService;
+
+    @PostMapping("/task/generate")
+    @AuthToken(type = "admin")
+    public ResultBean generateTask(Long planId, @DateTimeFormat(pattern = "yyyy-MM-dd") Date taskDate) {
+        ResultBean bean = new ResultBean();
+        if (planId == null || taskDate == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        int count = inspectScheduleService.generateTaskByPlan(planId, taskDate);
+        bean.getRtnBean().setRtnCode(count > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("生成完成");
+        bean.setObj(count);
+        return bean;
+    }
+
+    @PostMapping("/task/page")
+    @AuthToken(type = "admin")
+    public ResultBean taskPage(int pageSize, int currentPage, BuInspectTaskSearchBean searchBean) {
+        PageControlData page = buildPage(pageSize, currentPage);
+        List<?> list = inspectTaskService.getTaskPage(page, searchBean);
+        return getPageResult(page, list);
+    }
+
+    @PostMapping("/task/detail")
+    @AuthToken(type = "admin")
+    public ResultBean taskDetail(Long id) {
+        return objectResult(id == null ? null : inspectTaskService.getTaskDetail(id));
+    }
+
+    @PostMapping("/task/items")
+    @AuthToken(type = "admin")
+    public ResultBean taskItems(Long taskId) {
+        if (taskId == null) {
+            return paramError();
+        }
+        return listResult(inspectTaskService.getTaskItems(taskId));
+    }
+
+    @PostMapping("/task/myList")
+    @AuthToken(type = "admin")
+    public ResultBean myTaskList(Long inspectorId) {
+        if (inspectorId == null) {
+            return paramError();
+        }
+        return listResult(inspectTaskService.getMyTaskList(inspectorId));
+    }
+
+    @PostMapping("/task/start")
+    @AuthToken(type = "admin")
+    public ResultBean startTask(Long id, Long inspectorId) {
+        if (id == null || inspectorId == null) {
+            return paramError();
+        }
+        return statusResult(inspectTaskService.startTask(id, inspectorId), "开始执行完成");
+    }
+
+    @PostMapping("/task/statistics")
+    @AuthToken(type = "admin")
+    public ResultBean taskStatistics(Long planId) {
+        return listResult(inspectTaskService.getTaskStatistics(planId));
+    }
+
+    @PostMapping("/task/update")
+    @AuthToken(type = "admin")
+    public ResultBean updateTask(BuInspectTask task) {
+        ResultBean bean = new ResultBean();
+        if (task == null || task.getTaskId() == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectTaskService.updateTask(task) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("更新完成");
+        return bean;
+    }
+
+    @PostMapping("/task/cancel")
+    @AuthToken(type = "admin")
+    public ResultBean cancelTask(Long id) {
+        ResultBean bean = new ResultBean();
+        if (id == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectTaskService.cancelTask(id) > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("取消完成");
+        return bean;
+    }
+
+    @PostMapping("/task/submit")
+    @AuthToken(type = "admin")
+    public ResultBean submitTask(Long id, Long inspectorId) {
+        ResultBean bean = new ResultBean();
+        if (id == null || inspectorId == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectTaskService.submitTask(id, inspectorId) > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("提交完成");
+        return bean;
+    }
+
+    @PostMapping("/taskItem/page")
+    @AuthToken(type = "admin")
+    public ResultBean taskItemPage(int pageSize, int currentPage, BuInspectTaskItemSearchBean searchBean) {
+        PageControlData page = buildPage(pageSize, currentPage);
+        List<?> list = inspectTaskService.getTaskItemPage(page, searchBean);
+        return getPageResult(page, list);
+    }
+
+    @PostMapping("/taskItem/save")
+    @AuthToken(type = "admin")
+    public ResultBean saveTaskItem(BuInspectTaskItem taskItem) {
+        ResultBean bean = new ResultBean();
+        if (taskItem == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectTaskService.saveTaskItem(taskItem) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(taskItem.getTaskItemId() == null ? "新增完成" : "保存完成");
+        return bean;
+    }
+
+    @PostMapping("/taskItem/update")
+    @AuthToken(type = "admin")
+    public ResultBean updateTaskItem(BuInspectTaskItem taskItem) {
+        ResultBean bean = new ResultBean();
+        if (taskItem == null || taskItem.getTaskItemId() == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectTaskService.updateTaskItem(taskItem) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("更新完成");
+        return bean;
+    }
+
+    @PostMapping("/taskItem/delete")
+    @AuthToken(type = "admin")
+    public ResultBean deleteTaskItem(Long id) {
+        ResultBean bean = new ResultBean();
+        if (id == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(inspectTaskService.deleteTaskItem(id) > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("删除完成");
+        return bean;
+    }
+
+    @PostMapping("/uploadPicture")
+    @AuthToken(type = "admin")
+    public ResultBean uploadPicture(@RequestParam(value = "file", required = false) MultipartFile file,
+                                    String dir) {
+        ResultBean bean = new ResultBean();
+        if (file == null || file.getOriginalFilename() == null || file.getOriginalFilename().isEmpty()) {
+            return paramError();
+        }
+        if (file.getSize() > 204800) {
+            bean.getRtnBean().setRtnCode(ReturnCode.SYSTEMERR);
+            bean.getRtnBean().setRtnMsg("图片大小超过200K");
+            return bean;
+        }
+        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.') + 1);
+        String fileName = DateUtil.getStrToday("yyyyMMddHHmmssSSS") + "." + suffix;
+        String path = dir == null || dir.trim().isEmpty() ? "/inspect" : dir;
+        try {
+            if (!FileFtpUploadUtil.uploadFile(path, fileName, file.getInputStream())) {
+                bean.getRtnBean().setRtnCode(ReturnCode.SYSTEMERR);
+                bean.getRtnBean().setRtnMsg("文件上传失败");
+                return bean;
+            }
+        } catch (Exception e) {
+            bean.getRtnBean().setRtnCode(ReturnCode.SYSTEMERR);
+            bean.getRtnBean().setRtnMsg("文件上传失败");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        bean.setObj(FileFtpUploadUtil.rootURL + path + "/" + fileName);
+        return bean;
+    }
+
+    private ResultBean statusResult(int result, String message) {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(result > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(message);
+        return bean;
+    }
+
+    private ResultBean paramError() {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+        bean.getRtnBean().setRtnMsg("参数错误");
+        return bean;
+    }
+
+    private ResultBean objectResult(Object object) {
+        ResultBean bean = new ResultBean();
+        if (object == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.FAILED);
+            bean.getRtnBean().setRtnMsg("数据不存在");
+            return bean;
+        }
+        bean.setObj(object);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+    private ResultBean listResult(List<?> list) {
+        ResultBean bean = new ResultBean();
+        bean.setList(list == null ? new ArrayList<>() : list);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+    private PageControlData buildPage(int pageSize, int currentPage) {
+        PageControlData page = new PageControlData();
+        page.setPageSize(pageSize);
+        page.setCurrentPage(currentPage);
+        return page;
+    }
+
+    private ResultBean getPageResult(PageControlData page, List<?> list) {
+        ResultBean bean = new ResultBean();
+        bean.setPage(page);
+        bean.setList(list);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+}

+ 191 - 0
client-app/src/main/java/com/sapit/client/app/manage/controller/inspect/RectificationManageController.java

@@ -0,0 +1,191 @@
+package com.sapit.client.app.manage.controller.inspect;
+
+import com.sapit.client.app.manage.inspect.service.RectificationService;
+import com.sapit.common.constant.ReturnCode;
+import com.sapit.common.framwork.AuthToken;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.framwork.ResultBean;
+import com.sapit.common.entity.inspect.BuRectification;
+import com.sapit.common.search.inspect.BuRectificationSearchBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@RestController
+@RequestMapping(value = "inspectManage", produces = "application/json")
+public class RectificationManageController {
+    @Autowired
+    private RectificationService rectificationService;
+
+    @PostMapping("/rectification/page")
+    @AuthToken(type = "admin")
+    public ResultBean rectificationPage(int pageSize, int currentPage, BuRectificationSearchBean searchBean) {
+        PageControlData page = buildPage(pageSize, currentPage);
+        List<?> list = rectificationService.getRectPage(page, searchBean);
+        return getPageResult(page, list);
+    }
+
+    @PostMapping("/rectification/detail")
+    @AuthToken(type = "admin")
+    public ResultBean rectificationDetail(Long id) {
+        return objectResult(id == null ? null : rectificationService.getRectDetail(id));
+    }
+
+    @PostMapping("/rectification/save")
+    @AuthToken(type = "admin")
+    public ResultBean saveRectification(BuRectification rectification) {
+        ResultBean bean = new ResultBean();
+        if (rectification == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(rectificationService.saveRectification(rectification) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(rectification.getRectId() == null ? "新增完成" : "保存完成");
+        return bean;
+    }
+
+    @PostMapping("/rectification/update")
+    @AuthToken(type = "admin")
+    public ResultBean updateRectification(BuRectification rectification) {
+        ResultBean bean = new ResultBean();
+        if (rectification == null || rectification.getRectId() == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(rectificationService.updateRectification(rectification) ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("更新完成");
+        return bean;
+    }
+
+    @PostMapping("/rectification/delete")
+    @AuthToken(type = "admin")
+    public ResultBean deleteRectification(Long id) {
+        ResultBean bean = new ResultBean();
+        if (id == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(rectificationService.deleteRectification(id) > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("删除完成");
+        return bean;
+    }
+
+    @PostMapping("/rectification/batchUpdateDeadline")
+    @AuthToken(type = "admin")
+    public ResultBean batchUpdateDeadline(@RequestParam("rectIds") List<Long> rectIds,
+                                          @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date deadlineDate) {
+        if (rectIds == null || rectIds.isEmpty() || deadlineDate == null) {
+            return paramError();
+        }
+        return statusResult(rectificationService.batchUpdateDeadline(rectIds, deadlineDate), "截止日期更新完成");
+    }
+
+    @PostMapping("/rectification/statistics")
+    @AuthToken(type = "admin")
+    public ResultBean rectificationStatistics(Long storeId) {
+        return listResult(rectificationService.getRectStatistics(storeId));
+    }
+
+    @PostMapping("/rectification/urge")
+    @AuthToken(type = "admin")
+    public ResultBean urgeRectification(Long id) {
+        if (id == null) {
+            return paramError();
+        }
+        return statusResult(rectificationService.urge(id), "催办完成");
+    }
+
+    @PostMapping("/rectification/feedback")
+    @AuthToken(type = "admin")
+    public ResultBean submitRectification(Long id, String feedbackContent, String feedbackPictures) {
+        ResultBean bean = new ResultBean();
+        if (id == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(
+                rectificationService.submitRectification(id, feedbackContent, feedbackPictures) > 0
+                        ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("提交完成");
+        return bean;
+    }
+
+    @PostMapping("/rectification/recheck")
+    @AuthToken(type = "admin")
+    public ResultBean recheckRectification(Long id, BigDecimal recheckScore, Integer recheckResult,
+                                           String recheckPictures, String recheckRmk,
+                                           Long recheckUserId, String recheckUserName) {
+        ResultBean bean = new ResultBean();
+        if (id == null || recheckResult == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(
+                rectificationService.recheck(id, recheckScore, recheckResult,
+                        recheckPictures, recheckRmk, recheckUserId, recheckUserName) > 0
+                        ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg("复检完成");
+        return bean;
+    }
+
+    private ResultBean statusResult(int result, String message) {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(result > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(message);
+        return bean;
+    }
+
+    private ResultBean paramError() {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+        bean.getRtnBean().setRtnMsg("参数错误");
+        return bean;
+    }
+
+    private ResultBean objectResult(Object object) {
+        ResultBean bean = new ResultBean();
+        if (object == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.FAILED);
+            bean.getRtnBean().setRtnMsg("数据不存在");
+            return bean;
+        }
+        bean.setObj(object);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+    private ResultBean listResult(List<?> list) {
+        ResultBean bean = new ResultBean();
+        bean.setList(list == null ? new ArrayList<>() : list);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+    private PageControlData buildPage(int pageSize, int currentPage) {
+        PageControlData page = new PageControlData();
+        page.setPageSize(pageSize);
+        page.setCurrentPage(currentPage);
+        return page;
+    }
+
+    private ResultBean getPageResult(PageControlData page, List<?> list) {
+        ResultBean bean = new ResultBean();
+        bean.setPage(page);
+        bean.setList(list);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+}

+ 16 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuAssessResultDao.java

@@ -0,0 +1,16 @@
+package com.sapit.client.app.manage.inspect.persistence;
+
+import com.sapit.common.entity.inspect.BuAssessResult;
+import com.sapit.common.framwork.BaseDao;
+import org.springframework.stereotype.Repository;
+
+/**
+ * I01-A08
+ * 门店考核结果Dao。
+ */
+@Repository
+public class BuAssessResultDao extends BaseDao<BuAssessResult> {
+    public BuAssessResultDao() {
+        super.setEntity("BuAssessResult");
+    }
+}

+ 16 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectIndicatorDao.java

@@ -0,0 +1,16 @@
+package com.sapit.client.app.manage.inspect.persistence;
+
+import com.sapit.common.entity.inspect.BuInspectIndicator;
+import com.sapit.common.framwork.BaseDao;
+import org.springframework.stereotype.Repository;
+
+/**
+ * I01-A01
+ * 巡检指标Dao。
+ */
+@Repository
+public class BuInspectIndicatorDao extends BaseDao<BuInspectIndicator> {
+    public BuInspectIndicatorDao() {
+        super.setEntity("BuInspectIndicator");
+    }
+}

+ 16 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectPlanDao.java

@@ -0,0 +1,16 @@
+package com.sapit.client.app.manage.inspect.persistence;
+
+import com.sapit.common.entity.inspect.BuInspectPlan;
+import com.sapit.common.framwork.BaseDao;
+import org.springframework.stereotype.Repository;
+
+/**
+ * I01-A02
+ * 巡检计划Dao。
+ */
+@Repository
+public class BuInspectPlanDao extends BaseDao<BuInspectPlan> {
+    public BuInspectPlanDao() {
+        super.setEntity("BuInspectPlan");
+    }
+}

+ 16 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectPlanIndicatorDao.java

@@ -0,0 +1,16 @@
+package com.sapit.client.app.manage.inspect.persistence;
+
+import com.sapit.common.entity.inspect.BuInspectPlanIndicator;
+import com.sapit.common.framwork.BaseDao;
+import org.springframework.stereotype.Repository;
+
+/**
+ * I01-A03
+ * 巡检计划指标关联Dao。
+ */
+@Repository
+public class BuInspectPlanIndicatorDao extends BaseDao<BuInspectPlanIndicator> {
+    public BuInspectPlanIndicatorDao() {
+        super.setEntity("BuInspectPlanIndicator");
+    }
+}

+ 16 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectPlanStoreDao.java

@@ -0,0 +1,16 @@
+package com.sapit.client.app.manage.inspect.persistence;
+
+import com.sapit.common.entity.inspect.BuInspectPlanStore;
+import com.sapit.common.framwork.BaseDao;
+import org.springframework.stereotype.Repository;
+
+/**
+ * I01-A04
+ * 巡检计划门店关联Dao。
+ */
+@Repository
+public class BuInspectPlanStoreDao extends BaseDao<BuInspectPlanStore> {
+    public BuInspectPlanStoreDao() {
+        super.setEntity("BuInspectPlanStore");
+    }
+}

+ 16 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectTaskDao.java

@@ -0,0 +1,16 @@
+package com.sapit.client.app.manage.inspect.persistence;
+
+import com.sapit.common.entity.inspect.BuInspectTask;
+import com.sapit.common.framwork.BaseDao;
+import org.springframework.stereotype.Repository;
+
+/**
+ * I01-A05
+ * 巡检任务Dao。
+ */
+@Repository
+public class BuInspectTaskDao extends BaseDao<BuInspectTask> {
+    public BuInspectTaskDao() {
+        super.setEntity("BuInspectTask");
+    }
+}

+ 16 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuInspectTaskItemDao.java

@@ -0,0 +1,16 @@
+package com.sapit.client.app.manage.inspect.persistence;
+
+import com.sapit.common.entity.inspect.BuInspectTaskItem;
+import com.sapit.common.framwork.BaseDao;
+import org.springframework.stereotype.Repository;
+
+/**
+ * I01-A06
+ * 巡检任务明细Dao。
+ */
+@Repository
+public class BuInspectTaskItemDao extends BaseDao<BuInspectTaskItem> {
+    public BuInspectTaskItemDao() {
+        super.setEntity("BuInspectTaskItem");
+    }
+}

+ 16 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/BuRectificationDao.java

@@ -0,0 +1,16 @@
+package com.sapit.client.app.manage.inspect.persistence;
+
+import com.sapit.common.entity.inspect.BuRectification;
+import com.sapit.common.framwork.BaseDao;
+import org.springframework.stereotype.Repository;
+
+/**
+ * I01-A07
+ * 整改记录Dao。
+ */
+@Repository
+public class BuRectificationDao extends BaseDao<BuRectification> {
+    public BuRectificationDao() {
+        super.setEntity("BuRectification");
+    }
+}

+ 336 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/service/AssessResultService.java

@@ -0,0 +1,336 @@
+package com.sapit.client.app.manage.inspect.service;
+
+import com.sapit.client.app.manage.inspect.persistence.BuAssessResultDao;
+import com.sapit.client.app.manage.inspect.persistence.BuInspectPlanDao;
+import com.sapit.common.entity.inspect.BuAssessResult;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.search.inspect.BuAssessResultSearchBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+@Service
+@Transactional
+public class AssessResultService {
+
+    @Autowired
+    private BuAssessResultDao assessResultDao;
+    @Autowired
+    private BuInspectPlanDao inspectPlanDao;
+
+    public List<BuAssessResult> getAssessPage(PageControlData page, BuAssessResultSearchBean searchBean) {
+        return assessResultDao.getList(page, searchBean, " order by summaryDate desc, assessResultId desc");
+    }
+
+    public BuAssessResult getAssessDetail(Long id) {
+        return (BuAssessResult) assessResultDao.findById(BuAssessResult.class, id);
+    }
+
+    public BuAssessResult getAssessDetailByPeriod(Long storeId, Integer periodType, String periodCode) {
+        if (storeId == null || periodType == null || periodCode == null) {
+            return null;
+        }
+        String escapedCode = InspectCommonUtils.escapeSql(periodCode);
+        List<?> rows = assessResultDao.searchBySql(
+                "select * from bu_assess_result where STORE_ID = " + storeId
+                        + " and PERIOD_TYPE = " + periodType
+                        + " and PERIOD_CODE = '" + escapedCode + "'");
+        if (rows == null || rows.isEmpty()) {
+            return null;
+        }
+        return (BuAssessResult) rows.get(0);
+    }
+
+    public List<Map<String, Object>> getAssessDetailTasks(Long storeId, Integer periodType, String periodCode) {
+        List<Map<String, Object>> tasks = new ArrayList<>();
+        if (storeId == null || periodType == null || periodCode == null) {
+            return tasks;
+        }
+        String escapedCode = InspectCommonUtils.escapeSql(periodCode);
+        String sql = "select t.PLAN_NAME, t.TASK_DATE, t.TOTAL_SCORE, t.STATUS "
+                + "from bu_inspect_task t "
+                + "join bu_assess_result a on a.STORE_ID = t.STORE_ID "
+                + "and a.PERIOD_TYPE = " + periodType
+                + " and a.PERIOD_CODE = '" + escapedCode + "' "
+                + "where t.STORE_ID = " + storeId
+                + " and t.TASK_DATE >= a.PERIOD_START_DATE "
+                + " and t.TASK_DATE <= a.PERIOD_END_DATE "
+                + "order by t.TASK_DATE desc";
+        List<?> rows = inspectPlanDao.searchBySql(sql);
+        if (rows == null) {
+            return tasks;
+        }
+        for (Object row : rows) {
+            Object[] arr = (Object[]) row;
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("planName", arr[0]);
+            item.put("taskDate", arr[1]);
+            item.put("totalScore", arr[2]);
+            item.put("status", arr[3]);
+            tasks.add(item);
+        }
+        return tasks;
+    }
+
+    public List<Map<String, Object>> getAssessTrend(Long[] storeIds, Integer periodType) {
+        List<Map<String, Object>> trend = new ArrayList<>();
+        if (storeIds == null || storeIds.length == 0 || periodType == null) {
+            return trend;
+        }
+        StringBuilder ids = new StringBuilder();
+        for (int i = 0; i < storeIds.length; i++) {
+            if (i > 0) {
+                ids.append(",");
+            }
+            ids.append(storeIds[i]);
+        }
+        String sql = "select a.STORE_ID, a.STORE_NAME, a.PERIOD_CODE, a.TOTAL_SCORE, a.RANK_NO "
+                + "from bu_assess_result a where a.STORE_ID in (" + ids + ")"
+                + " and a.PERIOD_TYPE = " + periodType + " and a.STATUS = 2 "
+                + "order by a.STORE_ID, a.PERIOD_CODE desc";
+        List<?> rows = assessResultDao.searchBySql(sql);
+        if (rows == null) {
+            return trend;
+        }
+        for (Object row : rows) {
+            Object[] arr = (Object[]) row;
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("storeId", arr[0]);
+            item.put("storeName", arr[1]);
+            item.put("periodCode", arr[2]);
+            item.put("totalScore", arr[3]);
+            item.put("rankNo", arr[4]);
+            trend.add(item);
+        }
+        return trend;
+    }
+
+    public BuAssessResult getAssessResultById(Long id) {
+        return getAssessDetail(id);
+    }
+
+    public boolean saveAssessResult(BuAssessResult assessResult) {
+        return assessResultDao.save(assessResult);
+    }
+
+    public boolean updateAssessResult(BuAssessResult assessResult) {
+        if (assessResult == null || assessResult.getAssessResultId() == null) {
+            return false;
+        }
+        BuAssessResult old = (BuAssessResult) assessResultDao.findById(
+                BuAssessResult.class, assessResult.getAssessResultId());
+        if (old == null) {
+            return false;
+        }
+        assessResult.setCreateDate(old.getCreateDate());
+        assessResult.setUpdateDate(old.getUpdateDate());
+        return assessResultDao.update(assessResult);
+    }
+
+    public int deleteAssessResult(Long id) {
+        return assessResultDao.executeSql("delete from bu_assess_result where ASSESS_RESULT_ID = :id",
+                InspectCommonUtils.params("id", id));
+    }
+
+    public List<Map<String, Object>> getLevelDistribution(Integer periodType, String periodCode) {
+        List<Map<String, Object>> distribution = new ArrayList<>();
+        if (periodType == null || periodCode == null) {
+            return distribution;
+        }
+        String escapedCode = InspectCommonUtils.escapeSql(periodCode);
+        String sql = "select a.ASSESS_LEVEL, count(*) as cnt "
+                + "from bu_assess_result a where a.PERIOD_TYPE = " + periodType
+                + " and a.PERIOD_CODE = '" + escapedCode + "' and a.STATUS = 2 "
+                + "group by a.ASSESS_LEVEL order by a.ASSESS_LEVEL";
+        List<?> rows = assessResultDao.searchBySql(sql);
+        if (rows == null) {
+            return distribution;
+        }
+        String[] levelNames = new String[]{"", "A-优秀", "B-良好", "C-合格", "D-不合格"};
+        for (Object row : rows) {
+            Object[] arr = (Object[]) row;
+            int level = arr[0] != null ? Integer.parseInt(arr[0].toString()) : 0;
+            long count = arr[1] != null ? Long.parseLong(arr[1].toString()) : 0L;
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("level", level);
+            item.put("levelName", level >= 1 && level < levelNames.length ? levelNames[level] : "未知");
+            item.put("count", count);
+            distribution.add(item);
+        }
+        return distribution;
+    }
+
+    public Map<String, Object> exportAssessReport(Integer periodType, String periodCode) {
+        Map<String, Object> report = new LinkedHashMap<>();
+        String assessSql = "select a.STORE_ID, a.STORE_NAME, a.TOTAL_SCORE, a.RANK_NO, a.SUMMARY_DATE "
+                + "from bu_assess_result a where a.PERIOD_TYPE = " + periodType
+                + " and a.PERIOD_CODE = '" + InspectCommonUtils.escapeSql(periodCode) + "'"
+                + " order by a.RANK_NO asc";
+        List<?> assessRows = assessResultDao.searchBySql(assessSql);
+        List<Map<String, Object>> assessList = new ArrayList<>();
+        for (Object row : assessRows) {
+            Object[] arr = (Object[]) row;
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("storeId", arr[0]);
+            item.put("storeName", arr[1]);
+            item.put("totalScore", arr[2]);
+            item.put("ranking", arr[3]);
+            item.put("assessDate", arr[4]);
+            assessList.add(item);
+        }
+        report.put("assessResults", assessList);
+        return report;
+    }
+
+    public int triggerAssess(Integer periodType, String periodCode, Date startDate, Date endDate) {
+        if (periodType == null || periodCode == null || startDate == null || endDate == null) {
+            return 0;
+        }
+        String start = InspectCommonUtils.escapeSql(new java.text.SimpleDateFormat("yyyy-MM-dd").format(startDate));
+        String end = InspectCommonUtils.escapeSql(new java.text.SimpleDateFormat("yyyy-MM-dd").format(endDate));
+        String storeSql = "select distinct STORE_ID, STORE_NAME from bu_inspect_task "
+                + "where TASK_DATE >= '" + start + "' and TASK_DATE <= '" + end + "'";
+        List<?> storeRows = inspectPlanDao.searchBySql(storeSql);
+        int count = 0;
+        String escapedCode = InspectCommonUtils.escapeSql(periodCode);
+        for (Object row : storeRows) {
+            Object[] arr = (Object[]) row;
+            Long storeId = Long.valueOf(arr[0].toString());
+            String storeName = arr[1] != null ? arr[1].toString() : "";
+            List<?> existing = assessResultDao.searchBySql(
+                    "select 1 from bu_assess_result where PERIOD_TYPE = " + periodType
+                            + " and PERIOD_CODE = '" + escapedCode + "' and STORE_ID = " + storeId);
+            if (existing != null && !existing.isEmpty()) {
+                continue;
+            }
+            BuAssessResult result = new BuAssessResult();
+            result.setStoreId(storeId);
+            result.setStoreName(storeName);
+            result.setPeriodType(periodType);
+            result.setPeriodCode(periodCode);
+            result.setPeriodStartDate(new java.sql.Timestamp(startDate.getTime()));
+            result.setPeriodEndDate(new java.sql.Timestamp(endDate.getTime()));
+            result.setStatus(1);
+            result.setSummaryDate(new java.sql.Timestamp(System.currentTimeMillis()));
+            result.setCreateDate(new java.sql.Timestamp(System.currentTimeMillis()));
+            assessResultDao.save(result);
+            count++;
+        }
+        return count;
+    }
+
+    public int summarizeAssessResult(Long storeId, Integer periodType, String periodCode,
+                                      Date startDate, Date endDate) {
+        if (storeId == null || periodType == null || periodCode == null
+                || startDate == null || endDate == null) {
+            return 0;
+        }
+        String start = InspectCommonUtils.escapeSql(new java.text.SimpleDateFormat("yyyy-MM-dd").format(startDate));
+        String end = InspectCommonUtils.escapeSql(new java.text.SimpleDateFormat("yyyy-MM-dd").format(endDate));
+        String escapedCode = InspectCommonUtils.escapeSql(periodCode);
+        String sql = "update bu_assess_result a set "
+                + "a.INSPECT_SCORE = (select ifnull(sum(i.WEIGHTED_SCORE), 0) from bu_inspect_task_item i "
+                + "join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3 and i.INDICATOR_TYPE = 2), "
+                + "a.AUTO_SCORE = (select ifnull(sum(i.WEIGHTED_SCORE), 0) from bu_inspect_task_item i "
+                + "join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3 and i.INDICATOR_TYPE = 1), "
+                + "a.TOTAL_SCORE = (select ifnull(sum(i.WEIGHTED_SCORE), 0) * 0.6 from bu_inspect_task_item i "
+                + "join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3 and i.INDICATOR_TYPE = 2) "
+                + "+ (select ifnull(sum(i.WEIGHTED_SCORE), 0) * 0.4 from bu_inspect_task_item i "
+                + "join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3 and i.INDICATOR_TYPE = 1), "
+                + "a.TASK_COUNT = (select count(*) from bu_inspect_task t "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + "), "
+                + "a.FINISH_TASK_COUNT = (select count(*) from bu_inspect_task t "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3), "
+                + "a.ASSESS_LEVEL = case when ((select ifnull(sum(i.WEIGHTED_SCORE), 0) * 0.6 from bu_inspect_task_item i "
+                + "join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3 and i.INDICATOR_TYPE = 2) "
+                + "+ (select ifnull(sum(i.WEIGHTED_SCORE), 0) * 0.4 from bu_inspect_task_item i "
+                + "join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3 and i.INDICATOR_TYPE = 1)) >= 90 then 1 "
+                + "when ((select ifnull(sum(i.WEIGHTED_SCORE), 0) * 0.6 from bu_inspect_task_item i "
+                + "join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3 and i.INDICATOR_TYPE = 2) "
+                + "+ (select ifnull(sum(i.WEIGHTED_SCORE), 0) * 0.4 from bu_inspect_task_item i "
+                + "join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3 and i.INDICATOR_TYPE = 1)) >= 80 then 2 "
+                + "when ((select ifnull(sum(i.WEIGHTED_SCORE), 0) * 0.6 from bu_inspect_task_item i "
+                + "join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3 and i.INDICATOR_TYPE = 2) "
+                + "+ (select ifnull(sum(i.WEIGHTED_SCORE), 0) * 0.4 from bu_inspect_task_item i "
+                + "join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where t.TASK_DATE >= '" + start + "' and t.TASK_DATE <= '" + end + "' "
+                + "and t.STORE_ID = " + storeId + " and t.STATUS = 3 and i.INDICATOR_TYPE = 1)) >= 60 then 3 "
+                + "else 4 end, "
+                + "a.SUMMARY_DATE = NOW(), a.STATUS = 2 "
+                + "where a.PERIOD_TYPE = " + periodType + " and a.PERIOD_CODE = '" + escapedCode + "' "
+                + "and a.STORE_ID = " + storeId + " and a.STATUS = 1";
+        return assessResultDao.executeSql(sql);
+    }
+
+    public int updateAssessRank(Integer periodType, String periodCode) {
+        if (periodType == null || periodCode == null) {
+            return 0;
+        }
+        String escapedCode = InspectCommonUtils.escapeSql(periodCode);
+        String sql = "select a.ASSESS_RESULT_ID, a.TOTAL_SCORE "
+                + "from bu_assess_result a where a.PERIOD_TYPE = " + periodType
+                + " and a.PERIOD_CODE = '" + escapedCode + "' and a.STATUS = 2";
+        List<?> rows = assessResultDao.searchBySql(sql);
+        if (rows == null || rows.isEmpty()) {
+            return 0;
+        }
+        List<Long> ids = new ArrayList<>();
+        List<BigDecimal> scores = new ArrayList<>();
+        for (Object row : rows) {
+            Object[] arr = (Object[]) row;
+            ids.add(Long.valueOf(arr[0].toString()));
+            scores.add(arr[1] != null ? new BigDecimal(arr[1].toString()) : BigDecimal.ZERO);
+        }
+        Integer[] indices = new Integer[ids.size()];
+        for (int i = 0; i < indices.length; i++) {
+            indices[i] = i;
+        }
+        Arrays.sort(indices, (a, b) -> {
+            int cmp = scores.get(b).compareTo(scores.get(a));
+            return cmp;
+        });
+        int totalUpdated = 0;
+        for (int rank = 0; rank < indices.length; ) {
+            int sameStart = rank;
+            BigDecimal currentScore = scores.get(indices[rank]);
+            int displayRank = rank + 1;
+            while (rank < indices.length
+                    && scores.get(indices[rank]).compareTo(currentScore) == 0) {
+                rank++;
+            }
+            for (int i = sameStart; i < rank; i++) {
+                int updated = assessResultDao.executeSql(
+                        "update bu_assess_result set RANK_NO = :ranking "
+                                + "where ASSESS_RESULT_ID = :id",
+                        InspectCommonUtils.with(
+                                InspectCommonUtils.params("id", ids.get(indices[i])),
+                                "ranking", displayRank));
+                totalUpdated += updated;
+            }
+        }
+        return totalUpdated;
+    }
+}

+ 189 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectAutoScoreService.java

@@ -0,0 +1,189 @@
+package com.sapit.client.app.manage.inspect.service;
+
+import com.sapit.client.app.manage.inspect.persistence.BuInspectIndicatorDao;
+import com.sapit.common.entity.inspect.BuInspectIndicator;
+import com.sapit.common.entity.inspect.BuInspectTask;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Service
+@Transactional
+public class InspectAutoScoreService {
+
+    @Autowired
+    private BuInspectIndicatorDao inspectIndicatorDao;
+
+    /**
+     * 根据指标自动采集评分规则,从订单/评价等数据源自动计算得分。
+     * 对空值场景均做安全保护,确保不会抛出 NullPointerException。
+     *
+     * @param task      巡检任务,可能为 null
+     * @param indicator 巡检指标,可能为 null
+     * @return AutoScoreResult,所有字段保证非 null
+     */
+    public InspectCommonUtils.AutoScoreResult calculateAutoScore(BuInspectTask task, BuInspectIndicator indicator) {
+        InspectCommonUtils.AutoScoreResult result = new InspectCommonUtils.AutoScoreResult();
+        result.rawValue = BigDecimal.ZERO;
+        result.rawValueText = "";
+        result.score = BigDecimal.ZERO;
+        result.weightedScore = BigDecimal.ZERO;
+        result.passFlag = 0;
+
+        if (task == null || indicator == null) {
+            return result;
+        }
+        if (indicator.getIndicatorType() == null || indicator.getIndicatorType() != 2) {
+            return result;
+        }
+        if (indicator.getDataSource() == null || indicator.getDataSource().trim().isEmpty()) {
+            return result;
+        }
+        if (indicator.getScoreRule() == null || indicator.getScoreRule().trim().isEmpty()) {
+            return result;
+        }
+        if (task.getStoreId() == null) {
+            return result;
+        }
+
+        String dataSource = indicator.getDataSource().trim();
+        BigDecimal rawValue = null;
+
+        try {
+            if ("order_count".equals(dataSource)) {
+                rawValue = fetchOrderCount(task.getStoreId(), task.getTaskDate());
+            } else if ("order_amount".equals(dataSource)) {
+                rawValue = fetchOrderAmount(task.getStoreId(), task.getTaskDate());
+            } else if ("evaluation_score".equals(dataSource)) {
+                rawValue = fetchEvaluationScore(task.getStoreId(), task.getTaskDate());
+            } else if ("evaluation_count".equals(dataSource)) {
+                rawValue = fetchEvaluationCount(task.getStoreId(), task.getTaskDate());
+            } else if ("complaint_count".equals(dataSource)) {
+                rawValue = fetchComplaintCount(task.getStoreId(), task.getTaskDate());
+            } else if ("response_rate".equals(dataSource)) {
+                rawValue = fetchResponseRate(task.getStoreId(), task.getTaskDate());
+            } else if ("on_time_rate".equals(dataSource)) {
+                rawValue = fetchOnTimeRate(task.getStoreId(), task.getTaskDate());
+            } else {
+                return result;
+            }
+        } catch (Exception e) {
+            return result;
+        }
+
+        if (rawValue == null) {
+            rawValue = BigDecimal.ZERO;
+        }
+        result.rawValue = rawValue;
+        result.rawValueText = rawValue.toPlainString();
+
+        try {
+            InspectCommonUtils.scoreByRule(result, indicator.getScoreRule());
+        } catch (Exception e) {
+            result.score = BigDecimal.ZERO;
+            result.weightedScore = BigDecimal.ZERO;
+            result.passFlag = 0;
+        }
+
+        if (result.score == null) {
+            result.score = BigDecimal.ZERO;
+        }
+        if (result.weightedScore == null) {
+            result.weightedScore = BigDecimal.ZERO;
+        }
+        if (result.passFlag == null) {
+            result.passFlag = 0;
+        }
+
+        return result;
+    }
+
+    private BigDecimal fetchOrderCount(Long storeId, java.util.Date taskDate) {
+        try {
+            List<?> rows = inspectIndicatorDao.searchBySql(
+                    "select ifnull(count(1), 0) from order_info where store_id = " + storeId
+                            + " and date(order_date) = date('" + formatDate(taskDate) + "')");
+            return InspectCommonUtils.firstDecimal(rows);
+        } catch (Exception e) {
+            return BigDecimal.ZERO;
+        }
+    }
+
+    private BigDecimal fetchOrderAmount(Long storeId, java.util.Date taskDate) {
+        try {
+            List<?> rows = inspectIndicatorDao.searchBySql(
+                    "select ifnull(sum(order_amount), 0) from order_info where store_id = " + storeId
+                            + " and date(order_date) = date('" + formatDate(taskDate) + "')");
+            return InspectCommonUtils.firstDecimal(rows);
+        } catch (Exception e) {
+            return BigDecimal.ZERO;
+        }
+    }
+
+    private BigDecimal fetchEvaluationScore(Long storeId, java.util.Date taskDate) {
+        try {
+            List<?> rows = inspectIndicatorDao.searchBySql(
+                    "select ifnull(avg(score), 0) from evaluation_info where store_id = " + storeId
+                            + " and date(evaluation_date) = date('" + formatDate(taskDate) + "')");
+            return InspectCommonUtils.firstDecimal(rows);
+        } catch (Exception e) {
+            return BigDecimal.ZERO;
+        }
+    }
+
+    private BigDecimal fetchEvaluationCount(Long storeId, java.util.Date taskDate) {
+        try {
+            List<?> rows = inspectIndicatorDao.searchBySql(
+                    "select ifnull(count(1), 0) from evaluation_info where store_id = " + storeId
+                            + " and date(evaluation_date) = date('" + formatDate(taskDate) + "')");
+            return InspectCommonUtils.firstDecimal(rows);
+        } catch (Exception e) {
+            return BigDecimal.ZERO;
+        }
+    }
+
+    private BigDecimal fetchComplaintCount(Long storeId, java.util.Date taskDate) {
+        try {
+            List<?> rows = inspectIndicatorDao.searchBySql(
+                    "select ifnull(count(1), 0) from complaint_info where store_id = " + storeId
+                            + " and date(complaint_date) = date('" + formatDate(taskDate) + "')");
+            return InspectCommonUtils.firstDecimal(rows);
+        } catch (Exception e) {
+            return BigDecimal.ZERO;
+        }
+    }
+
+    private BigDecimal fetchResponseRate(Long storeId, java.util.Date taskDate) {
+        try {
+            String td = formatDate(taskDate);
+            List<?> totalRows = inspectIndicatorDao.searchBySql(
+                    "select ifnull(count(1), 0) from evaluation_info where store_id = " + storeId
+                            + " and date(evaluation_date) = date('" + td + "')");
+            List<?> respondedRows = inspectIndicatorDao.searchBySql(
+                    "select ifnull(count(1), 0) from evaluation_info where store_id = " + storeId
+                            + " and date(evaluation_date) = date('" + td + "') and reply_content is not null and reply_content <> ''");
+            BigDecimal total = InspectCommonUtils.firstDecimal(totalRows);
+            BigDecimal responded = InspectCommonUtils.firstDecimal(respondedRows);
+            if (total.compareTo(BigDecimal.ZERO) == 0) {
+                return BigDecimal.ZERO;
+            }
+            return responded.multiply(new BigDecimal("100")).divide(total, 2, BigDecimal.ROUND_HALF_UP);
+        } catch (Exception e) {
+            return BigDecimal.ZERO;
+        }
+    }
+
+    private BigDecimal fetchOnTimeRate(Long storeId, java.util.Date taskDate) {
+        return fetchResponseRate(storeId, taskDate);
+    }
+
+    private String formatDate(java.util.Date date) {
+        if (date == null) {
+            return new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date());
+        }
+        return new java.text.SimpleDateFormat("yyyy-MM-dd").format(date);
+    }
+}

+ 186 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectCommonUtils.java

@@ -0,0 +1,186 @@
+package com.sapit.client.app.manage.inspect.service;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.sapit.common.entity.inspect.BuInspectPlan;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 巡检考核通用工具方法。
+ *
+ * 所有 Service 均可直接静态调用,无需继承基类。
+ */
+public final class InspectCommonUtils {
+
+    private InspectCommonUtils() {
+    }
+
+    public static String escapeSql(String value) {
+        return value == null ? "" : value.replace("'", "''");
+    }
+
+    public static Map<String, Object> params(String name, Object value) {
+        Map<String, Object> params = new HashMap<>();
+        params.put(name, value);
+        return params;
+    }
+
+    public static Map<String, Object> with(Map<String, Object> params, String name, Object value) {
+        params.put(name, value);
+        return params;
+    }
+
+    public static BigDecimal firstDecimal(List<?> list) {
+        if (list == null || list.isEmpty() || list.get(0) == null) {
+            return BigDecimal.ZERO;
+        }
+        Object first = list.get(0);
+        Object value = first instanceof Object[] ? (((Object[]) first).length == 0 ? null : ((Object[]) first)[0]) : first;
+        if (value == null) {
+            return BigDecimal.ZERO;
+        }
+        if (value instanceof BigDecimal) {
+            return (BigDecimal) value;
+        }
+        return new BigDecimal(String.valueOf(value));
+    }
+
+    public static BigDecimal weighted(BigDecimal score, BigDecimal weight) {
+        if (score == null) {
+            return BigDecimal.ZERO;
+        }
+        if (weight == null) {
+            return score;
+        }
+        return score.multiply(weight).divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_UP);
+    }
+
+    public static boolean validPlan(BuInspectPlan plan) {
+        return plan != null && plan.getPlanName() != null && !plan.getPlanName().trim().isEmpty()
+                && plan.getStartDate() != null && plan.getEndDate() != null
+                && plan.getEndDate().after(plan.getStartDate())
+                && plan.getCycleType() != null;
+    }
+
+    public static boolean validPictureUrls(String pictureUrls) {
+        if (pictureUrls == null || pictureUrls.trim().isEmpty()) {
+            return true;
+        }
+        String normalized = pictureUrls.replace(";", ",");
+        String[] urls = normalized.split(",");
+        int count = 0;
+        for (String url : urls) {
+            if (url != null && !url.trim().isEmpty()) {
+                count++;
+            }
+        }
+        return count <= 5;
+    }
+
+    public static BigDecimal scoreByRule(BigDecimal raw, String scoreRule, boolean higherBetter,
+                                         int first, int second, int third) {
+        if (raw == null) {
+            return BigDecimal.ZERO;
+        }
+        if (scoreRule != null && !scoreRule.trim().isEmpty()) {
+            try {
+                JSONObject rule = JSONObject.parseObject(scoreRule);
+                if ("range".equals(rule.getString("type"))) {
+                    JSONArray ranges = rule.getJSONArray("ranges");
+                    if (ranges != null) {
+                        for (int i = 0; i < ranges.size(); i++) {
+                            JSONObject range = ranges.getJSONObject(i);
+                            BigDecimal min = range.getBigDecimal("min");
+                            BigDecimal max = range.getBigDecimal("max");
+                            boolean minOk = min == null || raw.compareTo(min) >= 0;
+                            boolean maxOk = max == null || raw.compareTo(max) <= 0;
+                            if (minOk && maxOk) {
+                                return range.getBigDecimal("score");
+                            }
+                        }
+                    }
+                } else if ("percentage".equals(rule.getString("type"))) {
+                    JSONArray thresholds = rule.getJSONArray("thresholds");
+                    if (thresholds != null) {
+                        for (int i = 0; i < thresholds.size(); i++) {
+                            JSONObject threshold = thresholds.getJSONObject(i);
+                            BigDecimal min = threshold.getBigDecimal("min");
+                            if (min == null || raw.compareTo(min) >= 0) {
+                                return threshold.getBigDecimal("score");
+                            }
+                        }
+                    }
+                } else if ("percentage_reverse".equals(rule.getString("type"))) {
+                    JSONArray thresholds = rule.getJSONArray("thresholds");
+                    if (thresholds != null) {
+                        for (int i = 0; i < thresholds.size(); i++) {
+                            JSONObject threshold = thresholds.getJSONObject(i);
+                            BigDecimal max = threshold.getBigDecimal("max");
+                            if (max != null && raw.compareTo(max) <= 0) {
+                                return threshold.getBigDecimal("score");
+                            }
+                        }
+                    }
+                }
+            } catch (Exception ignored) {
+            }
+        }
+        if (higherBetter && first > second) {
+            return mapByRate(raw, true);
+        }
+        return mapByRange(raw, !higherBetter, first, second, third);
+    }
+
+    private static BigDecimal mapByRate(BigDecimal raw, boolean higherBetter) {
+        if (raw == null) {
+            return BigDecimal.ZERO;
+        }
+        if (higherBetter) {
+            if (raw.compareTo(new BigDecimal("95")) >= 0) return new BigDecimal("100");
+            if (raw.compareTo(new BigDecimal("90")) >= 0) return new BigDecimal("80");
+            if (raw.compareTo(new BigDecimal("80")) >= 0) return new BigDecimal("60");
+            return new BigDecimal("40");
+        }
+        return mapByRange(raw, false, 1, 3, 5);
+    }
+
+    private static BigDecimal mapByRange(BigDecimal raw, boolean lowerBetter, int first, int second, int third) {
+        if (raw == null) {
+            return BigDecimal.ZERO;
+        }
+        if (lowerBetter) {
+            if (raw.compareTo(new BigDecimal(first)) <= 0) return new BigDecimal("100");
+            if (raw.compareTo(new BigDecimal(second)) <= 0) return new BigDecimal("80");
+            if (raw.compareTo(new BigDecimal(third)) <= 0) return new BigDecimal("60");
+            return new BigDecimal("40");
+        }
+        if (raw.compareTo(new BigDecimal(first)) <= 0) return new BigDecimal("100");
+        if (raw.compareTo(new BigDecimal(second)) <= 0) return new BigDecimal("80");
+        if (raw.compareTo(new BigDecimal(third)) <= 0) return new BigDecimal("60");
+        return new BigDecimal("40");
+    }
+
+    /**
+     * 根据评分规则填充 AutoScoreResult 的 score、weightedScore、passFlag。
+     */
+    public static void scoreByRule(AutoScoreResult result, String scoreRule) {
+        if (result == null || result.rawValue == null || scoreRule == null || scoreRule.trim().isEmpty()) {
+            return;
+        }
+        result.score = scoreByRule(result.rawValue, scoreRule, true, 95, 90, 80);
+        result.weightedScore = result.score;
+        result.passFlag = result.score != null && result.score.compareTo(BigDecimal.ZERO) > 0 ? 1 : 0;
+    }
+
+    public static class AutoScoreResult {
+        public BigDecimal rawValue;
+        public String rawValueText;
+        public BigDecimal score;
+        public BigDecimal weightedScore;
+        public Integer passFlag;
+    }
+}

+ 121 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectIndicatorService.java

@@ -0,0 +1,121 @@
+package com.sapit.client.app.manage.inspect.service;
+
+import com.sapit.client.app.manage.inspect.persistence.BuInspectIndicatorDao;
+import com.sapit.client.app.manage.inspect.persistence.BuInspectPlanIndicatorDao;
+import com.sapit.common.entity.inspect.BuInspectIndicator;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.search.inspect.BuInspectIndicatorSearchBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Transactional
+public class InspectIndicatorService {
+
+    @Autowired
+    private BuInspectIndicatorDao inspectIndicatorDao;
+    @Autowired
+    private BuInspectPlanIndicatorDao inspectPlanIndicatorDao;
+
+    public List<BuInspectIndicator> getIndicatorList(PageControlData page, BuInspectIndicatorSearchBean searchBean) {
+        return inspectIndicatorDao.getList(page, searchBean, " order by sort asc, indicatorId desc");
+    }
+
+    public List<BuInspectIndicator> getIndicatorPage(PageControlData page, BuInspectIndicatorSearchBean searchBean) {
+        return getIndicatorList(page, searchBean);
+    }
+
+    public BuInspectIndicator getIndicatorById(Long id) {
+        return (BuInspectIndicator) inspectIndicatorDao.findById(BuInspectIndicator.class, id);
+    }
+
+    public boolean save(BuInspectIndicator indicator) {
+        if (indicator == null || indicator.getIndicatorCode() == null
+                || indicator.getIndicatorCode().trim().isEmpty()) {
+            return false;
+        }
+        if (indicator.getCreateDate() == null) {
+            indicator.setCreateDate(new java.sql.Timestamp(System.currentTimeMillis()));
+        }
+        return inspectIndicatorDao.save(indicator);
+    }
+
+    public boolean saveIndicator(BuInspectIndicator indicator) {
+        return save(indicator);
+    }
+
+    public boolean update(BuInspectIndicator indicator) {
+        if (indicator == null || indicator.getIndicatorId() == null) {
+            return false;
+        }
+        BuInspectIndicator old = getIndicatorById(indicator.getIndicatorId());
+        if (old == null) {
+            return false;
+        }
+        List<?> referenced = inspectPlanIndicatorDao.searchBySql(
+                "select PLAN_INDICATOR_ID from bu_inspect_plan_indicator where INDICATOR_ID = "
+                        + indicator.getIndicatorId() + " and STATUS = 1 limit 1");
+        if (!referenced.isEmpty()) {
+            indicator.setIndicatorCode(old.getIndicatorCode());
+            indicator.setIndicatorType(old.getIndicatorType());
+            indicator.setDataSource(old.getDataSource());
+            indicator.setScoreRule(old.getScoreRule());
+            indicator.setSort(old.getSort());
+            indicator.setStatus(old.getStatus());
+        }
+        indicator.setCreateDate(old.getCreateDate());
+        indicator.setUpdateDate(old.getUpdateDate());
+        return inspectIndicatorDao.update(indicator);
+    }
+
+    public boolean updateIndicator(BuInspectIndicator indicator) {
+        return update(indicator);
+    }
+
+    public boolean isReferencedByPlan(Long indicatorId) {
+        if (indicatorId == null) {
+            return false;
+        }
+        List<?> referenced = inspectPlanIndicatorDao.searchBySql(
+                "select PLAN_INDICATOR_ID from bu_inspect_plan_indicator where INDICATOR_ID = "
+                        + indicatorId + " and STATUS = 1 limit 1");
+        return !referenced.isEmpty();
+    }
+
+    public int changeStatus(Long id, Integer status) {
+        return inspectIndicatorDao.executeSql(
+                "update bu_inspect_indicator set STATUS = :status where INDICATOR_ID = :id",
+                InspectCommonUtils.with(InspectCommonUtils.params("id", id), "status", status));
+    }
+
+    public int changeIndicatorStatus(Long id, Integer status) {
+        return changeStatus(id, status);
+    }
+
+    public int delete(Long id) {
+        Map<String, Object> params = InspectCommonUtils.params("id", id);
+        return inspectIndicatorDao.executeSql(
+                "delete from bu_inspect_indicator where INDICATOR_ID = :id and STATUS = 0 "
+                        + "and not exists (select 1 from bu_inspect_plan_indicator p "
+                        + "where p.INDICATOR_ID = :id and p.STATUS = 1)", params);
+    }
+
+    public int deleteIndicator(Long id) {
+        return delete(id);
+    }
+
+    public BigDecimal checkWeight() {
+        List<?> rows = inspectIndicatorDao.searchBySql(
+                "select ifnull(sum(WEIGHT), 0) from bu_inspect_indicator where STATUS = 1");
+        return InspectCommonUtils.firstDecimal(rows);
+    }
+
+    public BigDecimal getEnabledIndicatorWeight() {
+        return checkWeight();
+    }
+}

+ 232 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectPlanService.java

@@ -0,0 +1,232 @@
+package com.sapit.client.app.manage.inspect.service;
+
+import com.sapit.client.app.manage.inspect.persistence.BuInspectPlanDao;
+import com.sapit.client.app.manage.inspect.persistence.BuInspectPlanIndicatorDao;
+import com.sapit.client.app.manage.inspect.persistence.BuInspectPlanStoreDao;
+import com.sapit.common.entity.inspect.BuInspectPlan;
+import com.sapit.common.entity.inspect.BuInspectPlanIndicator;
+import com.sapit.common.entity.inspect.BuInspectPlanStore;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.search.inspect.BuInspectPlanIndicatorSearchBean;
+import com.sapit.common.search.inspect.BuInspectPlanSearchBean;
+import com.sapit.common.search.inspect.BuInspectPlanStoreSearchBean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+@Service
+@Transactional
+public class InspectPlanService {
+
+    private static final Logger log = LoggerFactory.getLogger(InspectPlanService.class);
+
+    @Autowired
+    private BuInspectPlanDao inspectPlanDao;
+    @Autowired
+    private BuInspectPlanIndicatorDao inspectPlanIndicatorDao;
+    @Autowired
+    private BuInspectPlanStoreDao inspectPlanStoreDao;
+
+    public List<BuInspectPlan> getPlanPage(PageControlData page, BuInspectPlanSearchBean searchBean) {
+        return inspectPlanDao.getList(page, searchBean, " order by planId desc");
+    }
+
+    public BuInspectPlan getPlanDetail(Long id) {
+        return (BuInspectPlan) inspectPlanDao.findById(BuInspectPlan.class, id);
+    }
+
+    public BuInspectPlan getPlanById(Long id) {
+        return getPlanDetail(id);
+    }
+
+    public List<BuInspectPlanIndicator> getPlanIndicators(Long planId) {
+        BuInspectPlanIndicatorSearchBean searchBean = new BuInspectPlanIndicatorSearchBean();
+        searchBean.setPlanId(planId);
+        searchBean.setStatus(1);
+        return inspectPlanIndicatorDao.getList(searchBean, " order by sort asc, planIndicatorId asc");
+    }
+
+    public List<BuInspectPlanStore> getPlanStores(Long planId) {
+        BuInspectPlanStoreSearchBean searchBean = new BuInspectPlanStoreSearchBean();
+        searchBean.setPlanId(planId);
+        searchBean.setStatus(1);
+        return inspectPlanStoreDao.getList(searchBean, " order by planStoreId asc");
+    }
+
+    public boolean save(BuInspectPlan plan) {
+        if (!InspectCommonUtils.validPlan(plan)) {
+            return false;
+        }
+        return inspectPlanDao.save(plan);
+    }
+
+    public boolean savePlan(BuInspectPlan plan) {
+        return save(plan);
+    }
+
+    public boolean update(BuInspectPlan plan) {
+        if (!InspectCommonUtils.validPlan(plan) || plan.getPlanId() == null) {
+            return false;
+        }
+        BuInspectPlan old = getPlanById(plan.getPlanId());
+        if (old == null) {
+            return false;
+        }
+        if (old.getStatus() != null && old.getStatus() != 0) {
+            return false;
+        }
+        plan.setCreateDate(old.getCreateDate());
+        plan.setUpdateDate(old.getUpdateDate());
+        return inspectPlanDao.update(plan);
+    }
+
+    public boolean updatePlan(BuInspectPlan plan) {
+        return update(plan);
+    }
+
+    public int publish(Long id) {
+        BuInspectPlan plan = getPlanById(id);
+        if (plan == null) {
+            log.warn("publish 失败: plan 不存在, id={}", id);
+            return 0;
+        }
+        if (!InspectCommonUtils.validPlan(plan)) {
+            log.warn("publish 失败: plan 数据不完整, planName={}, startDate={}, endDate={}, cycleType={}",
+                    plan.getPlanName(), plan.getStartDate(), plan.getEndDate(), plan.getCycleType());
+            return 0;
+        }
+        List<BuInspectPlanIndicator> indicators = getPlanIndicators(id);
+        if (indicators.isEmpty()) {
+            log.warn("publish 失败: 没有关联指标, id={}", id);
+            return 0;
+        }
+        List<BuInspectPlanStore> stores = getPlanStores(id);
+        if (stores.isEmpty()) {
+            log.warn("publish 失败: 没有关联门店, id={}", id);
+            return 0;
+        }
+        if (!validPlanIndicatorWeight(id)) {
+            log.warn("publish 失败: 指标权重和不为100, id={}", id);
+            return 0;
+        }
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        Date today = calendar.getTime();
+        if (plan.getStartDate() != null && plan.getStartDate().before(today)) {
+            log.warn("publish 失败: startDate 已过期, startDate={}, today={}", plan.getStartDate(), today);
+            return 0;
+        }
+        log.info("publish 成功: 执行 UPDATE, id={}", id);
+        return inspectPlanDao.executeSql(
+                "update bu_inspect_plan set STATUS = 1, PUBLISH_DATE = NOW() where PLAN_ID = :id and STATUS = 0",
+                InspectCommonUtils.params("id", id));
+    }
+
+    public int publishPlan(Long id) {
+        return publish(id);
+    }
+
+    public int changeStatus(Long id, Integer status) {
+        return inspectPlanDao.executeSql(
+                "update bu_inspect_plan set STATUS = :status where PLAN_ID = :id",
+                InspectCommonUtils.with(InspectCommonUtils.params("id", id), "status", status));
+    }
+
+    public int pausePlan(Long id) {
+        return inspectPlanDao.executeSql(
+                "update bu_inspect_plan set STATUS = 2 where PLAN_ID = :id and STATUS = 1",
+                InspectCommonUtils.params("id", id));
+    }
+
+    public int finish(Long id) {
+        return inspectPlanDao.executeSql(
+                "update bu_inspect_plan set STATUS = 3, FINISH_DATE = NOW() where PLAN_ID = :id and STATUS in (1, 2)",
+                InspectCommonUtils.params("id", id));
+    }
+
+    public int finishPlan(Long id) {
+        return finish(id);
+    }
+
+    public int delete(Long id) {
+        return inspectPlanDao.executeSql("delete from bu_inspect_plan where PLAN_ID = :id",
+                InspectCommonUtils.params("id", id));
+    }
+
+    public int deletePlan(Long id) {
+        return delete(id);
+    }
+
+    public boolean validPlanIndicatorWeight(Long planId) {
+        List<?> rows = inspectPlanIndicatorDao.searchBySql(
+                "select ifnull(sum(WEIGHT), 0) from bu_inspect_plan_indicator "
+                        + "where PLAN_ID = " + planId + " and STATUS = 1");
+        BigDecimal weight = InspectCommonUtils.firstDecimal(rows);
+        return weight.compareTo(new BigDecimal("100")) == 0;
+    }
+
+    public List<BuInspectPlanIndicator> getPlanIndicatorPage(PageControlData page, BuInspectPlanIndicatorSearchBean searchBean) {
+        return inspectPlanIndicatorDao.getList(page, searchBean, " order by sort asc, planIndicatorId desc");
+    }
+
+    public boolean savePlanIndicator(BuInspectPlanIndicator planIndicator) {
+        return inspectPlanIndicatorDao.save(planIndicator);
+    }
+
+    public boolean updatePlanIndicator(BuInspectPlanIndicator planIndicator) {
+        if (planIndicator == null || planIndicator.getPlanIndicatorId() == null) {
+            return false;
+        }
+        BuInspectPlanIndicator old = (BuInspectPlanIndicator) inspectPlanIndicatorDao.findById(
+                BuInspectPlanIndicator.class, planIndicator.getPlanIndicatorId());
+        if (old == null) {
+            return false;
+        }
+        planIndicator.setCreateDate(old.getCreateDate());
+        planIndicator.setUpdateDate(old.getUpdateDate());
+        return inspectPlanIndicatorDao.update(planIndicator);
+    }
+
+    public int deletePlanIndicator(Long id) {
+        return inspectPlanIndicatorDao.executeSql(
+                "delete from bu_inspect_plan_indicator where PLAN_INDICATOR_ID = :id",
+                InspectCommonUtils.params("id", id));
+    }
+
+    public List<BuInspectPlanStore> getPlanStorePage(PageControlData page, BuInspectPlanStoreSearchBean searchBean) {
+        return inspectPlanStoreDao.getList(page, searchBean, " order by planStoreId desc");
+    }
+
+    public boolean savePlanStore(BuInspectPlanStore planStore) {
+        return inspectPlanStoreDao.save(planStore);
+    }
+
+    public boolean updatePlanStore(BuInspectPlanStore planStore) {
+        if (planStore == null || planStore.getPlanStoreId() == null) {
+            return false;
+        }
+        BuInspectPlanStore old = (BuInspectPlanStore) inspectPlanStoreDao.findById(
+                BuInspectPlanStore.class, planStore.getPlanStoreId());
+        if (old == null) {
+            return false;
+        }
+        planStore.setCreateDate(old.getCreateDate());
+        return inspectPlanStoreDao.update(planStore);
+    }
+
+    public int deletePlanStore(Long id) {
+        return inspectPlanStoreDao.executeSql(
+                "delete from bu_inspect_plan_store where PLAN_STORE_ID = :id",
+                InspectCommonUtils.params("id", id));
+    }
+}

+ 256 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectScheduleService.java

@@ -0,0 +1,256 @@
+package com.sapit.client.app.manage.inspect.service;
+
+import com.sapit.client.app.manage.inspect.persistence.*;
+import com.sapit.common.entity.inspect.BuInspectIndicator;
+import com.sapit.common.entity.inspect.BuInspectPlan;
+import com.sapit.common.entity.inspect.BuInspectPlanIndicator;
+import com.sapit.common.entity.inspect.BuInspectPlanStore;
+import com.sapit.common.entity.inspect.BuInspectTask;
+import com.sapit.common.entity.inspect.BuInspectTaskItem;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+@Service
+@Transactional
+public class InspectScheduleService {
+
+    @Autowired
+    private BuInspectPlanDao inspectPlanDao;
+    @Autowired
+    private BuInspectPlanIndicatorDao inspectPlanIndicatorDao;
+    @Autowired
+    private BuInspectPlanStoreDao inspectPlanStoreDao;
+    @Autowired
+    private BuInspectTaskDao inspectTaskDao;
+    @Autowired
+    private BuInspectTaskItemDao inspectTaskItemDao;
+    @Autowired
+    private BuRectificationDao rectificationDao;
+    @Autowired
+    private BuAssessResultDao assessResultDao;
+
+    @Autowired
+    private InspectAutoScoreService autoScoreService;
+    @Autowired
+    private InspectPlanService inspectPlanService;
+    @Autowired
+    private InspectIndicatorService inspectIndicatorService;
+    @Autowired
+    private AssessResultService assessResultService;
+
+    /**
+     * 为指定计划在指定日期生成任务(管理员手动触发)。
+     */
+    public int generateTaskByPlan(Long planId, Date taskDate) {
+        if (planId == null || taskDate == null) {
+            return 0;
+        }
+        BuInspectPlan plan = inspectPlanService.getPlanById(planId);
+        if (plan == null || plan.getStatus() == null || plan.getStatus() != 1) {
+            return 0;
+        }
+        generateTaskByPlan(plan, taskDate);
+        return 1;
+    }
+
+    /**
+     * 生成到期任务:扫描已发布计划中今日应生成任务的数据,为每个计划门店创建巡检任务。
+     */
+    public void generateDueTasks() {
+        Date today = new Date();
+        generateDailyTasks(today);
+        generateWeeklyTasks(today);
+        generateMonthlyTasks(today);
+    }
+
+    private void generateDailyTasks(Date today) {
+        List<BuInspectPlan> plans = getPlansByCycleType(1, today);
+        for (BuInspectPlan plan : plans) {
+            generateTaskByPlan(plan, today);
+        }
+    }
+
+    private void generateWeeklyTasks(Date today) {
+        List<BuInspectPlan> plans = getPlansByCycleType(2, today);
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(today);
+        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
+        for (BuInspectPlan plan : plans) {
+            generateTaskByPlan(plan, today);
+        }
+    }
+
+    private void generateMonthlyTasks(Date today) {
+        List<BuInspectPlan> plans = getPlansByCycleType(3, today);
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(today);
+        int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
+        for (BuInspectPlan plan : plans) {
+            generateTaskByPlan(plan, today);
+        }
+    }
+
+    private List<BuInspectPlan> getPlansByCycleType(Integer cycleType, Date today) {
+        return inspectPlanDao.searchBySql(
+                "select * from bu_inspect_plan where STATUS = 1 and CYCLE_TYPE = " + cycleType + " "
+                        + "and START_DATE <= '" + formatDate(today) + "' "
+                        + "and (FINISH_DATE is null or FINISH_DATE >= '" + formatDate(today) + "')");
+    }
+
+    private void generateTaskByPlan(BuInspectPlan plan, Date today) {
+        List<BuInspectPlanIndicator> planIndicators = inspectPlanService.getPlanIndicators(plan.getPlanId());
+        if (planIndicators == null || planIndicators.isEmpty()) {
+            return;
+        }
+        List<BuInspectPlanStore> planStores = inspectPlanService.getPlanStores(plan.getPlanId());
+        if (planStores == null || planStores.isEmpty()) {
+            return;
+        }
+        for (BuInspectPlanStore planStore : planStores) {
+            boolean exists = checkTaskExists(plan.getPlanId(), planStore.getStoreId(), today);
+            if (exists) {
+                continue;
+            }
+            BuInspectTask task = new BuInspectTask();
+            task.setPlanId(plan.getPlanId());
+            task.setPlanName(plan.getPlanName());
+            task.setStoreId(planStore.getStoreId());
+            task.setStoreName(planStore.getStoreName());
+            task.setInspectorId(plan.getInspectorId());
+            task.setInspectorName(plan.getInspectorName());
+            task.setTaskDate(new java.sql.Timestamp(today.getTime()));
+            task.setStatus(1);
+            task.setTotalScore(BigDecimal.ZERO);
+            task.setMaxScore(BigDecimal.ZERO);
+            task.setPassScore(BigDecimal.ZERO);
+            task.setUnpassItemCount(0);
+            task.setPassFlag(0);
+            task.setCreateDate(new java.sql.Timestamp(System.currentTimeMillis()));
+            Long taskId = null;
+            inspectTaskDao.save(task);
+            if (task.getTaskId() != null) {
+                taskId = task.getTaskId();
+            } else {
+                continue;
+            }
+            for (BuInspectPlanIndicator planIndicator : planIndicators) {
+                BuInspectTaskItem item = new BuInspectTaskItem();
+                item.setTaskId(taskId);
+                item.setPlanId(plan.getPlanId());
+                item.setStoreId(planStore.getStoreId());
+                item.setPlanIndicatorId(planIndicator.getPlanIndicatorId());
+                item.setIndicatorId(planIndicator.getIndicatorId());
+                item.setIndicatorName(planIndicator.getIndicatorName());
+                item.setIndicatorType(planIndicator.getIndicatorType());
+                item.setMaxScore(planIndicator.getMaxScore());
+                item.setPassScore(planIndicator.getPassScore());
+                item.setWeight(planIndicator.getWeight());
+                item.setSort(planIndicator.getSort());
+                item.setStatus(1);
+                item.setScoreLevel(0);
+                item.setScore(BigDecimal.ZERO);
+                item.setWeightedScore(BigDecimal.ZERO);
+                item.setPassFlag(0);
+                item.setCreateDate(new java.sql.Timestamp(System.currentTimeMillis()));
+                BuInspectIndicator indicator = inspectIndicatorService.getIndicatorById(planIndicator.getIndicatorId());
+                if (indicator != null && indicator.getIndicatorType() != null && indicator.getIndicatorType() == 2) {
+                    InspectCommonUtils.AutoScoreResult autoScoreResult = autoScoreService.calculateAutoScore(task, indicator);
+                    if (autoScoreResult != null) {
+                        item.setScore(autoScoreResult.score);
+                        item.setWeightedScore(autoScoreResult.weightedScore);
+                        item.setRawValue(autoScoreResult.rawValue);
+                        item.setRawValueText(autoScoreResult.rawValueText);
+                        item.setPassFlag(autoScoreResult.passFlag);
+                    }
+                }
+                inspectTaskItemDao.save(item);
+            }
+        }
+    }
+
+    private boolean checkTaskExists(Long planId, Long storeId, Date today) {
+        List<?> rows = inspectTaskDao.searchBySql(
+                "select 1 from bu_inspect_task where PLAN_ID = " + planId
+                        + " and STORE_ID = " + storeId + " and date(TASK_DATE) = date('" + formatDate(today) + "')");
+        return rows != null && !rows.isEmpty();
+    }
+
+    /**
+     * 检查逾期整改:将超过截止日期且未完成的整改标记为逾期(6)。
+     */
+    public void checkRectificationOvertime() {
+        rectificationDao.executeSql(
+                "update bu_rectification set STATUS = 6 "
+                        + "where STATUS in (1, 2, 3, 5) and DEADLINE_DATE < NOW()");
+    }
+
+    /**
+     * 周考核汇总:按周期汇聚本周数据。
+     */
+    public void assessWeeklySummary() {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(new Date());
+        cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
+        Date startOfWeek = cal.getTime();
+        cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek() + 6);
+        Date endOfWeek = cal.getTime();
+        int weekOfYear = cal.get(Calendar.WEEK_OF_YEAR);
+        int year = cal.get(Calendar.YEAR);
+        String periodCode = year + "W" + weekOfYear;
+        assessResultService.triggerAssess(1, periodCode, startOfWeek, endOfWeek);
+        List<?> storeRows = assessResultDao.searchBySql(
+                "select distinct STORE_ID from bu_assess_result "
+                        + "where PERIOD_TYPE = 1 and PERIOD_CODE = '" + periodCode + "' and STATUS = 1");
+        for (Object row : storeRows) {
+            Long storeId = Long.valueOf(((Object[]) row)[0].toString());
+            assessResultService.summarizeAssessResult(storeId, 1, periodCode, startOfWeek, endOfWeek);
+        }
+        assessResultService.updateAssessRank(1, periodCode);
+    }
+
+    /**
+     * 月考核汇总:按周期汇聚本月数据。
+     */
+    public void assessMonthlySummary() {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(new Date());
+        cal.set(Calendar.DAY_OF_MONTH, 1);
+        Date startOfMonth = cal.getTime();
+        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+        Date endOfMonth = cal.getTime();
+        int month = cal.get(Calendar.MONTH) + 1;
+        int year = cal.get(Calendar.YEAR);
+        String periodCode = year + (month < 10 ? "0" : "") + month;
+        assessResultService.triggerAssess(2, periodCode, startOfMonth, endOfMonth);
+        List<?> storeRows = assessResultDao.searchBySql(
+                "select distinct STORE_ID from bu_assess_result "
+                        + "where PERIOD_TYPE = 2 and PERIOD_CODE = '" + periodCode + "' and STATUS = 1");
+        for (Object row : storeRows) {
+            Long storeId = Long.valueOf(((Object[]) row)[0].toString());
+            assessResultService.summarizeAssessResult(storeId, 2, periodCode, startOfMonth, endOfMonth);
+        }
+        assessResultService.updateAssessRank(2, periodCode);
+    }
+
+    /**
+     * 检查计划状态:将已过结束日期且仍在发布中的计划自动标记为已结束(4)。
+     */
+    public void checkPlanFinish() {
+        inspectPlanDao.executeSql(
+                "update bu_inspect_plan set STATUS = 4 "
+                        + "where STATUS = 1 and END_DATE is not null and END_DATE < NOW()");
+    }
+
+    private String formatDate(Date date) {
+        if (date == null) {
+            return new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date());
+        }
+        return new java.text.SimpleDateFormat("yyyy-MM-dd").format(date);
+    }
+}

+ 203 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/service/InspectTaskService.java

@@ -0,0 +1,203 @@
+package com.sapit.client.app.manage.inspect.service;
+
+import com.sapit.client.app.manage.inspect.persistence.BuInspectTaskDao;
+import com.sapit.client.app.manage.inspect.persistence.BuInspectTaskItemDao;
+import com.sapit.client.app.manage.inspect.persistence.BuRectificationDao;
+import com.sapit.common.entity.inspect.BuInspectTask;
+import com.sapit.common.entity.inspect.BuInspectTaskItem;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.search.inspect.BuInspectTaskItemSearchBean;
+import com.sapit.common.search.inspect.BuInspectTaskSearchBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Transactional
+public class InspectTaskService {
+
+    @Autowired
+    private BuInspectTaskDao inspectTaskDao;
+    @Autowired
+    private BuInspectTaskItemDao inspectTaskItemDao;
+    @Autowired
+    private BuRectificationDao rectificationDao;
+
+    public List<BuInspectTask> getTaskPage(PageControlData page, BuInspectTaskSearchBean searchBean) {
+        return inspectTaskDao.getList(page, searchBean, " order by taskDate desc, taskId desc");
+    }
+
+    public BuInspectTask getTaskDetail(Long id) {
+        return (BuInspectTask) inspectTaskDao.findById(BuInspectTask.class, id);
+    }
+
+    public BuInspectTask getTaskById(Long id) {
+        return getTaskDetail(id);
+    }
+
+    public boolean updateTask(BuInspectTask task) {
+        if (task == null || task.getTaskId() == null) {
+            return false;
+        }
+        BuInspectTask old = getTaskById(task.getTaskId());
+        if (old == null) {
+            return false;
+        }
+        task.setCreateDate(old.getCreateDate());
+        task.setUpdateDate(old.getUpdateDate());
+        return inspectTaskDao.update(task);
+    }
+
+    public int cancelTask(Long id) {
+        return inspectTaskDao.executeSql(
+                "update bu_inspect_task set STATUS = 4, CANCEL_DATE = NOW() where TASK_ID = :id and STATUS in (1, 2)",
+                InspectCommonUtils.params("id", id));
+    }
+
+    public List<BuInspectTaskItem> getTaskItems(Long taskId) {
+        BuInspectTaskItemSearchBean searchBean = new BuInspectTaskItemSearchBean();
+        searchBean.setTaskId(taskId);
+        return inspectTaskItemDao.getList(searchBean, " order by sort asc, taskItemId asc");
+    }
+
+    public int startTask(Long taskId) {
+        return inspectTaskDao.executeSql(
+                "update bu_inspect_task set STATUS = 2, START_DATE = NOW() where TASK_ID = :id and STATUS = 1",
+                InspectCommonUtils.params("id", taskId));
+    }
+
+    public int startTask(Long taskId, Long inspectorId) {
+        if (taskId == null || inspectorId == null) {
+            return 0;
+        }
+        return inspectTaskDao.executeSql(
+                "update bu_inspect_task set STATUS = 2, START_DATE = NOW() "
+                        + "where TASK_ID = :id and INSPECTOR_ID = :inspectorId and STATUS = 1",
+                InspectCommonUtils.with(InspectCommonUtils.params("id", taskId), "inspectorId", inspectorId));
+    }
+
+    public int submitTask(Long taskId) {
+        BuInspectTask task = getTaskById(taskId);
+        if (task == null || task.getStatus() == null || task.getStatus() != 2) {
+            return 0;
+        }
+        List<BuInspectTaskItem> items = getTaskItems(taskId);
+        if (items.isEmpty()) {
+            return 0;
+        }
+        for (BuInspectTaskItem item : items) {
+            if (item.getScore() == null || item.getMaxScore() == null
+                    || item.getScore().compareTo(BigDecimal.ZERO) < 0
+                    || item.getScore().compareTo(item.getMaxScore()) > 0) {
+                return 0;
+            }
+            int passFlag = item.getScore().compareTo(
+                    item.getPassScore() == null ? BigDecimal.ZERO : item.getPassScore()) >= 0 ? 1 : 0;
+            BigDecimal weightedScore = InspectCommonUtils.weighted(item.getScore(), item.getWeight());
+            Map<String, Object> itemParams = new HashMap<>();
+            itemParams.put("itemId", item.getTaskItemId());
+            itemParams.put("passFlag", passFlag);
+            itemParams.put("weightedScore", weightedScore);
+            inspectTaskItemDao.executeSql(
+                    "update bu_inspect_task_item set PASS_FLAG = :passFlag, WEIGHTED_SCORE = :weightedScore "
+                            + "where TASK_ITEM_ID = :itemId and TASK_ID = :taskId",
+                    InspectCommonUtils.with(itemParams, "taskId", taskId));
+        }
+        String sql = "update bu_inspect_task t set t.TOTAL_SCORE = "
+                + "(select ifnull(sum(i.WEIGHTED_SCORE), 0) from bu_inspect_task_item i where i.TASK_ID = t.TASK_ID), "
+                + "t.MAX_SCORE = (select ifnull(sum(i.MAX_SCORE * ifnull(i.WEIGHT, 100) / 100), 0) from bu_inspect_task_item i where i.TASK_ID = t.TASK_ID), "
+                + "t.PASS_SCORE = (select ifnull(sum(i.PASS_SCORE * ifnull(i.WEIGHT, 100) / 100), 0) from bu_inspect_task_item i where i.TASK_ID = t.TASK_ID), "
+                + "t.UNPASS_ITEM_COUNT = (select count(*) from bu_inspect_task_item i where i.TASK_ID = t.TASK_ID and i.PASS_FLAG = 0), "
+                + "t.PASS_FLAG = case when exists (select 1 from bu_inspect_task_item i where i.TASK_ID = t.TASK_ID and i.PASS_FLAG = 0) then 0 else 1 end, "
+                + "t.STATUS = 3, t.SUBMIT_DATE = NOW() where t.TASK_ID = " + taskId
+                + " and t.STATUS = 2";
+        int updated = inspectTaskDao.executeSql(sql);
+        if (updated > 0) {
+            createRectificationsForTask(taskId);
+        }
+        return updated;
+    }
+
+    public int submitTask(Long taskId, Long inspectorId) {
+        BuInspectTask task = getTaskById(taskId);
+        if (task == null || inspectorId == null || !inspectorId.equals(task.getInspectorId())) {
+            return 0;
+        }
+        return submitTask(taskId);
+    }
+
+    public List<BuInspectTask> getMyTaskList(Long inspectorId) {
+        if (inspectorId == null) {
+            return new ArrayList<>();
+        }
+        BuInspectTaskSearchBean searchBean = new BuInspectTaskSearchBean();
+        searchBean.setInspectorId(inspectorId);
+        searchBean.setStatus(1);
+        return inspectTaskDao.getList(searchBean, " order by taskDate desc, taskId desc");
+    }
+
+    public List<?> getTaskStatistics(Long planId) {
+        String sql = "select PLAN_ID, count(1) as task_count, sum(case when STATUS = 3 then 1 else 0 end) as finish_count, "
+                + "sum(case when PASS_FLAG = 1 then 1 else 0 end) as pass_count, "
+                + "ifnull(avg(TOTAL_SCORE), 0) as avg_score "
+                + "from bu_inspect_task where 1 = 1 ";
+        if (planId != null) {
+            sql += " and PLAN_ID = " + planId;
+        }
+        sql += " group by PLAN_ID";
+        return inspectTaskDao.searchBySql(sql);
+    }
+
+    public List<BuInspectTaskItem> getTaskItemPage(PageControlData page, BuInspectTaskItemSearchBean searchBean) {
+        return inspectTaskItemDao.getList(page, searchBean, " order by sort asc, taskItemId desc");
+    }
+
+    public boolean saveTaskItem(BuInspectTaskItem taskItem) {
+        if (taskItem != null && !InspectCommonUtils.validPictureUrls(taskItem.getPictureUrls())) {
+            return false;
+        }
+        return inspectTaskItemDao.save(taskItem);
+    }
+
+    public boolean updateTaskItem(BuInspectTaskItem taskItem) {
+        if (taskItem != null && !InspectCommonUtils.validPictureUrls(taskItem.getPictureUrls())) {
+            return false;
+        }
+        if (taskItem == null || taskItem.getTaskItemId() == null) {
+            return false;
+        }
+        BuInspectTaskItem old = (BuInspectTaskItem) inspectTaskItemDao.findById(
+                BuInspectTaskItem.class, taskItem.getTaskItemId());
+        if (old == null) {
+            return false;
+        }
+        taskItem.setCreateDate(old.getCreateDate());
+        taskItem.setUpdateDate(old.getUpdateDate());
+        return inspectTaskItemDao.update(taskItem);
+    }
+
+    public int deleteTaskItem(Long id) {
+        return inspectTaskItemDao.executeSql(
+                "delete from bu_inspect_task_item where TASK_ITEM_ID = :id",
+                InspectCommonUtils.params("id", id));
+    }
+
+    public int createRectificationsForTask(Long taskId) {
+        String sql = "insert into bu_rectification "
+                + "(TASK_ID, TASK_ITEM_ID, PLAN_ID, STORE_ID, STORE_NAME, INDICATOR_ID, INDICATOR_NAME, "
+                + "RECT_ROUND, INSPECT_SCORE, PASS_SCORE, NOTICE_CONTENT, DEADLINE_DATE, STATUS, CREATE_DATE) "
+                + "select i.TASK_ID, i.TASK_ITEM_ID, i.PLAN_ID, i.STORE_ID, t.STORE_NAME, i.INDICATOR_ID, "
+                + "i.INDICATOR_NAME, 1, i.SCORE, i.PASS_SCORE, concat('指标【', i.INDICATOR_NAME, '】未达标,请完成整改'), "
+                + "date_add(ifnull(t.SUBMIT_DATE, NOW()), interval 7 day), 1, NOW() "
+                + "from bu_inspect_task_item i join bu_inspect_task t on t.TASK_ID = i.TASK_ID "
+                + "where i.TASK_ID = :taskId and i.PASS_FLAG = 0 "
+                + "and not exists (select 1 from bu_rectification r where r.TASK_ITEM_ID = i.TASK_ITEM_ID and r.RECT_ROUND = 1)";
+        return rectificationDao.executeSql(sql, InspectCommonUtils.params("taskId", taskId));
+    }
+}

+ 197 - 0
client-app/src/main/java/com/sapit/client/app/manage/inspect/service/RectificationService.java

@@ -0,0 +1,197 @@
+package com.sapit.client.app.manage.inspect.service;
+
+import com.sapit.client.app.manage.inspect.persistence.BuRectificationDao;
+import com.sapit.common.entity.inspect.BuRectification;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.search.inspect.BuRectificationSearchBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Transactional
+public class RectificationService {
+
+    @Autowired
+    private BuRectificationDao rectificationDao;
+
+    public List<BuRectification> getRectPage(PageControlData page, BuRectificationSearchBean searchBean) {
+        return rectificationDao.getList(page, searchBean, " order by deadlineDate asc, rectId desc");
+    }
+
+    public List<BuRectification> getRectificationPage(PageControlData page, BuRectificationSearchBean searchBean) {
+        return getRectPage(page, searchBean);
+    }
+
+    public BuRectification getRectDetail(Long id) {
+        return (BuRectification) rectificationDao.findById(BuRectification.class, id);
+    }
+
+    public BuRectification getRectificationById(Long id) {
+        return getRectDetail(id);
+    }
+
+    public List<BuRectification> getMyRectificationList(Long storeId) {
+        BuRectificationSearchBean searchBean = new BuRectificationSearchBean();
+        searchBean.setStoreId(storeId);
+        return rectificationDao.getList(searchBean, " order by status asc, deadlineDate asc, rectId desc");
+    }
+
+    public int startRectification(Long id, Long storeId) {
+        return rectificationDao.executeSql(
+                "update bu_rectification set STATUS = 2, START_DATE = NOW() "
+                        + "where RECT_ID = :id and STORE_ID = :storeId and STATUS = 1",
+                InspectCommonUtils.with(InspectCommonUtils.params("id", id), "storeId", storeId));
+    }
+
+    public boolean saveRectification(BuRectification rectification) {
+        return rectificationDao.save(rectification);
+    }
+
+    public boolean updateRectification(BuRectification rectification) {
+        if (rectification == null || rectification.getRectId() == null) {
+            return false;
+        }
+        BuRectification old = getRectificationById(rectification.getRectId());
+        if (old == null) {
+            return false;
+        }
+        rectification.setCreateDate(old.getCreateDate());
+        rectification.setUpdateDate(old.getUpdateDate());
+        return rectificationDao.update(rectification);
+    }
+
+    public int deleteRectification(Long id) {
+        return rectificationDao.executeSql("delete from bu_rectification where RECT_ID = :id",
+                InspectCommonUtils.params("id", id));
+    }
+
+    public int batchUpdateDeadline(List<Long> rectIds, Date deadlineDate) {
+        if (rectIds == null || rectIds.isEmpty() || deadlineDate == null) {
+            return 0;
+        }
+        String deadline = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(deadlineDate);
+        StringBuilder sql = new StringBuilder("update bu_rectification set DEADLINE_DATE = '")
+                .append(deadline).append("' where RECT_ID in (");
+        for (int i = 0; i < rectIds.size(); i++) {
+            if (i > 0) {
+                sql.append(',');
+            }
+            sql.append(rectIds.get(i));
+        }
+        sql.append(')');
+        return rectificationDao.executeSql(sql.toString());
+    }
+
+    public int submitRectification(Long id, String feedbackContent, String feedbackPictures) {
+        if (!InspectCommonUtils.validPictureUrls(feedbackPictures)) {
+            return 0;
+        }
+        String sql = "update bu_rectification set FEEDBACK_CONTENT = :feedbackContent, "
+                + "FEEDBACK_PICTURES = :feedbackPictures, FEEDBACK_DATE = NOW(), STATUS = 3 "
+                + "where RECT_ID = :id and STATUS in (1, 2)";
+        Map<String, Object> params = new HashMap<>();
+        params.put("id", id);
+        params.put("feedbackContent", feedbackContent);
+        params.put("feedbackPictures", feedbackPictures);
+        return rectificationDao.executeSql(sql, params);
+    }
+
+    public int recheck(Long id, BigDecimal recheckScore, Integer recheckResult,
+                       String recheckPictures, String recheckRmk, Long recheckUserId,
+                       String recheckUserName) {
+        if (!InspectCommonUtils.validPictureUrls(recheckPictures)) {
+            return 0;
+        }
+        BuRectification current = id == null ? null : getRectificationById(id);
+        if (current == null || recheckScore == null || recheckScore.compareTo(BigDecimal.ZERO) < 0
+                || (current.getPassScore() != null
+                && recheckScore.compareTo(current.getPassScore().multiply(new BigDecimal("2"))) > 0)) {
+            return 0;
+        }
+        boolean passed = recheckResult != null && recheckResult == 1;
+        boolean finalFailed = !passed && current != null && current.getRectRound() != null
+                && current.getRectRound() >= 3;
+        int status = passed ? 4 : (finalFailed ? 7 : 5);
+        String sql = "update bu_rectification set RECHECK_SCORE = :recheckScore, "
+                + "RECHECK_RESULT = :recheckResult, RECHECK_PICTURES = :recheckPictures, "
+                + "RECHECK_RMK = :recheckRmk, RECHECK_USER_ID = :recheckUserId, "
+                + "RECHECK_USER_NAME = :recheckUserName, RECHECK_DATE = NOW(), STATUS = :status "
+                + "where RECT_ID = :id and STATUS = 3";
+        Map<String, Object> params = new HashMap<>();
+        params.put("id", id);
+        params.put("recheckScore", recheckScore);
+        params.put("recheckResult", recheckResult);
+        params.put("recheckPictures", recheckPictures);
+        params.put("recheckRmk", recheckRmk);
+        params.put("recheckUserId", recheckUserId);
+        params.put("recheckUserName", recheckUserName);
+        params.put("status", status);
+        int updated = rectificationDao.executeSql(sql, params);
+        if (updated > 0 && status == 5) {
+            BuRectification rectification = getRectificationById(id);
+            if (rectification != null && rectification.getRectRound() != null
+                    && rectification.getRectRound() < 3) {
+                createNextRectification(rectification);
+            }
+        }
+        return updated;
+    }
+
+    public int recheckRectification(Long id, BigDecimal recheckScore, Integer recheckResult,
+                                    String recheckPictures, String recheckRmk, Long recheckUserId,
+                                    String recheckUserName) {
+        return recheck(id, recheckScore, recheckResult, recheckPictures, recheckRmk, recheckUserId, recheckUserName);
+    }
+
+    public int urge(Long id) {
+        return rectificationDao.executeSql(
+                "update bu_rectification set URGE_COUNT = ifnull(URGE_COUNT, 0) + 1, "
+                        + "LAST_URGE_DATE = NOW() where RECT_ID = :id and STATUS in (1, 2, 3, 5, 6)",
+                InspectCommonUtils.params("id", id));
+    }
+
+    public int urgeRectification(Long id) {
+        return urge(id);
+    }
+
+    public List<?> getRectStatistics(Long storeId) {
+        String sql = "select STORE_ID, count(1) as rect_count, "
+                + "sum(case when STATUS = 6 then 1 else 0 end) as overtime_count, "
+                + "ifnull(avg(timestampdiff(day, CREATE_DATE, ifnull(RECHECK_DATE, NOW()))), 0) as avg_days "
+                + "from bu_rectification where 1 = 1 ";
+        if (storeId != null) {
+            sql += " and STORE_ID = " + storeId;
+        }
+        sql += " group by STORE_ID";
+        return rectificationDao.searchBySql(sql);
+    }
+
+    public int createNextRectification(BuRectification previous) {
+        String sql = "insert into bu_rectification "
+                + "(TASK_ID, TASK_ITEM_ID, PLAN_ID, STORE_ID, STORE_NAME, INDICATOR_ID, INDICATOR_NAME, "
+                + "RECT_ROUND, PARENT_RECT_ID, INSPECT_SCORE, PASS_SCORE, NOTICE_CONTENT, DEADLINE_DATE, STATUS, CREATE_DATE) "
+                + "values (:taskId, :taskItemId, :planId, :storeId, :storeName, :indicatorId, :indicatorName, "
+                + ":rectRound, :parentRectId, :inspectScore, :passScore, :noticeContent, date_add(NOW(), interval 7 day), 1, NOW())";
+        Map<String, Object> values = new HashMap<>();
+        values.put("taskId", previous.getTaskId());
+        values.put("taskItemId", previous.getTaskItemId());
+        values.put("planId", previous.getPlanId());
+        values.put("storeId", previous.getStoreId());
+        values.put("storeName", previous.getStoreName());
+        values.put("indicatorId", previous.getIndicatorId());
+        values.put("indicatorName", previous.getIndicatorName());
+        values.put("rectRound", previous.getRectRound() + 1);
+        values.put("parentRectId", previous.getRectId());
+        values.put("inspectScore", previous.getInspectScore());
+        values.put("passScore", previous.getPassScore());
+        values.put("noticeContent", previous.getNoticeContent());
+        return rectificationDao.executeSql(sql, values);
+    }
+}

+ 40 - 0
client-app/src/main/java/com/sapit/client/app/scheduled/ctl/InspectScheduledHandle.java

@@ -0,0 +1,40 @@
+package com.sapit.client.app.scheduled.ctl;
+
+import com.sapit.client.app.manage.inspect.service.InspectScheduleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+/**
+ * 门店巡检与考核定时任务入口。
+ */
+@Component
+public class InspectScheduledHandle {
+    @Autowired
+    private InspectScheduleService inspectScheduleService;
+
+    @Scheduled(cron = "0 5 0 * * ?")
+    public void generateDueTasks() {
+        inspectScheduleService.generateDueTasks();
+    }
+
+    @Scheduled(cron = "0 15 0 * * ?")
+    public void updateOvertimeRectification() {
+        inspectScheduleService.checkRectificationOvertime();
+    }
+
+    @Scheduled(cron = "0 30 0 ? * MON")
+    public void weeklyAssessSummary() {
+        inspectScheduleService.assessWeeklySummary();
+    }
+
+    @Scheduled(cron = "0 45 0 1 * ?")
+    public void monthlyAssessSummary() {
+        inspectScheduleService.assessMonthlySummary();
+    }
+
+    @Scheduled(cron = "0 25 0 * * ?")
+    public void checkPlanFinish() {
+        inspectScheduleService.checkPlanFinish();
+    }
+}

+ 132 - 0
client-app/src/main/java/com/sapit/client/app/store/controller/inspect/RectificationApiController.java

@@ -0,0 +1,132 @@
+package com.sapit.client.app.store.controller.inspect;
+
+import com.sapit.client.app.manage.inspect.service.RectificationService;
+import com.sapit.common.constant.ReturnCode;
+import com.sapit.common.entity.inspect.BuRectification;
+import com.sapit.common.framwork.AuthToken;
+import com.sapit.common.framwork.ResultBean;
+import com.sapit.common.util.DateUtil;
+import com.sapit.common.util.FileFtpUploadUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * 店铺端整改接口。
+ *
+ * 门店身份由现有销售端 token 体系负责解析,当前接口保留 storeId 参数,
+ * 便于兼容项目已有 Controller 的参数绑定方式。
+ */
+@RestController
+@RequestMapping(value = "rectificationApi", produces = "application/json")
+public class RectificationApiController {
+    @Autowired
+    private RectificationService rectificationService;
+
+    @PostMapping("/getMyRectList")
+    @AuthToken(type = "sales")
+    public ResultBean getMyRectList(Long storeId) {
+        if (storeId == null) {
+            return paramError();
+        }
+        return listResult(rectificationService.getMyRectificationList(storeId));
+    }
+
+    @PostMapping("/startRect")
+    @AuthToken(type = "sales")
+    public ResultBean startRect(Long id, Long storeId) {
+        if (id == null || storeId == null) {
+            return paramError();
+        }
+        return updateResult(rectificationService.startRectification(id, storeId), "开始整改完成");
+    }
+
+    @PostMapping("/submitRect")
+    @AuthToken(type = "sales")
+    public ResultBean submitRect(Long id, Long storeId, String feedbackContent, String feedbackPictures) {
+        if (id == null || storeId == null) {
+            return paramError();
+        }
+        BuRectification rectification = rectificationService.getRectificationById(id);
+        if (rectification == null || !storeId.equals(rectification.getStoreId())) {
+            return failed("整改记录不存在");
+        }
+        return updateResult(rectificationService.submitRectification(id, feedbackContent, feedbackPictures), "提交整改完成");
+    }
+
+    @PostMapping("/getRectDetail")
+    @AuthToken(type = "sales")
+    public ResultBean getRectDetail(Long id, Long storeId) {
+        if (id == null || storeId == null) {
+            return paramError();
+        }
+        BuRectification rectification = rectificationService.getRectificationById(id);
+        if (rectification == null || !storeId.equals(rectification.getStoreId())) {
+            return failed("整改记录不存在");
+        }
+        ResultBean bean = new ResultBean();
+        bean.setObj(rectification);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+    @PostMapping("/uploadPicture")
+    @AuthToken(type = "sales")
+    public ResultBean uploadPicture(@RequestParam(value = "file", required = false) MultipartFile file) {
+        ResultBean bean = new ResultBean();
+        if (file == null || file.getOriginalFilename() == null || file.getOriginalFilename().isEmpty()) {
+            return paramError();
+        }
+        if (file.getSize() > 204800) {
+            bean.getRtnBean().setRtnCode(ReturnCode.SYSTEMERR);
+            bean.getRtnBean().setRtnMsg("图片大小超过200K");
+            return bean;
+        }
+        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.') + 1);
+        String fileName = DateUtil.getStrToday("yyyyMMddHHmmssSSS") + "." + suffix;
+        try {
+            if (!FileFtpUploadUtil.uploadFile("/rectification", fileName, file.getInputStream())) {
+                bean.getRtnBean().setRtnCode(ReturnCode.SYSTEMERR);
+                bean.getRtnBean().setRtnMsg("文件上传失败");
+                return bean;
+            }
+        } catch (Exception e) {
+            bean.getRtnBean().setRtnCode(ReturnCode.SYSTEMERR);
+            bean.getRtnBean().setRtnMsg("文件上传失败");
+            return bean;
+        }
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        bean.setObj(FileFtpUploadUtil.rootURL + "/rectification/" + fileName);
+        return bean;
+    }
+
+    private ResultBean updateResult(int result, String message) {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(result > 0 ? ReturnCode.SUCCESS : ReturnCode.FAILED);
+        bean.getRtnBean().setRtnMsg(message);
+        return bean;
+    }
+
+    private ResultBean listResult(List<?> list) {
+        ResultBean bean = new ResultBean();
+        bean.setList(list);
+        bean.getRtnBean().setRtnCode(ReturnCode.SUCCESS);
+        return bean;
+    }
+
+    private ResultBean paramError() {
+        return failed("参数错误");
+    }
+
+    private ResultBean failed(String message) {
+        ResultBean bean = new ResultBean();
+        bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+        bean.getRtnBean().setRtnMsg(message);
+        return bean;
+    }
+}

+ 4 - 2
client-app/src/main/resources/application-dev.yml

@@ -69,9 +69,11 @@ spring:
         max-idle: 50
 
 wxparam:
-  # 寰俊鍏紬鍙?  appid: wx94a610f21d00b833
+  # 寰俊鍏紬鍙?
+  appid: wx94a610f21d00b833
   appsecret: f0557dc27d56b4f38a7371a0bb8eb62c
-  # 閬囪鏈潵鏁板瓧绉戞妧灏忕▼搴?  appidmic: wx287bb4bfc3973523
+  # 閬囪鏈潵鏁板瓧绉戞妧灏忕▼搴?
+  appidmic: wx287bb4bfc3973523
   appsecretmic: 170a895d0a9f7a78a0ef557eab38e122
   # 闂ㄥ簵绔皬绋嬪簭
   appidstore: wxcd1045afaa69dfe6

+ 38 - 1
common/src/main/java/com/sapit/common/config/WebAppConfiguration.java

@@ -1,7 +1,13 @@
 package com.sapit.common.config;
 
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.format.FormatterRegistry;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
@@ -17,8 +23,39 @@ public class WebAppConfiguration implements WebMvcConfigurer {
             .excludePathPatterns("/**/login").excludePathPatterns("/**/logout");
     }
 
+    @Override
+    public void addFormatters(FormatterRegistry registry) {
+        registry.addConverter(new Converter<Date, Timestamp>() {
+            @Override
+            public Timestamp convert(Date source) {
+                return new Timestamp(source.getTime());
+            }
+        });
+        registry.addConverter(new Converter<String, Date>() {
+            private final String[] patterns = {
+                "yyyy/MM/dd'T'HH:mm:ss.SSSZ",
+                "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
+                "yyyy/MM/dd HH:mm:ss",
+                "yyyy-MM-dd HH:mm:ss",
+                "yyyy/MM/dd",
+                "yyyy-MM-dd"
+            };
+
+            @Override
+            public Date convert(String source) {
+                for (String pattern : patterns) {
+                    try {
+                        return new SimpleDateFormat(pattern).parse(source);
+                    } catch (Exception ignored) {
+                    }
+                }
+                throw new IllegalArgumentException("Cannot parse date: " + source);
+            }
+        });
+    }
+
     @Bean
     public void systemParaInit() {
         
     }
-}
+}

+ 255 - 0
common/src/main/java/com/sapit/common/entity/inspect/BuAssessResult.java

@@ -0,0 +1,255 @@
+package com.sapit.common.entity.inspect;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.util.Date;
+
+/**
+ * I01-A08
+ * 门店考核结果实体。
+ */
+@Entity
+@Table(name = "bu_assess_result")
+public class BuAssessResult implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private Long assessResultId;
+    private Long storeId;
+    private String storeName;
+    private Long storeBelongto;
+    private Long province;
+    private Long city;
+    private Integer periodType;
+    private String periodCode;
+    private Date periodStartDate;
+    private Date periodEndDate;
+    private BigDecimal inspectScore;
+    private BigDecimal autoScore;
+    private BigDecimal totalScore;
+    private Integer assessLevel;
+    private Integer rankNo;
+    private Integer taskCount;
+    private Integer finishTaskCount;
+    private Integer rectCount;
+    private Integer overtimeRectCount;
+    private Timestamp summaryDate;
+    private Integer status;
+    private Timestamp createDate;
+    private Timestamp updateDate;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "ASSESS_RESULT_ID")
+    public Long getAssessResultId() {
+        return assessResultId;
+    }
+
+    public void setAssessResultId(Long assessResultId) {
+        this.assessResultId = assessResultId;
+    }
+
+    @Column(name = "STORE_ID")
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(Long storeId) {
+        this.storeId = storeId;
+    }
+
+    @Column(name = "STORE_NAME")
+    public String getStoreName() {
+        return storeName;
+    }
+
+    public void setStoreName(String storeName) {
+        this.storeName = storeName;
+    }
+
+    @Column(name = "STORE_BELONGTO")
+    public Long getStoreBelongto() {
+        return storeBelongto;
+    }
+
+    public void setStoreBelongto(Long storeBelongto) {
+        this.storeBelongto = storeBelongto;
+    }
+
+    @Column(name = "PROVINCE")
+    public Long getProvince() {
+        return province;
+    }
+
+    public void setProvince(Long province) {
+        this.province = province;
+    }
+
+    @Column(name = "CITY")
+    public Long getCity() {
+        return city;
+    }
+
+    public void setCity(Long city) {
+        this.city = city;
+    }
+
+    @Column(name = "PERIOD_TYPE")
+    public Integer getPeriodType() {
+        return periodType;
+    }
+
+    public void setPeriodType(Integer periodType) {
+        this.periodType = periodType;
+    }
+
+    @Column(name = "PERIOD_CODE")
+    public String getPeriodCode() {
+        return periodCode;
+    }
+
+    public void setPeriodCode(String periodCode) {
+        this.periodCode = periodCode;
+    }
+
+    @Column(name = "PERIOD_START_DATE")
+    public Date getPeriodStartDate() {
+        return periodStartDate;
+    }
+
+    public void setPeriodStartDate(Date periodStartDate) {
+        this.periodStartDate = periodStartDate;
+    }
+
+    @Column(name = "PERIOD_END_DATE")
+    public Date getPeriodEndDate() {
+        return periodEndDate;
+    }
+
+    public void setPeriodEndDate(Date periodEndDate) {
+        this.periodEndDate = periodEndDate;
+    }
+
+    @Column(name = "INSPECT_SCORE")
+    public BigDecimal getInspectScore() {
+        return inspectScore;
+    }
+
+    public void setInspectScore(BigDecimal inspectScore) {
+        this.inspectScore = inspectScore;
+    }
+
+    @Column(name = "AUTO_SCORE")
+    public BigDecimal getAutoScore() {
+        return autoScore;
+    }
+
+    public void setAutoScore(BigDecimal autoScore) {
+        this.autoScore = autoScore;
+    }
+
+    @Column(name = "TOTAL_SCORE")
+    public BigDecimal getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(BigDecimal totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    @Column(name = "ASSESS_LEVEL")
+    public Integer getAssessLevel() {
+        return assessLevel;
+    }
+
+    public void setAssessLevel(Integer assessLevel) {
+        this.assessLevel = assessLevel;
+    }
+
+    @Column(name = "RANK_NO")
+    public Integer getRankNo() {
+        return rankNo;
+    }
+
+    public void setRankNo(Integer rankNo) {
+        this.rankNo = rankNo;
+    }
+
+    @Column(name = "TASK_COUNT")
+    public Integer getTaskCount() {
+        return taskCount;
+    }
+
+    public void setTaskCount(Integer taskCount) {
+        this.taskCount = taskCount;
+    }
+
+    @Column(name = "FINISH_TASK_COUNT")
+    public Integer getFinishTaskCount() {
+        return finishTaskCount;
+    }
+
+    public void setFinishTaskCount(Integer finishTaskCount) {
+        this.finishTaskCount = finishTaskCount;
+    }
+
+    @Column(name = "RECT_COUNT")
+    public Integer getRectCount() {
+        return rectCount;
+    }
+
+    public void setRectCount(Integer rectCount) {
+        this.rectCount = rectCount;
+    }
+
+    @Column(name = "OVERTIME_RECT_COUNT")
+    public Integer getOvertimeRectCount() {
+        return overtimeRectCount;
+    }
+
+    public void setOvertimeRectCount(Integer overtimeRectCount) {
+        this.overtimeRectCount = overtimeRectCount;
+    }
+
+    @Column(name = "SUMMARY_DATE")
+    public Timestamp getSummaryDate() {
+        return summaryDate;
+    }
+
+    public void setSummaryDate(Timestamp summaryDate) {
+        this.summaryDate = summaryDate;
+    }
+
+    @Column(name = "STATUS")
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    @Column(name = "CREATE_DATE")
+    public Timestamp getCreateDate() {
+        return createDate;
+    }
+
+    public void setCreateDate(Timestamp createDate) {
+        this.createDate = createDate;
+    }
+
+    @Column(name = "UPDATE_DATE")
+    public Timestamp getUpdateDate() {
+        return updateDate;
+    }
+
+    public void setUpdateDate(Timestamp updateDate) {
+        this.updateDate = updateDate;
+    }
+}

+ 178 - 0
common/src/main/java/com/sapit/common/entity/inspect/BuInspectIndicator.java

@@ -0,0 +1,178 @@
+package com.sapit.common.entity.inspect;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+
+/**
+ * I01-A01
+ * 巡检指标实体。
+ */
+@Entity
+@Table(name = "bu_inspect_indicator")
+public class BuInspectIndicator implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 指标ID */
+    private Long indicatorId;
+    /** 指标编码 */
+    private String indicatorCode;
+    /** 指标名称 */
+    private String indicatorName;
+    /** 指标类型:1自动采集,2人工巡检 */
+    private Integer indicatorType;
+    /** 自动采集数据来源 */
+    private String dataSource;
+    /** 满分 */
+    private BigDecimal maxScore;
+    /** 及格分 */
+    private BigDecimal passScore;
+    /** 权重百分比 */
+    private BigDecimal weight;
+    /** 评分规则 */
+    private String scoreRule;
+    /** 排序 */
+    private Integer sort;
+    /** 状态:1启用,0停用 */
+    private Integer status;
+    /** 备注 */
+    private String rmk;
+    /** 创建时间 */
+    private Timestamp createDate;
+    /** 更新时间 */
+    private Timestamp updateDate;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "INDICATOR_ID")
+    public Long getIndicatorId() {
+        return indicatorId;
+    }
+
+    public void setIndicatorId(Long indicatorId) {
+        this.indicatorId = indicatorId;
+    }
+
+    @Column(name = "INDICATOR_CODE")
+    public String getIndicatorCode() {
+        return indicatorCode;
+    }
+
+    public void setIndicatorCode(String indicatorCode) {
+        this.indicatorCode = indicatorCode;
+    }
+
+    @Column(name = "INDICATOR_NAME")
+    public String getIndicatorName() {
+        return indicatorName;
+    }
+
+    public void setIndicatorName(String indicatorName) {
+        this.indicatorName = indicatorName;
+    }
+
+    @Column(name = "INDICATOR_TYPE")
+    public Integer getIndicatorType() {
+        return indicatorType;
+    }
+
+    public void setIndicatorType(Integer indicatorType) {
+        this.indicatorType = indicatorType;
+    }
+
+    @Column(name = "DATA_SOURCE")
+    public String getDataSource() {
+        return dataSource;
+    }
+
+    public void setDataSource(String dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    @Column(name = "MAX_SCORE")
+    public BigDecimal getMaxScore() {
+        return maxScore;
+    }
+
+    public void setMaxScore(BigDecimal maxScore) {
+        this.maxScore = maxScore;
+    }
+
+    @Column(name = "PASS_SCORE")
+    public BigDecimal getPassScore() {
+        return passScore;
+    }
+
+    public void setPassScore(BigDecimal passScore) {
+        this.passScore = passScore;
+    }
+
+    @Column(name = "WEIGHT")
+    public BigDecimal getWeight() {
+        return weight;
+    }
+
+    public void setWeight(BigDecimal weight) {
+        this.weight = weight;
+    }
+
+    @Column(name = "SCORE_RULE")
+    public String getScoreRule() {
+        return scoreRule;
+    }
+
+    public void setScoreRule(String scoreRule) {
+        this.scoreRule = scoreRule;
+    }
+
+    @Column(name = "SORT")
+    public Integer getSort() {
+        return sort;
+    }
+
+    public void setSort(Integer sort) {
+        this.sort = sort;
+    }
+
+    @Column(name = "STATUS")
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    @Column(name = "RMK")
+    public String getRmk() {
+        return rmk;
+    }
+
+    public void setRmk(String rmk) {
+        this.rmk = rmk;
+    }
+
+    @Column(name = "CREATE_DATE")
+    public Timestamp getCreateDate() {
+        return createDate;
+    }
+
+    public void setCreateDate(Timestamp createDate) {
+        this.createDate = createDate;
+    }
+
+    @Column(name = "UPDATE_DATE")
+    public Timestamp getUpdateDate() {
+        return updateDate;
+    }
+
+    public void setUpdateDate(Timestamp updateDate) {
+        this.updateDate = updateDate;
+    }
+}

+ 195 - 0
common/src/main/java/com/sapit/common/entity/inspect/BuInspectPlan.java

@@ -0,0 +1,195 @@
+package com.sapit.common.entity.inspect;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.util.Date;
+
+/**
+ * I01-A02
+ * 巡检计划实体。
+ */
+@Entity
+@Table(name = "bu_inspect_plan")
+public class BuInspectPlan implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private Long planId;
+    private String planName;
+    private Integer planType;
+    private Integer cycleType;
+    private Date startDate;
+    private Date endDate;
+    private Date nextExecuteDate;
+    private Long inspectorId;
+    private String inspectorName;
+    private Integer autoCollectFlag;
+    private Integer storeCount;
+    private Integer status;
+    private Timestamp publishDate;
+    private Timestamp finishDate;
+    private String rmk;
+    private Timestamp createDate;
+    private Timestamp updateDate;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "PLAN_ID")
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    @Column(name = "PLAN_NAME")
+    public String getPlanName() {
+        return planName;
+    }
+
+    public void setPlanName(String planName) {
+        this.planName = planName;
+    }
+
+    @Column(name = "PLAN_TYPE")
+    public Integer getPlanType() {
+        return planType;
+    }
+
+    public void setPlanType(Integer planType) {
+        this.planType = planType;
+    }
+
+    @Column(name = "CYCLE_TYPE")
+    public Integer getCycleType() {
+        return cycleType;
+    }
+
+    public void setCycleType(Integer cycleType) {
+        this.cycleType = cycleType;
+    }
+
+    @Column(name = "START_DATE")
+    public Date getStartDate() {
+        return startDate;
+    }
+
+    public void setStartDate(Date startDate) {
+        this.startDate = startDate;
+    }
+
+    @Column(name = "END_DATE")
+    public Date getEndDate() {
+        return endDate;
+    }
+
+    public void setEndDate(Date endDate) {
+        this.endDate = endDate;
+    }
+
+    @Column(name = "NEXT_EXECUTE_DATE")
+    public Date getNextExecuteDate() {
+        return nextExecuteDate;
+    }
+
+    public void setNextExecuteDate(Date nextExecuteDate) {
+        this.nextExecuteDate = nextExecuteDate;
+    }
+
+    @Column(name = "INSPECTOR_ID")
+    public Long getInspectorId() {
+        return inspectorId;
+    }
+
+    public void setInspectorId(Long inspectorId) {
+        this.inspectorId = inspectorId;
+    }
+
+    @Column(name = "INSPECTOR_NAME")
+    public String getInspectorName() {
+        return inspectorName;
+    }
+
+    public void setInspectorName(String inspectorName) {
+        this.inspectorName = inspectorName;
+    }
+
+    @Column(name = "AUTO_COLLECT_FLAG")
+    public Integer getAutoCollectFlag() {
+        return autoCollectFlag;
+    }
+
+    public void setAutoCollectFlag(Integer autoCollectFlag) {
+        this.autoCollectFlag = autoCollectFlag;
+    }
+
+    @Column(name = "STORE_COUNT")
+    public Integer getStoreCount() {
+        return storeCount;
+    }
+
+    public void setStoreCount(Integer storeCount) {
+        this.storeCount = storeCount;
+    }
+
+    @Column(name = "STATUS")
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    @Column(name = "PUBLISH_DATE")
+    public Timestamp getPublishDate() {
+        return publishDate;
+    }
+
+    public void setPublishDate(Timestamp publishDate) {
+        this.publishDate = publishDate;
+    }
+
+    @Column(name = "FINISH_DATE")
+    public Timestamp getFinishDate() {
+        return finishDate;
+    }
+
+    public void setFinishDate(Timestamp finishDate) {
+        this.finishDate = finishDate;
+    }
+
+    @Column(name = "RMK")
+    public String getRmk() {
+        return rmk;
+    }
+
+    public void setRmk(String rmk) {
+        this.rmk = rmk;
+    }
+
+    @Column(name = "CREATE_DATE")
+    public Timestamp getCreateDate() {
+        return createDate;
+    }
+
+    public void setCreateDate(Timestamp createDate) {
+        this.createDate = createDate;
+    }
+
+    @Column(name = "UPDATE_DATE")
+    public Timestamp getUpdateDate() {
+        return updateDate;
+    }
+
+    public void setUpdateDate(Timestamp updateDate) {
+        this.updateDate = updateDate;
+    }
+}

+ 154 - 0
common/src/main/java/com/sapit/common/entity/inspect/BuInspectPlanIndicator.java

@@ -0,0 +1,154 @@
+package com.sapit.common.entity.inspect;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+
+/**
+ * I01-A03
+ * 巡检计划与指标关联实体。
+ */
+@Entity
+@Table(name = "bu_inspect_plan_indicator")
+public class BuInspectPlanIndicator implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private Long planIndicatorId;
+    private Long planId;
+    private Long indicatorId;
+    private String indicatorName;
+    private Integer indicatorType;
+    private BigDecimal maxScore;
+    private BigDecimal passScore;
+    private BigDecimal weight;
+    private String scoreRule;
+    private Integer sort;
+    private Integer status;
+    private Timestamp createDate;
+    private Timestamp updateDate;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "PLAN_INDICATOR_ID")
+    public Long getPlanIndicatorId() {
+        return planIndicatorId;
+    }
+
+    public void setPlanIndicatorId(Long planIndicatorId) {
+        this.planIndicatorId = planIndicatorId;
+    }
+
+    @Column(name = "PLAN_ID")
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    @Column(name = "INDICATOR_ID")
+    public Long getIndicatorId() {
+        return indicatorId;
+    }
+
+    public void setIndicatorId(Long indicatorId) {
+        this.indicatorId = indicatorId;
+    }
+
+    @Column(name = "INDICATOR_NAME")
+    public String getIndicatorName() {
+        return indicatorName;
+    }
+
+    public void setIndicatorName(String indicatorName) {
+        this.indicatorName = indicatorName;
+    }
+
+    @Column(name = "INDICATOR_TYPE")
+    public Integer getIndicatorType() {
+        return indicatorType;
+    }
+
+    public void setIndicatorType(Integer indicatorType) {
+        this.indicatorType = indicatorType;
+    }
+
+    @Column(name = "MAX_SCORE")
+    public BigDecimal getMaxScore() {
+        return maxScore;
+    }
+
+    public void setMaxScore(BigDecimal maxScore) {
+        this.maxScore = maxScore;
+    }
+
+    @Column(name = "PASS_SCORE")
+    public BigDecimal getPassScore() {
+        return passScore;
+    }
+
+    public void setPassScore(BigDecimal passScore) {
+        this.passScore = passScore;
+    }
+
+    @Column(name = "WEIGHT")
+    public BigDecimal getWeight() {
+        return weight;
+    }
+
+    public void setWeight(BigDecimal weight) {
+        this.weight = weight;
+    }
+
+    @Column(name = "SCORE_RULE")
+    public String getScoreRule() {
+        return scoreRule;
+    }
+
+    public void setScoreRule(String scoreRule) {
+        this.scoreRule = scoreRule;
+    }
+
+    @Column(name = "SORT")
+    public Integer getSort() {
+        return sort;
+    }
+
+    public void setSort(Integer sort) {
+        this.sort = sort;
+    }
+
+    @Column(name = "STATUS")
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    @Column(name = "CREATE_DATE")
+    public Timestamp getCreateDate() {
+        return createDate;
+    }
+
+    public void setCreateDate(Timestamp createDate) {
+        this.createDate = createDate;
+    }
+
+    @Column(name = "UPDATE_DATE")
+    public Timestamp getUpdateDate() {
+        return updateDate;
+    }
+
+    public void setUpdateDate(Timestamp updateDate) {
+        this.updateDate = updateDate;
+    }
+}

+ 116 - 0
common/src/main/java/com/sapit/common/entity/inspect/BuInspectPlanStore.java

@@ -0,0 +1,116 @@
+package com.sapit.common.entity.inspect;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+import org.springframework.format.annotation.DateTimeFormat;
+
+/**
+ * I01-A04
+ * 巡检计划门店关联实体。
+ */
+@Entity
+@Table(name = "bu_inspect_plan_store")
+public class BuInspectPlanStore implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private Long planStoreId;
+    private Long planId;
+    private Long storeId;
+    private String storeName;
+    private Long storeBelongto;
+    private Long province;
+    private Long city;
+    private Integer status;
+    private Timestamp createDate;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "PLAN_STORE_ID")
+    public Long getPlanStoreId() {
+        return planStoreId;
+    }
+
+    public void setPlanStoreId(Long planStoreId) {
+        this.planStoreId = planStoreId;
+    }
+
+    @Column(name = "PLAN_ID")
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    @Column(name = "STORE_ID")
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(Long storeId) {
+        this.storeId = storeId;
+    }
+
+    @Column(name = "STORE_NAME")
+    public String getStoreName() {
+        return storeName;
+    }
+
+    public void setStoreName(String storeName) {
+        this.storeName = storeName;
+    }
+
+    @Column(name = "STORE_BELONGTO")
+    public Long getStoreBelongto() {
+        return storeBelongto;
+    }
+
+    public void setStoreBelongto(Long storeBelongto) {
+        this.storeBelongto = storeBelongto;
+    }
+
+    @Column(name = "PROVINCE")
+    public Long getProvince() {
+        return province;
+    }
+
+    public void setProvince(Long province) {
+        this.province = province;
+    }
+
+    @Column(name = "CITY")
+    public Long getCity() {
+        return city;
+    }
+
+    public void setCity(Long city) {
+        this.city = city;
+    }
+
+    @Column(name = "STATUS")
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
+    @Column(name = "CREATE_DATE")
+    public Timestamp getCreateDate() {
+        return createDate;
+    }
+
+    public void setCreateDate(Timestamp createDate) {
+        this.createDate = createDate;
+    }
+}

+ 225 - 0
common/src/main/java/com/sapit/common/entity/inspect/BuInspectTask.java

@@ -0,0 +1,225 @@
+package com.sapit.common.entity.inspect;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.util.Date;
+
+/**
+ * I01-A05
+ * 巡检任务实体。
+ */
+@Entity
+@Table(name = "bu_inspect_task")
+public class BuInspectTask implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private Long taskId;
+    private Long planId;
+    private String planName;
+    private Long storeId;
+    private String storeName;
+    private Date taskDate;
+    private Long inspectorId;
+    private String inspectorName;
+    private BigDecimal totalScore;
+    private BigDecimal maxScore;
+    private BigDecimal passScore;
+    private Integer passFlag;
+    private Integer unpassItemCount;
+    private Timestamp startDate;
+    private Timestamp submitDate;
+    private Timestamp cancelDate;
+    private Integer status;
+    private String rmk;
+    private Timestamp createDate;
+    private Timestamp updateDate;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "TASK_ID")
+    public Long getTaskId() {
+        return taskId;
+    }
+
+    public void setTaskId(Long taskId) {
+        this.taskId = taskId;
+    }
+
+    @Column(name = "PLAN_ID")
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    @Column(name = "PLAN_NAME")
+    public String getPlanName() {
+        return planName;
+    }
+
+    public void setPlanName(String planName) {
+        this.planName = planName;
+    }
+
+    @Column(name = "STORE_ID")
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(Long storeId) {
+        this.storeId = storeId;
+    }
+
+    @Column(name = "STORE_NAME")
+    public String getStoreName() {
+        return storeName;
+    }
+
+    public void setStoreName(String storeName) {
+        this.storeName = storeName;
+    }
+
+    @Column(name = "TASK_DATE")
+    public Date getTaskDate() {
+        return taskDate;
+    }
+
+    public void setTaskDate(Date taskDate) {
+        this.taskDate = taskDate;
+    }
+
+    @Column(name = "INSPECTOR_ID")
+    public Long getInspectorId() {
+        return inspectorId;
+    }
+
+    public void setInspectorId(Long inspectorId) {
+        this.inspectorId = inspectorId;
+    }
+
+    @Column(name = "INSPECTOR_NAME")
+    public String getInspectorName() {
+        return inspectorName;
+    }
+
+    public void setInspectorName(String inspectorName) {
+        this.inspectorName = inspectorName;
+    }
+
+    @Column(name = "TOTAL_SCORE")
+    public BigDecimal getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(BigDecimal totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    @Column(name = "MAX_SCORE")
+    public BigDecimal getMaxScore() {
+        return maxScore;
+    }
+
+    public void setMaxScore(BigDecimal maxScore) {
+        this.maxScore = maxScore;
+    }
+
+    @Column(name = "PASS_SCORE")
+    public BigDecimal getPassScore() {
+        return passScore;
+    }
+
+    public void setPassScore(BigDecimal passScore) {
+        this.passScore = passScore;
+    }
+
+    @Column(name = "PASS_FLAG")
+    public Integer getPassFlag() {
+        return passFlag;
+    }
+
+    public void setPassFlag(Integer passFlag) {
+        this.passFlag = passFlag;
+    }
+
+    @Column(name = "UNPASS_ITEM_COUNT")
+    public Integer getUnpassItemCount() {
+        return unpassItemCount;
+    }
+
+    public void setUnpassItemCount(Integer unpassItemCount) {
+        this.unpassItemCount = unpassItemCount;
+    }
+
+    @Column(name = "START_DATE")
+    public Timestamp getStartDate() {
+        return startDate;
+    }
+
+    public void setStartDate(Timestamp startDate) {
+        this.startDate = startDate;
+    }
+
+    @Column(name = "SUBMIT_DATE")
+    public Timestamp getSubmitDate() {
+        return submitDate;
+    }
+
+    public void setSubmitDate(Timestamp submitDate) {
+        this.submitDate = submitDate;
+    }
+
+    @Column(name = "CANCEL_DATE")
+    public Timestamp getCancelDate() {
+        return cancelDate;
+    }
+
+    public void setCancelDate(Timestamp cancelDate) {
+        this.cancelDate = cancelDate;
+    }
+
+    @Column(name = "STATUS")
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    @Column(name = "RMK")
+    public String getRmk() {
+        return rmk;
+    }
+
+    public void setRmk(String rmk) {
+        this.rmk = rmk;
+    }
+
+    @Column(name = "CREATE_DATE")
+    public Timestamp getCreateDate() {
+        return createDate;
+    }
+
+    public void setCreateDate(Timestamp createDate) {
+        this.createDate = createDate;
+    }
+
+    @Column(name = "UPDATE_DATE")
+    public Timestamp getUpdateDate() {
+        return updateDate;
+    }
+
+    public void setUpdateDate(Timestamp updateDate) {
+        this.updateDate = updateDate;
+    }
+}

+ 274 - 0
common/src/main/java/com/sapit/common/entity/inspect/BuInspectTaskItem.java

@@ -0,0 +1,274 @@
+package com.sapit.common.entity.inspect;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+
+/**
+ * I01-A06
+ * 巡检任务评分明细实体。
+ */
+@Entity
+@Table(name = "bu_inspect_task_item")
+public class BuInspectTaskItem implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private Long taskItemId;
+    private Long taskId;
+    private Long planId;
+    private Long storeId;
+    private Long planIndicatorId;
+    private Long indicatorId;
+    private Integer scoreLevel;
+    private String indicatorCode;
+    private String indicatorName;
+    private Integer indicatorType;
+    private String dataSource;
+    private BigDecimal weight;
+    private BigDecimal maxScore;
+    private BigDecimal passScore;
+    private BigDecimal rawValue;
+    private String rawValueText;
+    private BigDecimal score;
+    private BigDecimal weightedScore;
+    private Integer passFlag;
+    private String pictureUrls;
+    private String rmk;
+    private Integer sort;
+    private Integer status;
+    private Timestamp createDate;
+    private Timestamp updateDate;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "TASK_ITEM_ID")
+    public Long getTaskItemId() {
+        return taskItemId;
+    }
+
+    public void setTaskItemId(Long taskItemId) {
+        this.taskItemId = taskItemId;
+    }
+
+    @Column(name = "TASK_ID")
+    public Long getTaskId() {
+        return taskId;
+    }
+
+    public void setTaskId(Long taskId) {
+        this.taskId = taskId;
+    }
+
+    @Column(name = "PLAN_ID")
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    @Column(name = "STORE_ID")
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(Long storeId) {
+        this.storeId = storeId;
+    }
+
+    @Column(name = "PLAN_INDICATOR_ID")
+    public Long getPlanIndicatorId() {
+        return planIndicatorId;
+    }
+
+    public void setPlanIndicatorId(Long planIndicatorId) {
+        this.planIndicatorId = planIndicatorId;
+    }
+
+    @Column(name = "INDICATOR_ID")
+    public Long getIndicatorId() {
+        return indicatorId;
+    }
+
+    public void setIndicatorId(Long indicatorId) {
+        this.indicatorId = indicatorId;
+    }
+
+    @Column(name = "SCORE_LEVEL")
+    public Integer getScoreLevel() {
+        return scoreLevel;
+    }
+
+    public void setScoreLevel(Integer scoreLevel) {
+        this.scoreLevel = scoreLevel;
+    }
+
+    @Column(name = "INDICATOR_CODE")
+    public String getIndicatorCode() {
+        return indicatorCode;
+    }
+
+    public void setIndicatorCode(String indicatorCode) {
+        this.indicatorCode = indicatorCode;
+    }
+
+    @Column(name = "INDICATOR_NAME")
+    public String getIndicatorName() {
+        return indicatorName;
+    }
+
+    public void setIndicatorName(String indicatorName) {
+        this.indicatorName = indicatorName;
+    }
+
+    @Column(name = "INDICATOR_TYPE")
+    public Integer getIndicatorType() {
+        return indicatorType;
+    }
+
+    public void setIndicatorType(Integer indicatorType) {
+        this.indicatorType = indicatorType;
+    }
+
+    @Column(name = "DATA_SOURCE")
+    public String getDataSource() {
+        return dataSource;
+    }
+
+    public void setDataSource(String dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    @Column(name = "WEIGHT")
+    public BigDecimal getWeight() {
+        return weight;
+    }
+
+    public void setWeight(BigDecimal weight) {
+        this.weight = weight;
+    }
+
+    @Column(name = "MAX_SCORE")
+    public BigDecimal getMaxScore() {
+        return maxScore;
+    }
+
+    public void setMaxScore(BigDecimal maxScore) {
+        this.maxScore = maxScore;
+    }
+
+    @Column(name = "PASS_SCORE")
+    public BigDecimal getPassScore() {
+        return passScore;
+    }
+
+    public void setPassScore(BigDecimal passScore) {
+        this.passScore = passScore;
+    }
+
+    @Column(name = "RAW_VALUE")
+    public BigDecimal getRawValue() {
+        return rawValue;
+    }
+
+    public void setRawValue(BigDecimal rawValue) {
+        this.rawValue = rawValue;
+    }
+
+    @Column(name = "RAW_VALUE_TEXT")
+    public String getRawValueText() {
+        return rawValueText;
+    }
+
+    public void setRawValueText(String rawValueText) {
+        this.rawValueText = rawValueText;
+    }
+
+    @Column(name = "SCORE")
+    public BigDecimal getScore() {
+        return score;
+    }
+
+    public void setScore(BigDecimal score) {
+        this.score = score;
+    }
+
+    @Column(name = "WEIGHTED_SCORE")
+    public BigDecimal getWeightedScore() {
+        return weightedScore;
+    }
+
+    public void setWeightedScore(BigDecimal weightedScore) {
+        this.weightedScore = weightedScore;
+    }
+
+    @Column(name = "PASS_FLAG")
+    public Integer getPassFlag() {
+        return passFlag;
+    }
+
+    public void setPassFlag(Integer passFlag) {
+        this.passFlag = passFlag;
+    }
+
+    @Column(name = "PICTURE_URLS")
+    public String getPictureUrls() {
+        return pictureUrls;
+    }
+
+    public void setPictureUrls(String pictureUrls) {
+        this.pictureUrls = pictureUrls;
+    }
+
+    @Column(name = "RMK")
+    public String getRmk() {
+        return rmk;
+    }
+
+    public void setRmk(String rmk) {
+        this.rmk = rmk;
+    }
+
+    @Column(name = "SORT")
+    public Integer getSort() {
+        return sort;
+    }
+
+    public void setSort(Integer sort) {
+        this.sort = sort;
+    }
+
+    @Column(name = "STATUS")
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    @Column(name = "CREATE_DATE")
+    public Timestamp getCreateDate() {
+        return createDate;
+    }
+
+    public void setCreateDate(Timestamp createDate) {
+        this.createDate = createDate;
+    }
+
+    @Column(name = "UPDATE_DATE")
+    public Timestamp getUpdateDate() {
+        return updateDate;
+    }
+
+    public void setUpdateDate(Timestamp updateDate) {
+        this.updateDate = updateDate;
+    }
+}

+ 324 - 0
common/src/main/java/com/sapit/common/entity/inspect/BuRectification.java

@@ -0,0 +1,324 @@
+package com.sapit.common.entity.inspect;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+
+/**
+ * I01-A07
+ * 整改记录实体。
+ */
+@Entity
+@Table(name = "bu_rectification")
+public class BuRectification implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private Long rectId;
+    private Long taskId;
+    private Long taskItemId;
+    private Long planId;
+    private Long storeId;
+    private String storeName;
+    private Long indicatorId;
+    private String indicatorName;
+    private Integer rectRound;
+    private Long parentRectId;
+    private BigDecimal inspectScore;
+    private BigDecimal passScore;
+    private String noticeContent;
+    private Timestamp deadlineDate;
+    private Timestamp startDate;
+    private String feedbackContent;
+    private String feedbackPictures;
+    private Timestamp feedbackDate;
+    private BigDecimal recheckScore;
+    private Integer recheckResult;
+    private String recheckPictures;
+    private String recheckRmk;
+    private Long recheckUserId;
+    private String recheckUserName;
+    private Timestamp recheckDate;
+    private Integer urgeCount;
+    private Timestamp lastUrgeDate;
+    private Integer status;
+    private Timestamp createDate;
+    private Timestamp updateDate;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "RECT_ID")
+    public Long getRectId() {
+        return rectId;
+    }
+
+    public void setRectId(Long rectId) {
+        this.rectId = rectId;
+    }
+
+    @Column(name = "TASK_ID")
+    public Long getTaskId() {
+        return taskId;
+    }
+
+    public void setTaskId(Long taskId) {
+        this.taskId = taskId;
+    }
+
+    @Column(name = "TASK_ITEM_ID")
+    public Long getTaskItemId() {
+        return taskItemId;
+    }
+
+    public void setTaskItemId(Long taskItemId) {
+        this.taskItemId = taskItemId;
+    }
+
+    @Column(name = "PLAN_ID")
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    @Column(name = "STORE_ID")
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(Long storeId) {
+        this.storeId = storeId;
+    }
+
+    @Column(name = "STORE_NAME")
+    public String getStoreName() {
+        return storeName;
+    }
+
+    public void setStoreName(String storeName) {
+        this.storeName = storeName;
+    }
+
+    @Column(name = "INDICATOR_ID")
+    public Long getIndicatorId() {
+        return indicatorId;
+    }
+
+    public void setIndicatorId(Long indicatorId) {
+        this.indicatorId = indicatorId;
+    }
+
+    @Column(name = "INDICATOR_NAME")
+    public String getIndicatorName() {
+        return indicatorName;
+    }
+
+    public void setIndicatorName(String indicatorName) {
+        this.indicatorName = indicatorName;
+    }
+
+    @Column(name = "RECT_ROUND")
+    public Integer getRectRound() {
+        return rectRound;
+    }
+
+    public void setRectRound(Integer rectRound) {
+        this.rectRound = rectRound;
+    }
+
+    @Column(name = "PARENT_RECT_ID")
+    public Long getParentRectId() {
+        return parentRectId;
+    }
+
+    public void setParentRectId(Long parentRectId) {
+        this.parentRectId = parentRectId;
+    }
+
+    @Column(name = "INSPECT_SCORE")
+    public BigDecimal getInspectScore() {
+        return inspectScore;
+    }
+
+    public void setInspectScore(BigDecimal inspectScore) {
+        this.inspectScore = inspectScore;
+    }
+
+    @Column(name = "PASS_SCORE")
+    public BigDecimal getPassScore() {
+        return passScore;
+    }
+
+    public void setPassScore(BigDecimal passScore) {
+        this.passScore = passScore;
+    }
+
+    @Column(name = "NOTICE_CONTENT")
+    public String getNoticeContent() {
+        return noticeContent;
+    }
+
+    public void setNoticeContent(String noticeContent) {
+        this.noticeContent = noticeContent;
+    }
+
+    @Column(name = "DEADLINE_DATE")
+    public Timestamp getDeadlineDate() {
+        return deadlineDate;
+    }
+
+    public void setDeadlineDate(Timestamp deadlineDate) {
+        this.deadlineDate = deadlineDate;
+    }
+
+    @Column(name = "START_DATE")
+    public Timestamp getStartDate() {
+        return startDate;
+    }
+
+    public void setStartDate(Timestamp startDate) {
+        this.startDate = startDate;
+    }
+
+    @Column(name = "FEEDBACK_CONTENT")
+    public String getFeedbackContent() {
+        return feedbackContent;
+    }
+
+    public void setFeedbackContent(String feedbackContent) {
+        this.feedbackContent = feedbackContent;
+    }
+
+    @Column(name = "FEEDBACK_PICTURES")
+    public String getFeedbackPictures() {
+        return feedbackPictures;
+    }
+
+    public void setFeedbackPictures(String feedbackPictures) {
+        this.feedbackPictures = feedbackPictures;
+    }
+
+    @Column(name = "FEEDBACK_DATE")
+    public Timestamp getFeedbackDate() {
+        return feedbackDate;
+    }
+
+    public void setFeedbackDate(Timestamp feedbackDate) {
+        this.feedbackDate = feedbackDate;
+    }
+
+    @Column(name = "RECHECK_SCORE")
+    public BigDecimal getRecheckScore() {
+        return recheckScore;
+    }
+
+    public void setRecheckScore(BigDecimal recheckScore) {
+        this.recheckScore = recheckScore;
+    }
+
+    @Column(name = "RECHECK_RESULT")
+    public Integer getRecheckResult() {
+        return recheckResult;
+    }
+
+    public void setRecheckResult(Integer recheckResult) {
+        this.recheckResult = recheckResult;
+    }
+
+    @Column(name = "RECHECK_PICTURES")
+    public String getRecheckPictures() {
+        return recheckPictures;
+    }
+
+    public void setRecheckPictures(String recheckPictures) {
+        this.recheckPictures = recheckPictures;
+    }
+
+    @Column(name = "RECHECK_RMK")
+    public String getRecheckRmk() {
+        return recheckRmk;
+    }
+
+    public void setRecheckRmk(String recheckRmk) {
+        this.recheckRmk = recheckRmk;
+    }
+
+    @Column(name = "RECHECK_USER_ID")
+    public Long getRecheckUserId() {
+        return recheckUserId;
+    }
+
+    public void setRecheckUserId(Long recheckUserId) {
+        this.recheckUserId = recheckUserId;
+    }
+
+    @Column(name = "RECHECK_USER_NAME")
+    public String getRecheckUserName() {
+        return recheckUserName;
+    }
+
+    public void setRecheckUserName(String recheckUserName) {
+        this.recheckUserName = recheckUserName;
+    }
+
+    @Column(name = "RECHECK_DATE")
+    public Timestamp getRecheckDate() {
+        return recheckDate;
+    }
+
+    public void setRecheckDate(Timestamp recheckDate) {
+        this.recheckDate = recheckDate;
+    }
+
+    @Column(name = "URGE_COUNT")
+    public Integer getUrgeCount() {
+        return urgeCount;
+    }
+
+    public void setUrgeCount(Integer urgeCount) {
+        this.urgeCount = urgeCount;
+    }
+
+    @Column(name = "LAST_URGE_DATE")
+    public Timestamp getLastUrgeDate() {
+        return lastUrgeDate;
+    }
+
+    public void setLastUrgeDate(Timestamp lastUrgeDate) {
+        this.lastUrgeDate = lastUrgeDate;
+    }
+
+    @Column(name = "STATUS")
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    @Column(name = "CREATE_DATE")
+    public Timestamp getCreateDate() {
+        return createDate;
+    }
+
+    public void setCreateDate(Timestamp createDate) {
+        this.createDate = createDate;
+    }
+
+    @Column(name = "UPDATE_DATE")
+    public Timestamp getUpdateDate() {
+        return updateDate;
+    }
+
+    public void setUpdateDate(Timestamp updateDate) {
+        this.updateDate = updateDate;
+    }
+}

+ 8 - 0
common/src/main/java/com/sapit/common/framwork/BaseDao.java

@@ -159,6 +159,14 @@ public class BaseDao<T> {
     @Transactional
     public boolean save(Object entity) {
         try {
+            try {
+                java.lang.reflect.Method getCreateDate = entity.getClass().getMethod("getCreateDate");
+                java.lang.reflect.Method setCreateDate = entity.getClass().getMethod("setCreateDate", java.sql.Timestamp.class);
+                if (getCreateDate.invoke(entity) == null) {
+                    setCreateDate.invoke(entity, new java.sql.Timestamp(System.currentTimeMillis()));
+                }
+            } catch (NoSuchMethodException ignored) {
+            }
             entityManager.persist(entity);
             return true;
         } catch (Exception e) {

+ 4 - 2
common/src/main/java/com/sapit/common/framwork/BaseSearchBean.java

@@ -41,7 +41,9 @@ public class BaseSearchBean {
 									condition.append(" and " + anno.fieldName() + " "+ operCode + "(" + paramv + ")"); 	
 									
 								}else {
-									if (paramv.getClass().getTypeName().equals("java.lang.String")) {
+									if (paramv instanceof java.util.Date) {
+										condition.append(" and " + anno.fieldName() + operCode + "'" + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((java.util.Date) paramv) + "'");
+									} else if (paramv.getClass().getTypeName().equals("java.lang.String")) {
 										condition.append(" and " + anno.fieldName() + operCode + "'" + paramv + "'");
 									} else {
 										condition.append(" and " + anno.fieldName() + operCode + paramv);
@@ -86,4 +88,4 @@ public class BaseSearchBean {
 		} while (clazz != Object.class);
 		return sb.toString();
 	}
-}
+}

+ 134 - 0
common/src/main/java/com/sapit/common/search/inspect/BuAssessResultSearchBean.java

@@ -0,0 +1,134 @@
+package com.sapit.common.search.inspect;
+
+import com.sapit.common.framwork.BaseSearchBean;
+import com.sapit.common.framwork.CompositeSearch;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * I02-A05
+ * 考核结果查询条件。
+ */
+public class BuAssessResultSearchBean extends BaseSearchBean {
+    @CompositeSearch(operCode = "=", fieldName = "storeId")
+    private Long storeId;
+
+    @CompositeSearch(operCode = "=", fieldName = "storeBelongto")
+    private Long storeBelongto;
+
+    @CompositeSearch(operCode = "=", fieldName = "province")
+    private Long province;
+
+    @CompositeSearch(operCode = "=", fieldName = "city")
+    private Long city;
+
+    @CompositeSearch(operCode = "=", fieldName = "periodType")
+    private Integer periodType;
+
+    @CompositeSearch(operCode = "LR%", fieldName = "periodCode")
+    private String periodCode;
+
+    @CompositeSearch(operCode = "=", fieldName = "assessLevel")
+    private Integer assessLevel;
+
+    @CompositeSearch(operCode = ">=", fieldName = "periodStartDate")
+    private Date periodStartDateBegin;
+
+    @CompositeSearch(operCode = "<=", fieldName = "periodStartDate")
+    private Date periodStartDateEnd;
+
+    @CompositeSearch(operCode = ">=", fieldName = "periodEndDate")
+    private Date periodEndDateBegin;
+
+    @CompositeSearch(operCode = "<=", fieldName = "periodEndDate")
+    private Date periodEndDateEnd;
+
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(Long storeId) {
+        this.storeId = storeId;
+    }
+
+    public Long getStoreBelongto() {
+        return storeBelongto;
+    }
+
+    public void setStoreBelongto(Long storeBelongto) {
+        this.storeBelongto = storeBelongto;
+    }
+
+    public Long getProvince() {
+        return province;
+    }
+
+    public void setProvince(Long province) {
+        this.province = province;
+    }
+
+    public Long getCity() {
+        return city;
+    }
+
+    public void setCity(Long city) {
+        this.city = city;
+    }
+
+    public Integer getPeriodType() {
+        return periodType;
+    }
+
+    public void setPeriodType(Integer periodType) {
+        this.periodType = periodType;
+    }
+
+    public String getPeriodCode() {
+        return periodCode;
+    }
+
+    public void setPeriodCode(String periodCode) {
+        this.periodCode = periodCode;
+    }
+
+    public Integer getAssessLevel() {
+        return assessLevel;
+    }
+
+    public void setAssessLevel(Integer assessLevel) {
+        this.assessLevel = assessLevel;
+    }
+
+    public Date getPeriodStartDateBegin() {
+        return periodStartDateBegin;
+    }
+
+    public void setPeriodStartDateBegin(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date periodStartDateBegin) {
+        this.periodStartDateBegin = periodStartDateBegin;
+    }
+
+    public Date getPeriodStartDateEnd() {
+        return periodStartDateEnd;
+    }
+
+    public void setPeriodStartDateEnd(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date periodStartDateEnd) {
+        this.periodStartDateEnd = periodStartDateEnd;
+    }
+
+    public Date getPeriodEndDateBegin() {
+        return periodEndDateBegin;
+    }
+
+    public void setPeriodEndDateBegin(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date periodEndDateBegin) {
+        this.periodEndDateBegin = periodEndDateBegin;
+    }
+
+    public Date getPeriodEndDateEnd() {
+        return periodEndDateEnd;
+    }
+
+    public void setPeriodEndDateEnd(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date periodEndDateEnd) {
+        this.periodEndDateEnd = periodEndDateEnd;
+    }
+}

+ 54 - 0
common/src/main/java/com/sapit/common/search/inspect/BuInspectIndicatorSearchBean.java

@@ -0,0 +1,54 @@
+package com.sapit.common.search.inspect;
+
+import com.sapit.common.framwork.BaseSearchBean;
+import com.sapit.common.framwork.CompositeSearch;
+
+/**
+ * I02-A01
+ * 巡检指标查询条件。
+ */
+public class BuInspectIndicatorSearchBean extends BaseSearchBean {
+    @CompositeSearch(operCode = "LR%", fieldName = "indicatorCode")
+    private String indicatorCode;
+
+    @CompositeSearch(operCode = "LR%", fieldName = "indicatorName")
+    private String indicatorName;
+
+    @CompositeSearch(operCode = "=", fieldName = "indicatorType")
+    private Integer indicatorType;
+
+    @CompositeSearch(operCode = "=", fieldName = "status")
+    private Integer status;
+
+    public String getIndicatorCode() {
+        return indicatorCode;
+    }
+
+    public void setIndicatorCode(String indicatorCode) {
+        this.indicatorCode = indicatorCode;
+    }
+
+    public String getIndicatorName() {
+        return indicatorName;
+    }
+
+    public void setIndicatorName(String indicatorName) {
+        this.indicatorName = indicatorName;
+    }
+
+    public Integer getIndicatorType() {
+        return indicatorType;
+    }
+
+    public void setIndicatorType(Integer indicatorType) {
+        this.indicatorType = indicatorType;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+}

+ 43 - 0
common/src/main/java/com/sapit/common/search/inspect/BuInspectPlanIndicatorSearchBean.java

@@ -0,0 +1,43 @@
+package com.sapit.common.search.inspect;
+
+import com.sapit.common.framwork.BaseSearchBean;
+import com.sapit.common.framwork.CompositeSearch;
+
+/**
+ * I02-A06
+ * 巡检计划指标关联查询条件。
+ */
+public class BuInspectPlanIndicatorSearchBean extends BaseSearchBean {
+    @CompositeSearch(operCode = "=", fieldName = "planId")
+    private Long planId;
+
+    @CompositeSearch(operCode = "=", fieldName = "indicatorId")
+    private Long indicatorId;
+
+    @CompositeSearch(operCode = "=", fieldName = "status")
+    private Integer status;
+
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    public Long getIndicatorId() {
+        return indicatorId;
+    }
+
+    public void setIndicatorId(Long indicatorId) {
+        this.indicatorId = indicatorId;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+}

+ 112 - 0
common/src/main/java/com/sapit/common/search/inspect/BuInspectPlanSearchBean.java

@@ -0,0 +1,112 @@
+package com.sapit.common.search.inspect;
+
+import com.sapit.common.framwork.BaseSearchBean;
+import com.sapit.common.framwork.CompositeSearch;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * I02-A02
+ * 巡检计划查询条件。
+ */
+public class BuInspectPlanSearchBean extends BaseSearchBean {
+    @CompositeSearch(operCode = "LR%", fieldName = "planName")
+    private String planName;
+
+    @CompositeSearch(operCode = "=", fieldName = "planType")
+    private Integer planType;
+
+    @CompositeSearch(operCode = "=", fieldName = "cycleType")
+    private Integer cycleType;
+
+    @CompositeSearch(operCode = "=", fieldName = "status")
+    private Integer status;
+
+    @CompositeSearch(operCode = "=", fieldName = "inspectorId")
+    private Long inspectorId;
+
+    @CompositeSearch(operCode = ">=", fieldName = "startDate")
+    private Date startDateBegin;
+
+    @CompositeSearch(operCode = "<=", fieldName = "startDate")
+    private Date startDateEnd;
+
+    @CompositeSearch(operCode = ">=", fieldName = "endDate")
+    private Date endDateBegin;
+
+    @CompositeSearch(operCode = "<=", fieldName = "endDate")
+    private Date endDateEnd;
+
+    public String getPlanName() {
+        return planName;
+    }
+
+    public void setPlanName(String planName) {
+        this.planName = planName;
+    }
+
+    public Integer getPlanType() {
+        return planType;
+    }
+
+    public void setPlanType(Integer planType) {
+        this.planType = planType;
+    }
+
+    public Integer getCycleType() {
+        return cycleType;
+    }
+
+    public void setCycleType(Integer cycleType) {
+        this.cycleType = cycleType;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Long getInspectorId() {
+        return inspectorId;
+    }
+
+    public void setInspectorId(Long inspectorId) {
+        this.inspectorId = inspectorId;
+    }
+
+    public Date getStartDateBegin() {
+        return startDateBegin;
+    }
+
+    public void setStartDateBegin(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date startDateBegin) {
+        this.startDateBegin = startDateBegin;
+    }
+
+    public Date getStartDateEnd() {
+        return startDateEnd;
+    }
+
+    public void setStartDateEnd(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date startDateEnd) {
+        this.startDateEnd = startDateEnd;
+    }
+
+    public Date getEndDateBegin() {
+        return endDateBegin;
+    }
+
+    public void setEndDateBegin(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date endDateBegin) {
+        this.endDateBegin = endDateBegin;
+    }
+
+    public Date getEndDateEnd() {
+        return endDateEnd;
+    }
+
+    public void setEndDateEnd(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date endDateEnd) {
+        this.endDateEnd = endDateEnd;
+    }
+}

+ 76 - 0
common/src/main/java/com/sapit/common/search/inspect/BuInspectPlanStoreSearchBean.java

@@ -0,0 +1,76 @@
+package com.sapit.common.search.inspect;
+
+import com.sapit.common.framwork.BaseSearchBean;
+import com.sapit.common.framwork.CompositeSearch;
+
+/**
+ * I02-A07
+ * 巡检计划门店关联查询条件。
+ */
+public class BuInspectPlanStoreSearchBean extends BaseSearchBean {
+    @CompositeSearch(operCode = "=", fieldName = "planId")
+    private Long planId;
+
+    @CompositeSearch(operCode = "=", fieldName = "storeId")
+    private Long storeId;
+
+    @CompositeSearch(operCode = "=", fieldName = "storeBelongto")
+    private Long storeBelongto;
+
+    @CompositeSearch(operCode = "=", fieldName = "province")
+    private Long province;
+
+    @CompositeSearch(operCode = "=", fieldName = "city")
+    private Long city;
+
+    @CompositeSearch(operCode = "=", fieldName = "status")
+    private Integer status;
+
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(Long storeId) {
+        this.storeId = storeId;
+    }
+
+    public Long getStoreBelongto() {
+        return storeBelongto;
+    }
+
+    public void setStoreBelongto(Long storeBelongto) {
+        this.storeBelongto = storeBelongto;
+    }
+
+    public Long getProvince() {
+        return province;
+    }
+
+    public void setProvince(Long province) {
+        this.province = province;
+    }
+
+    public Long getCity() {
+        return city;
+    }
+
+    public void setCity(Long city) {
+        this.city = city;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+}

+ 76 - 0
common/src/main/java/com/sapit/common/search/inspect/BuInspectTaskItemSearchBean.java

@@ -0,0 +1,76 @@
+package com.sapit.common.search.inspect;
+
+import com.sapit.common.framwork.BaseSearchBean;
+import com.sapit.common.framwork.CompositeSearch;
+
+/**
+ * I02-A08
+ * 巡检任务明细查询条件。
+ */
+public class BuInspectTaskItemSearchBean extends BaseSearchBean {
+    @CompositeSearch(operCode = "=", fieldName = "taskId")
+    private Long taskId;
+
+    @CompositeSearch(operCode = "=", fieldName = "planId")
+    private Long planId;
+
+    @CompositeSearch(operCode = "=", fieldName = "storeId")
+    private Long storeId;
+
+    @CompositeSearch(operCode = "=", fieldName = "indicatorId")
+    private Long indicatorId;
+
+    @CompositeSearch(operCode = "=", fieldName = "status")
+    private Integer status;
+
+    @CompositeSearch(operCode = "=", fieldName = "passFlag")
+    private Integer passFlag;
+
+    public Long getTaskId() {
+        return taskId;
+    }
+
+    public void setTaskId(Long taskId) {
+        this.taskId = taskId;
+    }
+
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(Long storeId) {
+        this.storeId = storeId;
+    }
+
+    public Long getIndicatorId() {
+        return indicatorId;
+    }
+
+    public void setIndicatorId(Long indicatorId) {
+        this.indicatorId = indicatorId;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Integer getPassFlag() {
+        return passFlag;
+    }
+
+    public void setPassFlag(Integer passFlag) {
+        this.passFlag = passFlag;
+    }
+}

+ 101 - 0
common/src/main/java/com/sapit/common/search/inspect/BuInspectTaskSearchBean.java

@@ -0,0 +1,101 @@
+package com.sapit.common.search.inspect;
+
+import com.sapit.common.framwork.BaseSearchBean;
+import com.sapit.common.framwork.CompositeSearch;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * I02-A03
+ * 巡检任务查询条件。
+ */
+public class BuInspectTaskSearchBean extends BaseSearchBean {
+    @CompositeSearch(operCode = "=", fieldName = "planId")
+    private Long planId;
+
+    @CompositeSearch(operCode = "=", fieldName = "storeId")
+    private Long storeId;
+
+    @CompositeSearch(operCode = "=", fieldName = "taskDate")
+    private Date taskDate;
+
+    @CompositeSearch(operCode = ">=", fieldName = "taskDate")
+    private Date taskDateBegin;
+
+    @CompositeSearch(operCode = "<=", fieldName = "taskDate")
+    private Date taskDateEnd;
+
+    @CompositeSearch(operCode = "=", fieldName = "inspectorId")
+    private Long inspectorId;
+
+    @CompositeSearch(operCode = "=", fieldName = "status")
+    private Integer status;
+
+    @CompositeSearch(operCode = "=", fieldName = "passFlag")
+    private Integer passFlag;
+
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(Long storeId) {
+        this.storeId = storeId;
+    }
+
+    public Date getTaskDate() {
+        return taskDate;
+    }
+
+    public void setTaskDate(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date taskDate) {
+        this.taskDate = taskDate;
+    }
+
+    public Date getTaskDateBegin() {
+        return taskDateBegin;
+    }
+
+    public void setTaskDateBegin(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date taskDateBegin) {
+        this.taskDateBegin = taskDateBegin;
+    }
+
+    public Date getTaskDateEnd() {
+        return taskDateEnd;
+    }
+
+    public void setTaskDateEnd(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date taskDateEnd) {
+        this.taskDateEnd = taskDateEnd;
+    }
+
+    public Long getInspectorId() {
+        return inspectorId;
+    }
+
+    public void setInspectorId(Long inspectorId) {
+        this.inspectorId = inspectorId;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Integer getPassFlag() {
+        return passFlag;
+    }
+
+    public void setPassFlag(Integer passFlag) {
+        this.passFlag = passFlag;
+    }
+}

+ 112 - 0
common/src/main/java/com/sapit/common/search/inspect/BuRectificationSearchBean.java

@@ -0,0 +1,112 @@
+package com.sapit.common.search.inspect;
+
+import com.sapit.common.framwork.BaseSearchBean;
+import com.sapit.common.framwork.CompositeSearch;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * I02-A04
+ * 整改记录查询条件。
+ */
+public class BuRectificationSearchBean extends BaseSearchBean {
+    @CompositeSearch(operCode = "=", fieldName = "taskId")
+    private Long taskId;
+
+    @CompositeSearch(operCode = "=", fieldName = "taskItemId")
+    private Long taskItemId;
+
+    @CompositeSearch(operCode = "=", fieldName = "planId")
+    private Long planId;
+
+    @CompositeSearch(operCode = "=", fieldName = "storeId")
+    private Long storeId;
+
+    @CompositeSearch(operCode = "=", fieldName = "indicatorId")
+    private Long indicatorId;
+
+    @CompositeSearch(operCode = "=", fieldName = "status")
+    private Integer status;
+
+    @CompositeSearch(operCode = ">=", fieldName = "deadlineDate")
+    private Date deadlineDateBegin;
+
+    @CompositeSearch(operCode = "<=", fieldName = "deadlineDate")
+    private Date deadlineDateEnd;
+
+    @CompositeSearch(operCode = "=", fieldName = "recheckUserId")
+    private Long recheckUserId;
+
+    public Long getTaskId() {
+        return taskId;
+    }
+
+    public void setTaskId(Long taskId) {
+        this.taskId = taskId;
+    }
+
+    public Long getTaskItemId() {
+        return taskItemId;
+    }
+
+    public void setTaskItemId(Long taskItemId) {
+        this.taskItemId = taskItemId;
+    }
+
+    public Long getPlanId() {
+        return planId;
+    }
+
+    public void setPlanId(Long planId) {
+        this.planId = planId;
+    }
+
+    public Long getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(Long storeId) {
+        this.storeId = storeId;
+    }
+
+    public Long getIndicatorId() {
+        return indicatorId;
+    }
+
+    public void setIndicatorId(Long indicatorId) {
+        this.indicatorId = indicatorId;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Date getDeadlineDateBegin() {
+        return deadlineDateBegin;
+    }
+
+    public void setDeadlineDateBegin(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date deadlineDateBegin) {
+        this.deadlineDateBegin = deadlineDateBegin;
+    }
+
+    public Date getDeadlineDateEnd() {
+        return deadlineDateEnd;
+    }
+
+    public void setDeadlineDateEnd(@DateTimeFormat(pattern = "yyyy/MM/dd'T'HH:mm:ss.SSSZ") Date deadlineDateEnd) {
+        this.deadlineDateEnd = deadlineDateEnd;
+    }
+
+    public Long getRecheckUserId() {
+        return recheckUserId;
+    }
+
+    public void setRecheckUserId(Long recheckUserId) {
+        this.recheckUserId = recheckUserId;
+    }
+}

+ 144 - 0
docs/inspect-permission.sql

@@ -0,0 +1,144 @@
+-- Menu and button permissions for Store Inspection & Assessment.
+-- Run this script in database `eduepos-plat`.
+--
+-- 结构:独立顶级模块「巡检与考核」(sys_model INSPECT),其下 5 个子菜单:
+--   考核指标配置  /admin/inspect
+--   巡检计划管理  /admin/inspect-plan
+--   巡检任务执行  /admin/inspect-task
+--   整改流程管理  /admin/inspect-rect
+--   考核结果排名  /admin/inspect-assess
+-- 说明:侧边栏菜单来自登录时按角色查询的 SYS_USER_MENU_V(基于 sys_role),
+-- 必须同时把菜单/按钮授权给角色(sys_role / sys_role_menu_button)才能显示。
+-- 脚本幂等,可重复执行。
+
+-- 1. Create the INSPECT model (top-level module group) if not exists.
+INSERT INTO sys_model (MODEL_CODE, MODEL_NAME, STATUS, CREATE_DATE, RMK)
+SELECT 'INSPECT', '巡检与考核', 1, NOW(), 'el-icon-s-platform'
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM sys_model WHERE MODEL_CODE = 'INSPECT');
+
+-- 2. Re-point the existing /admin/inspect menu as the first sub-menu under INSPECT.
+UPDATE sys_menu
+SET MODEL_CODE = 'INSPECT',
+    MENU_NAME = '考核指标配置',
+    LEVEL = 2,
+    FATHER_ID = NULL,
+    ORDER_KEY = 1,
+    STATUS = 1
+WHERE URL = '/admin/inspect';
+
+-- 3. Insert the remaining four sub-menus under INSPECT (skip if already exists).
+INSERT INTO sys_menu (MODEL_CODE, MENU_NAME, URL, LEVEL, ORDER_KEY, FATHER_ID, STATUS, CREATE_DATE)
+SELECT 'INSPECT', '巡检计划管理', '/admin/inspect-plan', 2, 2, NULL, 1, NOW()
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE URL = '/admin/inspect-plan');
+
+INSERT INTO sys_menu (MODEL_CODE, MENU_NAME, URL, LEVEL, ORDER_KEY, FATHER_ID, STATUS, CREATE_DATE)
+SELECT 'INSPECT', '巡检任务执行', '/admin/inspect-task', 2, 3, NULL, 1, NOW()
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE URL = '/admin/inspect-task');
+
+INSERT INTO sys_menu (MODEL_CODE, MENU_NAME, URL, LEVEL, ORDER_KEY, FATHER_ID, STATUS, CREATE_DATE)
+SELECT 'INSPECT', '整改流程管理', '/admin/inspect-rect', 2, 4, NULL, 1, NOW()
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE URL = '/admin/inspect-rect');
+
+INSERT INTO sys_menu (MODEL_CODE, MENU_NAME, URL, LEVEL, ORDER_KEY, FATHER_ID, STATUS, CREATE_DATE)
+SELECT 'INSPECT', '考核结果排名', '/admin/inspect-assess', 2, 5, NULL, 1, NOW()
+FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE URL = '/admin/inspect-assess');
+
+-- 3.5 Clean up stray buttons left on the re-pointed indicator menu from an earlier broad grant.
+DELETE FROM sys_role_menu_button
+WHERE MENU_ID = (SELECT MENU_ID FROM sys_menu WHERE URL = '/admin/inspect')
+  AND BUTTON_CODE NOT IN ('add', 'edit', 'delete', 'weight');
+
+DELETE FROM sys_menu_button
+WHERE MENU_ID = (SELECT MENU_ID FROM sys_menu WHERE URL = '/admin/inspect')
+  AND BUTTON_CODE NOT IN ('add', 'edit', 'delete', 'weight');
+
+-- 4. Register button permissions for each menu.
+INSERT INTO sys_menu_button (MENU_ID, BUTTON_CODE)
+SELECT m.MENU_ID, b.BUTTON_CODE
+FROM sys_menu m
+JOIN (
+    SELECT '/admin/inspect' AS url, 'add' AS button_code
+    UNION ALL SELECT '/admin/inspect', 'edit'
+    UNION ALL SELECT '/admin/inspect', 'delete'
+    UNION ALL SELECT '/admin/inspect', 'weight'
+    UNION ALL SELECT '/admin/inspect-plan', 'add'
+    UNION ALL SELECT '/admin/inspect-plan', 'edit'
+    UNION ALL SELECT '/admin/inspect-plan', 'delete'
+    UNION ALL SELECT '/admin/inspect-plan', 'publish'
+    UNION ALL SELECT '/admin/inspect-plan', 'pause'
+    UNION ALL SELECT '/admin/inspect-plan', 'finish'
+    UNION ALL SELECT '/admin/inspect-plan', 'generate'
+    UNION ALL SELECT '/admin/inspect-task', 'execute'
+    UNION ALL SELECT '/admin/inspect-task', 'submit'
+    UNION ALL SELECT '/admin/inspect-task', 'cancel'
+    UNION ALL SELECT '/admin/inspect-rect', 'recheck'
+    UNION ALL SELECT '/admin/inspect-rect', 'urge'
+    UNION ALL SELECT '/admin/inspect-rect', 'batch'
+    UNION ALL SELECT '/admin/inspect-assess', 'export'
+    UNION ALL SELECT '/admin/inspect-assess', 'trigger'
+) b ON b.url = m.URL
+WHERE NOT EXISTS (
+    SELECT 1 FROM sys_menu_button exists_button
+    WHERE exists_button.MENU_ID = m.MENU_ID
+      AND exists_button.BUTTON_CODE = b.BUTTON_CODE
+);
+
+-- 5. Grant all five inspect menus to every role that already has the store management menu.
+INSERT INTO sys_role (CHARACTOR_ID, MENU_ID)
+SELECT
+    old_role.CHARACTOR_ID,
+    new_menu.MENU_ID
+FROM sys_role old_role
+JOIN sys_menu old_menu ON old_menu.MENU_ID = old_role.MENU_ID
+JOIN sys_menu new_menu ON new_menu.URL IN ('/admin/inspect', '/admin/inspect-plan', '/admin/inspect-task', '/admin/inspect-rect', '/admin/inspect-assess')
+WHERE old_menu.URL = '/admin/storemanage'
+  AND NOT EXISTS (
+      SELECT 1 FROM sys_role exists_role
+      WHERE exists_role.CHARACTOR_ID = old_role.CHARACTOR_ID
+        AND exists_role.MENU_ID = new_menu.MENU_ID
+  );
+
+-- 6. Fallback: if NO role has the store management menu, grant the five inspect menus to all roles.
+INSERT INTO sys_role (CHARACTOR_ID, MENU_ID)
+SELECT
+    all_role.CHARACTOR_ID,
+    new_menu.MENU_ID
+FROM (SELECT DISTINCT CHARACTOR_ID FROM sys_role) all_role
+JOIN sys_menu new_menu ON new_menu.URL IN ('/admin/inspect', '/admin/inspect-plan', '/admin/inspect-task', '/admin/inspect-rect', '/admin/inspect-assess')
+WHERE (SELECT COUNT(*) FROM sys_role r JOIN sys_menu m ON m.MENU_ID = r.MENU_ID WHERE m.URL = '/admin/storemanage') = 0
+  AND NOT EXISTS (
+      SELECT 1 FROM sys_role exists_role
+      WHERE exists_role.CHARACTOR_ID = all_role.CHARACTOR_ID
+        AND exists_role.MENU_ID = new_menu.MENU_ID
+  );
+
+-- 7. Grant matching button permissions to the roles that got the inspect menus.
+INSERT INTO sys_role_menu_button (MENU_ID, ROLE_ID, BUTTON_CODE)
+SELECT
+    new_menu.MENU_ID,
+    role.CHARACTOR_ID,
+    button.BUTTON_CODE
+FROM sys_role role
+JOIN sys_menu new_menu ON new_menu.URL IN ('/admin/inspect', '/admin/inspect-plan', '/admin/inspect-task', '/admin/inspect-rect', '/admin/inspect-assess')
+JOIN sys_menu_button button ON button.MENU_ID = new_menu.MENU_ID
+WHERE role.MENU_ID = new_menu.MENU_ID
+  AND NOT EXISTS (
+      SELECT 1 FROM sys_role_menu_button exists_button
+      WHERE exists_button.MENU_ID = new_menu.MENU_ID
+        AND exists_button.ROLE_ID = role.CHARACTOR_ID
+        AND exists_button.BUTTON_CODE = button.BUTTON_CODE
+  );
+
+-- 8. Verify model, menus, buttons and granted roles.
+SELECT mo.MODEL_CODE, mo.MODEL_NAME, m.MENU_ID, m.MENU_NAME, m.URL, m.ORDER_KEY, b.BUTTON_CODE, r.CHARACTOR_ID
+FROM sys_model mo
+LEFT JOIN sys_menu m ON m.MODEL_CODE = mo.MODEL_CODE
+LEFT JOIN sys_menu_button b ON b.MENU_ID = m.MENU_ID
+LEFT JOIN sys_role r ON r.MENU_ID = m.MENU_ID
+WHERE mo.MODEL_CODE = 'INSPECT'
+ORDER BY m.ORDER_KEY, b.BUTTON_CODE, r.CHARACTOR_ID;

+ 227 - 0
docs/inspect-schema-optimized.sql

@@ -0,0 +1,227 @@
+/* Store inspection and assessment schema. */
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+DROP TABLE IF EXISTS `bu_rectification`;
+DROP TABLE IF EXISTS `bu_inspect_task_item`;
+DROP TABLE IF EXISTS `bu_inspect_task`;
+DROP TABLE IF EXISTS `bu_inspect_plan_store`;
+DROP TABLE IF EXISTS `bu_inspect_plan_indicator`;
+DROP TABLE IF EXISTS `bu_inspect_plan`;
+DROP TABLE IF EXISTS `bu_inspect_indicator`;
+DROP TABLE IF EXISTS `bu_assess_result`;
+
+CREATE TABLE `bu_inspect_indicator` (
+  `INDICATOR_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Indicator ID',
+  `INDICATOR_CODE` varchar(64) NOT NULL COMMENT 'Indicator code',
+  `INDICATOR_NAME` varchar(100) NOT NULL COMMENT 'Indicator name',
+  `INDICATOR_TYPE` tinyint(4) NOT NULL DEFAULT '2' COMMENT '1 automatic, 2 manual',
+  `DATA_SOURCE` varchar(64) DEFAULT NULL COMMENT 'Automatic data source',
+  `MAX_SCORE` decimal(10,2) NOT NULL DEFAULT '100.00' COMMENT 'Default maximum score',
+  `PASS_SCORE` decimal(10,2) NOT NULL DEFAULT '60.00' COMMENT 'Default passing score',
+  `WEIGHT` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT 'Weight percentage',
+  `SCORE_RULE` text COMMENT 'Scoring rule JSON',
+  `SORT` int(11) NOT NULL DEFAULT '0' COMMENT 'Display order',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1 enabled, 0 disabled',
+  `RMK` varchar(500) DEFAULT NULL COMMENT 'Remark',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`INDICATOR_ID`),
+  UNIQUE KEY `UK_INSPECT_INDICATOR_CODE` (`INDICATOR_CODE`),
+  KEY `IDX_INSPECT_INDICATOR_TYPE` (`INDICATOR_TYPE`),
+  KEY `IDX_INSPECT_INDICATOR_STATUS_SORT` (`STATUS`,`SORT`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Inspection indicators';
+
+CREATE TABLE `bu_inspect_plan` (
+  `PLAN_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Inspection plan ID',
+  `PLAN_NAME` varchar(150) NOT NULL COMMENT 'Plan name',
+  `PLAN_TYPE` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1 regular, 2 special, 3 temporary',
+  `CYCLE_TYPE` tinyint(4) NOT NULL DEFAULT '2' COMMENT '1 daily, 2 weekly, 3 monthly, 4 once',
+  `START_DATE` date NOT NULL COMMENT 'Plan start date',
+  `END_DATE` date NOT NULL COMMENT 'Plan end date',
+  `NEXT_EXECUTE_DATE` date DEFAULT NULL COMMENT 'Next execution date',
+  `INSPECTOR_ID` bigint(20) DEFAULT NULL COMMENT 'Default inspector ID',
+  `INSPECTOR_NAME` varchar(100) DEFAULT NULL COMMENT 'Default inspector name snapshot',
+  `AUTO_COLLECT_FLAG` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1 enable automatic collection, 0 disable',
+  `STORE_COUNT` int(11) NOT NULL DEFAULT '0' COMMENT 'Associated store count snapshot',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0 draft, 1 published, 2 paused, 3 finished, 4 deleted',
+  `PUBLISH_DATE` datetime DEFAULT NULL,
+  `FINISH_DATE` datetime DEFAULT NULL,
+  `RMK` varchar(500) DEFAULT NULL,
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`PLAN_ID`),
+  KEY `IDX_INSPECT_PLAN_STATUS` (`STATUS`),
+  KEY `IDX_INSPECT_PLAN_CYCLE` (`CYCLE_TYPE`,`NEXT_EXECUTE_DATE`),
+  KEY `IDX_INSPECT_PLAN_DATE` (`START_DATE`,`END_DATE`),
+  KEY `IDX_INSPECT_PLAN_INSPECTOR` (`INSPECTOR_ID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Inspection plans';
+
+CREATE TABLE `bu_inspect_plan_indicator` (
+  `PLAN_INDICATOR_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Plan-indicator relation ID',
+  `PLAN_ID` bigint(20) NOT NULL COMMENT 'Inspection plan ID',
+  `INDICATOR_ID` bigint(20) NOT NULL COMMENT 'Indicator ID',
+  `INDICATOR_NAME` varchar(100) NOT NULL COMMENT 'Indicator name snapshot',
+  `INDICATOR_TYPE` tinyint(4) NOT NULL COMMENT 'Indicator type snapshot',
+  `MAX_SCORE` decimal(10,2) NOT NULL DEFAULT '100.00' COMMENT 'Plan-specific maximum score',
+  `PASS_SCORE` decimal(10,2) NOT NULL DEFAULT '60.00' COMMENT 'Plan-specific passing score',
+  `WEIGHT` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT 'Weight percentage',
+  `SCORE_RULE` text COMMENT 'Plan-specific scoring rule snapshot',
+  `SORT` int(11) NOT NULL DEFAULT '0',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1 valid, 0 invalid',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`PLAN_INDICATOR_ID`),
+  UNIQUE KEY `UK_INSPECT_PLAN_INDICATOR` (`PLAN_ID`,`INDICATOR_ID`),
+  KEY `IDX_INSPECT_PLAN_INDICATOR_INDICATOR` (`INDICATOR_ID`),
+  KEY `IDX_INSPECT_PLAN_INDICATOR_SORT` (`PLAN_ID`,`STATUS`,`SORT`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Inspection plan indicators';
+
+CREATE TABLE `bu_inspect_plan_store` (
+  `PLAN_STORE_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Plan-store relation ID',
+  `PLAN_ID` bigint(20) NOT NULL,
+  `STORE_ID` bigint(20) NOT NULL,
+  `STORE_NAME` varchar(150) DEFAULT NULL COMMENT 'Store name snapshot',
+  `STORE_BELONGTO` bigint(20) DEFAULT NULL,
+  `PROVINCE` bigint(20) DEFAULT NULL,
+  `CITY` bigint(20) DEFAULT NULL,
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1 valid, 0 deleted',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`PLAN_STORE_ID`),
+  UNIQUE KEY `UK_INSPECT_PLAN_STORE` (`PLAN_ID`,`STORE_ID`),
+  KEY `IDX_INSPECT_PLAN_STORE_STORE` (`STORE_ID`),
+  KEY `IDX_INSPECT_PLAN_STORE_AREA` (`STORE_BELONGTO`,`PROVINCE`,`CITY`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Inspection plan stores';
+
+CREATE TABLE `bu_inspect_task` (
+  `TASK_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Inspection task ID',
+  `PLAN_ID` bigint(20) NOT NULL,
+  `PLAN_NAME` varchar(150) DEFAULT NULL COMMENT 'Plan name snapshot',
+  `STORE_ID` bigint(20) NOT NULL,
+  `STORE_NAME` varchar(150) DEFAULT NULL COMMENT 'Store name snapshot',
+  `TASK_DATE` date NOT NULL COMMENT 'Task date',
+  `INSPECTOR_ID` bigint(20) DEFAULT NULL COMMENT 'Actual inspector ID',
+  `INSPECTOR_NAME` varchar(100) DEFAULT NULL COMMENT 'Actual inspector name snapshot',
+  `TOTAL_SCORE` decimal(10,2) DEFAULT NULL COMMENT 'Total score',
+  `MAX_SCORE` decimal(10,2) DEFAULT NULL COMMENT 'Total maximum score',
+  `PASS_SCORE` decimal(10,2) DEFAULT NULL COMMENT 'Total passing score',
+  `PASS_FLAG` tinyint(4) DEFAULT NULL COMMENT '1 passed, 0 failed',
+  `UNPASS_ITEM_COUNT` int(11) NOT NULL DEFAULT '0' COMMENT 'Failed item count',
+  `START_DATE` datetime DEFAULT NULL COMMENT 'Execution start time',
+  `SUBMIT_DATE` datetime DEFAULT NULL COMMENT 'Submission time',
+  `CANCEL_DATE` datetime DEFAULT NULL COMMENT 'Cancellation time',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1 pending, 2 executing, 3 finished, 4 cancelled',
+  `RMK` varchar(500) DEFAULT NULL COMMENT 'Task remark',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`TASK_ID`),
+  UNIQUE KEY `UK_INSPECT_TASK_PLAN_STORE_DATE` (`PLAN_ID`,`STORE_ID`,`TASK_DATE`),
+  KEY `IDX_INSPECT_TASK_STORE_DATE` (`STORE_ID`,`TASK_DATE`),
+  KEY `IDX_INSPECT_TASK_STATUS_DATE` (`STATUS`,`TASK_DATE`),
+  KEY `IDX_INSPECT_TASK_INSPECTOR` (`INSPECTOR_ID`,`STATUS`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Inspection tasks';
+
+CREATE TABLE `bu_inspect_task_item` (
+  `TASK_ITEM_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Task score item ID',
+  `TASK_ID` bigint(20) NOT NULL,
+  `PLAN_ID` bigint(20) NOT NULL,
+  `STORE_ID` bigint(20) NOT NULL,
+  `PLAN_INDICATOR_ID` bigint(20) NOT NULL DEFAULT '0' COMMENT '0 means plan-level score',
+  `INDICATOR_ID` bigint(20) DEFAULT NULL COMMENT 'Indicator ID; NULL for plan-level score',
+  `SCORE_LEVEL` tinyint(4) NOT NULL DEFAULT '2' COMMENT '1 plan level, 2 indicator level',
+  `INDICATOR_CODE` varchar(64) DEFAULT NULL COMMENT 'Indicator code snapshot',
+  `INDICATOR_NAME` varchar(100) DEFAULT NULL COMMENT 'Indicator name snapshot',
+  `INDICATOR_TYPE` tinyint(4) DEFAULT NULL,
+  `DATA_SOURCE` varchar(64) DEFAULT NULL,
+  `WEIGHT` decimal(10,2) NOT NULL DEFAULT '0.00',
+  `MAX_SCORE` decimal(10,2) NOT NULL DEFAULT '100.00',
+  `PASS_SCORE` decimal(10,2) NOT NULL DEFAULT '60.00',
+  `RAW_VALUE` decimal(12,4) DEFAULT NULL COMMENT 'Automatic collection raw value',
+  `RAW_VALUE_TEXT` varchar(100) DEFAULT NULL COMMENT 'Raw value display text',
+  `SCORE` decimal(10,2) DEFAULT NULL,
+  `WEIGHTED_SCORE` decimal(10,2) DEFAULT NULL,
+  `PASS_FLAG` tinyint(4) DEFAULT NULL COMMENT '1 passed, 0 failed',
+  `PICTURE_URLS` varchar(2000) DEFAULT NULL,
+  `RMK` varchar(1000) DEFAULT NULL,
+  `SORT` int(11) NOT NULL DEFAULT '0',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`TASK_ITEM_ID`),
+  UNIQUE KEY `UK_INSPECT_TASK_ITEM` (`TASK_ID`,`PLAN_INDICATOR_ID`),
+  KEY `IDX_INSPECT_TASK_ITEM_STORE` (`STORE_ID`),
+  KEY `IDX_INSPECT_TASK_ITEM_INDICATOR` (`INDICATOR_ID`),
+  KEY `IDX_INSPECT_TASK_ITEM_PASS` (`PASS_FLAG`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Inspection task score items';
+
+CREATE TABLE `bu_rectification` (
+  `RECT_ID` bigint(20) NOT NULL AUTO_INCREMENT,
+  `TASK_ID` bigint(20) NOT NULL,
+  `TASK_ITEM_ID` bigint(20) NOT NULL,
+  `PLAN_ID` bigint(20) DEFAULT NULL,
+  `STORE_ID` bigint(20) NOT NULL,
+  `STORE_NAME` varchar(150) DEFAULT NULL,
+  `INDICATOR_ID` bigint(20) DEFAULT NULL COMMENT 'NULL for plan-level rectification',
+  `INDICATOR_NAME` varchar(100) DEFAULT NULL COMMENT 'Indicator name snapshot',
+  `RECT_ROUND` int(11) NOT NULL DEFAULT '1',
+  `PARENT_RECT_ID` bigint(20) DEFAULT NULL,
+  `INSPECT_SCORE` decimal(10,2) DEFAULT NULL,
+  `PASS_SCORE` decimal(10,2) DEFAULT NULL,
+  `NOTICE_CONTENT` varchar(1000) DEFAULT NULL,
+  `DEADLINE_DATE` datetime NOT NULL,
+  `START_DATE` datetime DEFAULT NULL,
+  `FEEDBACK_CONTENT` varchar(1000) DEFAULT NULL,
+  `FEEDBACK_PICTURES` varchar(2000) DEFAULT NULL,
+  `FEEDBACK_DATE` datetime DEFAULT NULL,
+  `RECHECK_SCORE` decimal(10,2) DEFAULT NULL,
+  `RECHECK_RESULT` tinyint(4) DEFAULT NULL,
+  `RECHECK_PICTURES` varchar(2000) DEFAULT NULL,
+  `RECHECK_RMK` varchar(1000) DEFAULT NULL,
+  `RECHECK_USER_ID` bigint(20) DEFAULT NULL,
+  `RECHECK_USER_NAME` varchar(100) DEFAULT NULL,
+  `RECHECK_DATE` datetime DEFAULT NULL,
+  `URGE_COUNT` int(11) NOT NULL DEFAULT '0',
+  `LAST_URGE_DATE` datetime DEFAULT NULL,
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`RECT_ID`),
+  KEY `IDX_RECT_TASK_ITEM` (`TASK_ITEM_ID`),
+  KEY `IDX_RECT_STORE_STATUS` (`STORE_ID`,`STATUS`),
+  KEY `IDX_RECT_INDICATOR` (`INDICATOR_ID`),
+  KEY `IDX_RECT_DEADLINE` (`STATUS`,`DEADLINE_DATE`),
+  KEY `IDX_RECT_PARENT` (`PARENT_RECT_ID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Inspection rectification records';
+
+CREATE TABLE `bu_assess_result` (
+  `ASSESS_RESULT_ID` bigint(20) NOT NULL AUTO_INCREMENT,
+  `STORE_ID` bigint(20) NOT NULL,
+  `STORE_NAME` varchar(150) DEFAULT NULL,
+  `STORE_BELONGTO` bigint(20) DEFAULT NULL,
+  `PROVINCE` bigint(20) DEFAULT NULL,
+  `CITY` bigint(20) DEFAULT NULL,
+  `PERIOD_TYPE` tinyint(4) NOT NULL COMMENT '1 weekly, 2 monthly',
+  `PERIOD_CODE` varchar(20) NOT NULL,
+  `PERIOD_START_DATE` date NOT NULL,
+  `PERIOD_END_DATE` date NOT NULL,
+  `INSPECT_SCORE` decimal(10,2) NOT NULL DEFAULT '0.00',
+  `AUTO_SCORE` decimal(10,2) NOT NULL DEFAULT '0.00',
+  `TOTAL_SCORE` decimal(10,2) NOT NULL DEFAULT '0.00',
+  `ASSESS_LEVEL` tinyint(4) NOT NULL DEFAULT '4',
+  `RANK_NO` int(11) DEFAULT NULL,
+  `TASK_COUNT` int(11) NOT NULL DEFAULT '0',
+  `FINISH_TASK_COUNT` int(11) NOT NULL DEFAULT '0',
+  `RECT_COUNT` int(11) NOT NULL DEFAULT '0',
+  `OVERTIME_RECT_COUNT` int(11) NOT NULL DEFAULT '0',
+  `SUMMARY_DATE` datetime NOT NULL,
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`ASSESS_RESULT_ID`),
+  UNIQUE KEY `UK_ASSESS_STORE_PERIOD` (`STORE_ID`,`PERIOD_TYPE`,`PERIOD_CODE`),
+  KEY `IDX_ASSESS_PERIOD_RANK` (`PERIOD_TYPE`,`PERIOD_CODE`,`RANK_NO`),
+  KEY `IDX_ASSESS_AREA` (`STORE_BELONGTO`,`PROVINCE`,`CITY`),
+  KEY `IDX_ASSESS_LEVEL` (`ASSESS_LEVEL`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Store assessment results';
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 235 - 0
docs/inspect-schema.sql

@@ -0,0 +1,235 @@
+/*
+ Navicat MySQL Data Transfer
+
+ 门店巡检与考核管理模块表结构
+ 设计日期: 2026-08-20
+
+ 说明:
+ 1. 本脚本仅新增巡检考核模块表,不修改订单、门店、评价、结算等已有核心表。
+ 2. 关联 bu_store、bu_order、bu_evaluate 等已有表时只保存业务 ID,不创建外键,保持与现有项目风格一致。
+ 3. 图片字段使用逗号分隔 URL,后续如需更强审计能力可拆出图片附件表。
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+DROP TABLE IF EXISTS `bu_inspect_indicator`;
+CREATE TABLE `bu_inspect_indicator` (
+  `INDICATOR_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '考核指标ID',
+  `INDICATOR_CODE` varchar(64) NOT NULL COMMENT '指标编码,如 COOK_TIME、HYGIENE',
+  `INDICATOR_NAME` varchar(100) NOT NULL COMMENT '指标名称',
+  `INDICATOR_TYPE` tinyint(4) NOT NULL DEFAULT '2' COMMENT '指标类型:1自动采集,2人工巡检',
+  `DATA_SOURCE` varchar(64) DEFAULT NULL COMMENT '自动采集数据来源,如 ORDER_COOK_TIME、EVALUATE_GOOD_RATE',
+  `MAX_SCORE` decimal(10,2) NOT NULL DEFAULT '100.00' COMMENT '满分',
+  `PASS_SCORE` decimal(10,2) NOT NULL DEFAULT '60.00' COMMENT '及格分',
+  `WEIGHT` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '权重百分比',
+  `SCORE_RULE` text COMMENT '评分规则JSON,自动采集指标必填',
+  `SORT` int(11) NOT NULL DEFAULT '0' COMMENT '排序序号',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1启用,0停用',
+  `RMK` varchar(500) DEFAULT NULL COMMENT '备注',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`INDICATOR_ID`),
+  UNIQUE KEY `UK_INSPECT_INDICATOR_CODE` (`INDICATOR_CODE`),
+  KEY `IDX_INSPECT_INDICATOR_TYPE` (`INDICATOR_TYPE`),
+  KEY `IDX_INSPECT_INDICATOR_STATUS_SORT` (`STATUS`,`SORT`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡检考核指标表';
+
+DROP TABLE IF EXISTS `bu_inspect_plan`;
+CREATE TABLE `bu_inspect_plan` (
+  `PLAN_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '巡检计划ID',
+  `PLAN_NAME` varchar(150) NOT NULL COMMENT '计划名称',
+  `PLAN_TYPE` tinyint(4) NOT NULL DEFAULT '1' COMMENT '计划类型:1常规巡检,2专项巡检,3临时巡检',
+  `CYCLE_TYPE` tinyint(4) NOT NULL DEFAULT '2' COMMENT '周期类型:1每日,2每周,3每月,4单次',
+  `START_DATE` date NOT NULL COMMENT '计划开始日期',
+  `END_DATE` date NOT NULL COMMENT '计划结束日期',
+  `NEXT_EXECUTE_DATE` date DEFAULT NULL COMMENT '下次执行日期',
+  `INSPECTOR_ID` bigint(20) DEFAULT NULL COMMENT '巡检员ID,关联管理端用户',
+  `INSPECTOR_NAME` varchar(100) DEFAULT NULL COMMENT '巡检员姓名快照',
+  `AUTO_COLLECT_FLAG` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否开启自动采集:1是,0否',
+  `INDICATOR_IDS` varchar(1000) NOT NULL COMMENT '计划选择的指标ID,逗号分隔',
+  `INDICATOR_NAMES` varchar(1000) DEFAULT NULL COMMENT '计划选择的指标名称快照,逗号分隔',
+  `STORE_COUNT` int(11) NOT NULL DEFAULT '0' COMMENT '关联门店数量',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0草稿,1已发布,2已暂停,3已结束,4已删除',
+  `PUBLISH_DATE` datetime DEFAULT NULL COMMENT '发布时间',
+  `FINISH_DATE` datetime DEFAULT NULL COMMENT '结束时间',
+  `RMK` varchar(500) DEFAULT NULL COMMENT '备注',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`PLAN_ID`),
+  KEY `IDX_INSPECT_PLAN_STATUS` (`STATUS`),
+  KEY `IDX_INSPECT_PLAN_CYCLE` (`CYCLE_TYPE`,`NEXT_EXECUTE_DATE`),
+  KEY `IDX_INSPECT_PLAN_DATE` (`START_DATE`,`END_DATE`),
+  KEY `IDX_INSPECT_PLAN_INSPECTOR` (`INSPECTOR_ID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡检计划表';
+
+DROP TABLE IF EXISTS `bu_inspect_plan_store`;
+CREATE TABLE `bu_inspect_plan_store` (
+  `PLAN_STORE_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '计划门店关联ID',
+  `PLAN_ID` bigint(20) NOT NULL COMMENT '巡检计划ID',
+  `STORE_ID` bigint(20) NOT NULL COMMENT '门店ID',
+  `STORE_NAME` varchar(150) DEFAULT NULL COMMENT '门店名称快照',
+  `STORE_BELONGTO` bigint(20) DEFAULT NULL COMMENT '归属校区ID快照',
+  `PROVINCE` bigint(20) DEFAULT NULL COMMENT '一级区域ID快照',
+  `CITY` bigint(20) DEFAULT NULL COMMENT '二级区域ID快照',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1有效,0删除',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  PRIMARY KEY (`PLAN_STORE_ID`),
+  UNIQUE KEY `UK_INSPECT_PLAN_STORE` (`PLAN_ID`,`STORE_ID`),
+  KEY `IDX_INSPECT_PLAN_STORE_STORE` (`STORE_ID`),
+  KEY `IDX_INSPECT_PLAN_STORE_AREA` (`STORE_BELONGTO`,`PROVINCE`,`CITY`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡检计划关联门店表';
+
+DROP TABLE IF EXISTS `bu_inspect_task`;
+CREATE TABLE `bu_inspect_task` (
+  `TASK_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '巡检任务ID',
+  `PLAN_ID` bigint(20) NOT NULL COMMENT '巡检计划ID',
+  `PLAN_NAME` varchar(150) DEFAULT NULL COMMENT '计划名称快照',
+  `STORE_ID` bigint(20) NOT NULL COMMENT '门店ID',
+  `STORE_NAME` varchar(150) DEFAULT NULL COMMENT '门店名称快照',
+  `EXECUTE_DATE` date NOT NULL COMMENT '计划执行日期',
+  `INSPECTOR_ID` bigint(20) DEFAULT NULL COMMENT '巡检员ID',
+  `INSPECTOR_NAME` varchar(100) DEFAULT NULL COMMENT '巡检员姓名快照',
+  `TOTAL_SCORE` decimal(10,2) DEFAULT NULL COMMENT '加权总分',
+  `MAX_SCORE` decimal(10,2) DEFAULT NULL COMMENT '加权满分',
+  `PASS_SCORE` decimal(10,2) DEFAULT NULL COMMENT '加权及格分',
+  `PASS_FLAG` tinyint(4) DEFAULT NULL COMMENT '是否合格:1合格,0不合格',
+  `UNPASS_ITEM_COUNT` int(11) NOT NULL DEFAULT '0' COMMENT '不合格指标数量',
+  `START_DATE` datetime DEFAULT NULL COMMENT '开始执行时间',
+  `SUBMIT_DATE` datetime DEFAULT NULL COMMENT '提交时间',
+  `CANCEL_DATE` datetime DEFAULT NULL COMMENT '取消时间',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1待执行,2执行中,3已完成,4已取消',
+  `RMK` varchar(500) DEFAULT NULL COMMENT '任务备注',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`TASK_ID`),
+  UNIQUE KEY `UK_INSPECT_TASK_PLAN_STORE_DATE` (`PLAN_ID`,`STORE_ID`,`EXECUTE_DATE`),
+  KEY `IDX_INSPECT_TASK_STORE` (`STORE_ID`),
+  KEY `IDX_INSPECT_TASK_STATUS_DATE` (`STATUS`,`EXECUTE_DATE`),
+  KEY `IDX_INSPECT_TASK_INSPECTOR` (`INSPECTOR_ID`,`STATUS`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡检任务表';
+
+DROP TABLE IF EXISTS `bu_inspect_task_item`;
+CREATE TABLE `bu_inspect_task_item` (
+  `TASK_ITEM_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '巡检任务明细ID',
+  `TASK_ID` bigint(20) NOT NULL COMMENT '巡检任务ID',
+  `PLAN_ID` bigint(20) NOT NULL COMMENT '巡检计划ID',
+  `STORE_ID` bigint(20) NOT NULL COMMENT '门店ID',
+  `INDICATOR_ID` bigint(20) NOT NULL COMMENT '考核指标ID',
+  `INDICATOR_CODE` varchar(64) NOT NULL COMMENT '指标编码快照',
+  `INDICATOR_NAME` varchar(100) NOT NULL COMMENT '指标名称快照',
+  `INDICATOR_TYPE` tinyint(4) NOT NULL COMMENT '指标类型:1自动采集,2人工巡检',
+  `DATA_SOURCE` varchar(64) DEFAULT NULL COMMENT '自动采集数据来源快照',
+  `WEIGHT` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '权重百分比快照',
+  `MAX_SCORE` decimal(10,2) NOT NULL DEFAULT '100.00' COMMENT '满分快照',
+  `PASS_SCORE` decimal(10,2) NOT NULL DEFAULT '60.00' COMMENT '及格分快照',
+  `RAW_VALUE` decimal(12,4) DEFAULT NULL COMMENT '自动采集原始值',
+  `RAW_VALUE_TEXT` varchar(100) DEFAULT NULL COMMENT '自动采集原始值展示文本,如15分钟、92%',
+  `SCORE` decimal(10,2) DEFAULT NULL COMMENT '指标得分',
+  `WEIGHTED_SCORE` decimal(10,2) DEFAULT NULL COMMENT '加权得分',
+  `PASS_FLAG` tinyint(4) DEFAULT NULL COMMENT '单项是否合格:1合格,0不合格',
+  `PICTURE_URLS` varchar(2000) DEFAULT NULL COMMENT '巡检照片URL,逗号分隔',
+  `RMK` varchar(1000) DEFAULT NULL COMMENT '评分备注',
+  `SORT` int(11) NOT NULL DEFAULT '0' COMMENT '排序序号',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1有效,0删除',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`TASK_ITEM_ID`),
+  UNIQUE KEY `UK_INSPECT_TASK_ITEM_INDICATOR` (`TASK_ID`,`INDICATOR_ID`),
+  KEY `IDX_INSPECT_TASK_ITEM_STORE` (`STORE_ID`),
+  KEY `IDX_INSPECT_TASK_ITEM_INDICATOR` (`INDICATOR_ID`),
+  KEY `IDX_INSPECT_TASK_ITEM_PASS` (`PASS_FLAG`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡检任务评分明细表';
+
+DROP TABLE IF EXISTS `bu_rectification`;
+CREATE TABLE `bu_rectification` (
+  `RECT_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '整改记录ID',
+  `TASK_ID` bigint(20) NOT NULL COMMENT '巡检任务ID',
+  `TASK_ITEM_ID` bigint(20) NOT NULL COMMENT '不合格任务明细ID',
+  `PLAN_ID` bigint(20) DEFAULT NULL COMMENT '巡检计划ID',
+  `STORE_ID` bigint(20) NOT NULL COMMENT '门店ID',
+  `STORE_NAME` varchar(150) DEFAULT NULL COMMENT '门店名称快照',
+  `INDICATOR_ID` bigint(20) NOT NULL COMMENT '指标ID',
+  `INDICATOR_NAME` varchar(100) NOT NULL COMMENT '指标名称快照',
+  `RECT_ROUND` int(11) NOT NULL DEFAULT '1' COMMENT '整改轮次',
+  `PARENT_RECT_ID` bigint(20) DEFAULT NULL COMMENT '上一轮整改ID',
+  `INSPECT_SCORE` decimal(10,2) DEFAULT NULL COMMENT '巡检得分',
+  `PASS_SCORE` decimal(10,2) DEFAULT NULL COMMENT '及格分',
+  `NOTICE_CONTENT` varchar(1000) DEFAULT NULL COMMENT '整改通知内容',
+  `DEADLINE_DATE` datetime NOT NULL COMMENT '整改截止时间',
+  `START_DATE` datetime DEFAULT NULL COMMENT '门店开始整改时间',
+  `FEEDBACK_CONTENT` varchar(1000) DEFAULT NULL COMMENT '门店整改反馈说明',
+  `FEEDBACK_PICTURES` varchar(2000) DEFAULT NULL COMMENT '门店整改照片URL,逗号分隔',
+  `FEEDBACK_DATE` datetime DEFAULT NULL COMMENT '门店提交反馈时间',
+  `RECHECK_SCORE` decimal(10,2) DEFAULT NULL COMMENT '复检得分',
+  `RECHECK_RESULT` tinyint(4) DEFAULT NULL COMMENT '复检结果:1通过,0不通过',
+  `RECHECK_PICTURES` varchar(2000) DEFAULT NULL COMMENT '复检照片URL,逗号分隔',
+  `RECHECK_RMK` varchar(1000) DEFAULT NULL COMMENT '复检备注',
+  `RECHECK_USER_ID` bigint(20) DEFAULT NULL COMMENT '复检员ID',
+  `RECHECK_USER_NAME` varchar(100) DEFAULT NULL COMMENT '复检员姓名快照',
+  `RECHECK_DATE` datetime DEFAULT NULL COMMENT '复检时间',
+  `URGE_COUNT` int(11) NOT NULL DEFAULT '0' COMMENT '催办次数',
+  `LAST_URGE_DATE` datetime DEFAULT NULL COMMENT '最后催办时间',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1待整改,2整改中,3待复检,4复检通过,5复检不通过,6已超期,7最终不通过',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`RECT_ID`),
+  KEY `IDX_RECT_TASK_ITEM` (`TASK_ITEM_ID`),
+  KEY `IDX_RECT_STORE_STATUS` (`STORE_ID`,`STATUS`),
+  KEY `IDX_RECT_INDICATOR` (`INDICATOR_ID`),
+  KEY `IDX_RECT_DEADLINE` (`STATUS`,`DEADLINE_DATE`),
+  KEY `IDX_RECT_PARENT` (`PARENT_RECT_ID`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='巡检整改记录表';
+
+DROP TABLE IF EXISTS `bu_assess_result`;
+CREATE TABLE `bu_assess_result` (
+  `ASSESS_RESULT_ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '考核结果ID',
+  `STORE_ID` bigint(20) NOT NULL COMMENT '门店ID',
+  `STORE_NAME` varchar(150) DEFAULT NULL COMMENT '门店名称快照',
+  `STORE_BELONGTO` bigint(20) DEFAULT NULL COMMENT '归属校区ID快照',
+  `PROVINCE` bigint(20) DEFAULT NULL COMMENT '一级区域ID快照',
+  `CITY` bigint(20) DEFAULT NULL COMMENT '二级区域ID快照',
+  `PERIOD_TYPE` tinyint(4) NOT NULL COMMENT '考核周期类型:1周度,2月度',
+  `PERIOD_CODE` varchar(20) NOT NULL COMMENT '周期编码,如2026W31、202607',
+  `PERIOD_START_DATE` date NOT NULL COMMENT '周期开始日期',
+  `PERIOD_END_DATE` date NOT NULL COMMENT '周期结束日期',
+  `INSPECT_SCORE` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '巡检平均分',
+  `AUTO_SCORE` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '自动采集平均分',
+  `TOTAL_SCORE` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '综合得分',
+  `ASSESS_LEVEL` tinyint(4) NOT NULL DEFAULT '4' COMMENT '考核等级:1优秀,2良好,3合格,4不合格',
+  `RANK_NO` int(11) DEFAULT NULL COMMENT '排名',
+  `TASK_COUNT` int(11) NOT NULL DEFAULT '0' COMMENT '周期内巡检任务数',
+  `FINISH_TASK_COUNT` int(11) NOT NULL DEFAULT '0' COMMENT '周期内已完成任务数',
+  `RECT_COUNT` int(11) NOT NULL DEFAULT '0' COMMENT '周期内整改次数',
+  `OVERTIME_RECT_COUNT` int(11) NOT NULL DEFAULT '0' COMMENT '周期内超期整改次数',
+  `SUMMARY_DATE` datetime NOT NULL COMMENT '汇总时间',
+  `STATUS` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1有效,0删除',
+  `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `UPDATE_DATE` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`ASSESS_RESULT_ID`),
+  UNIQUE KEY `UK_ASSESS_STORE_PERIOD` (`STORE_ID`,`PERIOD_TYPE`,`PERIOD_CODE`),
+  KEY `IDX_ASSESS_PERIOD_RANK` (`PERIOD_TYPE`,`PERIOD_CODE`,`RANK_NO`),
+  KEY `IDX_ASSESS_AREA` (`STORE_BELONGTO`,`PROVINCE`,`CITY`),
+  KEY `IDX_ASSESS_LEVEL` (`ASSESS_LEVEL`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='门店考核结果与排名表';
+
+INSERT INTO `bu_inspect_indicator`
+  (`INDICATOR_CODE`, `INDICATOR_NAME`, `INDICATOR_TYPE`, `DATA_SOURCE`, `MAX_SCORE`, `PASS_SCORE`, `WEIGHT`, `SCORE_RULE`, `SORT`, `STATUS`, `RMK`)
+VALUES
+  ('COOK_TIME', '出餐时效', 1, 'ORDER_COOK_TIME', 100.00, 60.00, 20.00,
+   '{"type":"range","ranges":[{"min":0,"max":10,"score":100},{"min":10,"max":20,"score":80},{"min":20,"max":30,"score":60},{"min":30,"max":999,"score":40}],"unit":"分钟","description":"出餐时效:0-10分钟100分,10-20分钟80分,20-30分钟60分,30分钟以上40分"}',
+   10, 1, '系统预置自动采集指标'),
+  ('GOOD_RATE', '好评率', 1, 'EVALUATE_GOOD_RATE', 100.00, 60.00, 20.00,
+   '{"type":"percentage","thresholds":[{"min":95,"score":100},{"min":90,"score":80},{"min":80,"score":60},{"min":0,"score":40}],"unit":"%","description":"好评率:大于等于95%得100分,大于等于90%得80分,大于等于80%得60分,低于80%得40分"}',
+   20, 1, '系统预置自动采集指标'),
+  ('REJECT_RATE', '拒单率', 1, 'ORDER_REJECT_RATE', 100.00, 60.00, 15.00,
+   '{"type":"percentage_reverse","thresholds":[{"max":0,"score":100},{"max":2,"score":80},{"max":5,"score":60},{"max":100,"score":40}],"unit":"%","description":"拒单率:0%得100分,小于等于2%得80分,小于等于5%得60分,大于5%得40分"}',
+   30, 1, '系统预置自动采集指标'),
+  ('OVERTIME_RATE', '超时率', 1, 'ORDER_OVERTIME_RATE', 100.00, 60.00, 15.00,
+   '{"type":"percentage_reverse","thresholds":[{"max":0,"score":100},{"max":3,"score":80},{"max":8,"score":60},{"max":100,"score":40}],"unit":"%","description":"超时率:0%得100分,小于等于3%得80分,小于等于8%得60分,大于8%得40分"}',
+   40, 1, '系统预置自动采集指标'),
+  ('HYGIENE', '卫生评分', 2, NULL, 100.00, 60.00, 15.00, NULL, 50, 1, '系统预置人工巡检指标'),
+  ('SERVICE', '服务态度', 2, NULL, 100.00, 60.00, 10.00, NULL, 60, 1, '系统预置人工巡检指标'),
+  ('PACKAGING', '包装规范', 2, NULL, 100.00, 60.00, 5.00, NULL, 70, 1, '系统预置人工巡检指标');
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 23 - 0
docs/inspect-seed-indicators.sql

@@ -0,0 +1,23 @@
+-- Seed default inspection indicators for the current optimized schema.
+-- Run in database `eduepos`. Idempotent (unique on INDICATOR_CODE).
+SET NAMES utf8mb4;
+
+INSERT IGNORE INTO `bu_inspect_indicator`
+  (`INDICATOR_CODE`, `INDICATOR_NAME`, `INDICATOR_TYPE`, `DATA_SOURCE`, `MAX_SCORE`, `PASS_SCORE`, `WEIGHT`, `SCORE_RULE`, `SORT`, `STATUS`, `RMK`)
+VALUES
+  ('COOK_TIME', '鍑洪鏃舵晥', 1, 'ORDER_COOK_TIME', 100.00, 60.00, 20.00,
+   '{"type":"range","ranges":[{"min":0,"max":10,"score":100},{"min":10,"max":20,"score":80},{"min":20,"max":30,"score":60},{"min":30,"max":999,"score":40}],"unit":"鍒嗛挓","description":"鍑洪鏃舵晥锛?-10鍒嗛挓100鍒嗭紝10-20鍒嗛挓80鍒嗭紝20-30鍒嗛挓60鍒嗭紝30鍒嗛挓浠ヤ笂40鍒?}',
+   10, 1, '绯荤粺棰勭疆鑷姩閲囬泦鎸囨爣'),
+  ('GOOD_RATE', '好评率'EVALUATE_GOOD_RATE', 100.00, 60.00, 20.00,
+   '{"type":"percentage","thresholds":[{"min":95,"score":100},{"min":90,"score":80},{"min":80,"score":60},{"min":0,"score":40}],"unit":"%","description":"濂借瘎鐜囷細澶т簬绛変簬95%寰?00鍒嗭紝澶т簬绛変簬90%寰?0鍒嗭紝澶т簬绛変簬80%寰?0鍒嗭紝浣庝簬80%寰?0鍒?}',
+   20, 1, '绯荤粺棰勭疆鑷姩閲囬泦鎸囨爣'),
+  ('REJECT_RATE', '拒单率'ORDER_REJECT_RATE', 100.00, 60.00, 15.00,
+   '{"type":"percentage_reverse","thresholds":[{"max":0,"score":100},{"max":2,"score":80},{"max":5,"score":60},{"max":100,"score":40}],"unit":"%","description":"鎷掑崟鐜囷細0%寰?00鍒嗭紝灏忎簬绛変簬2%寰?0鍒嗭紝灏忎簬绛変簬5%寰?0鍒嗭紝澶т簬5%寰?0鍒?}',
+   30, 1, '绯荤粺棰勭疆鑷姩閲囬泦鎸囨爣'),
+  ('OVERTIME_RATE', '超时率'ORDER_OVERTIME_RATE', 100.00, 60.00, 15.00,
+   '{"type":"percentage_reverse","thresholds":[{"max":0,"score":100},{"max":3,"score":80},{"max":8,"score":60},{"max":100,"score":40}],"unit":"%","description":"瓒呮椂鐜囷細0%寰?00鍒嗭紝灏忎簬绛変簬3%寰?0鍒嗭紝灏忎簬绛変簬8%寰?0鍒嗭紝澶т簬8%寰?0鍒?}',
+   40, 1, '绯荤粺棰勭疆鑷姩閲囬泦鎸囨爣'),
+  ('HYGIENE', '鍗敓璇勫垎', 2, NULL, 100.00, 60.00, 15.00, NULL, 50, 1, '绯荤粺棰勭疆浜哄伐宸℃鎸囨爣'),
+  ('SERVICE', '鏈嶅姟鎬佸害', 2, NULL, 100.00, 60.00, 10.00, NULL, 60, 1, '绯荤粺棰勭疆浜哄伐宸℃鎸囨爣'),
+  ('PACKAGING', '鍖呰瑙勮寖', 2, NULL, 100.00, 60.00, 5.00, NULL, 70, 1, '绯荤粺棰勭疆浜哄伐宸℃鎸囨爣');
+

+ 300 - 0
docs/inspect-test-data.sql

@@ -0,0 +1,300 @@
+-- 门店巡检与考核 测试数据(严格遵循 PRD 业务规则,仅使用实际表存在的列)
+-- 运行库:eduepos。可重复执行(先清理同名/同期测试数据再写入)。
+-- 规则引用:I02-R01 结束>开始;I02-R02/R03 至少1指标1门店;I02-R04 发布时开始不早于当前;
+--          I03-R04 加权总分=Σ(得分×权重/100);I03-R05 及格=总分≥及格线;
+--          I04-R02 截止=提交+7天;I04-R03 复检不通过生成下一轮;
+--          I05-R01 综合=巡检×0.6+自动×0.4;I05-R03 等级 优秀≥90/良好≥80/合格≥60/不合格<60。
+
+SET NAMES utf8mb4;
+
+-- ============ 0. 清理既有测试数据(幂等) ============
+DELETE FROM bu_inspect_task_item
+WHERE TASK_ID IN (SELECT TASK_ID FROM bu_inspect_task
+                  WHERE PLAN_ID IN (SELECT PLAN_ID FROM bu_inspect_plan WHERE PLAN_NAME IN ('2026年8月食品安全常规巡检','暑期卫生专项巡检(草稿)')));
+DELETE FROM bu_rectification
+WHERE PLAN_ID IN (SELECT PLAN_ID FROM bu_inspect_plan WHERE PLAN_NAME IN ('2026年8月食品安全常规巡检','暑期卫生专项巡检(草稿)'));
+DELETE FROM bu_inspect_task
+WHERE PLAN_ID IN (SELECT PLAN_ID FROM bu_inspect_plan WHERE PLAN_NAME IN ('2026年8月食品安全常规巡检','暑期卫生专项巡检(草稿)'));
+DELETE FROM bu_inspect_plan_store
+WHERE PLAN_ID IN (SELECT PLAN_ID FROM bu_inspect_plan WHERE PLAN_NAME IN ('2026年8月食品安全常规巡检','暑期卫生专项巡检(草稿)'));
+DELETE FROM bu_inspect_plan_indicator
+WHERE PLAN_ID IN (SELECT PLAN_ID FROM bu_inspect_plan WHERE PLAN_NAME IN ('2026年8月食品安全常规巡检','暑期卫生专项巡检(草稿)'));
+DELETE FROM bu_inspect_plan WHERE PLAN_NAME IN ('2026年8月食品安全常规巡检','暑期卫生专项巡检(草稿)');
+DELETE FROM bu_assess_result WHERE PERIOD_CODE = '2026-08';
+
+-- ============ 1. 已发布计划:2026年8月食品安全常规巡检 ============
+-- 常规巡检(1)、每周(2)、08-01~09-30、下次执行08-22、3家门店、已发布(1)
+INSERT INTO bu_inspect_plan
+  (PLAN_NAME, PLAN_TYPE, CYCLE_TYPE, START_DATE, END_DATE, NEXT_EXECUTE_DATE,
+   INSPECTOR_ID, INSPECTOR_NAME, AUTO_COLLECT_FLAG, STORE_COUNT, STATUS,
+   PUBLISH_DATE, FINISH_DATE, RMK, CREATE_DATE)
+VALUES
+  ('2026年8月食品安全常规巡检', 1, 2, '2026-08-01', '2026-09-30', '2026-08-22',
+   1, '管理员', 1, 3, 1,
+   '2026-07-31 09:00:00', NULL, '每周常规巡检,覆盖7项考核指标、3家门店', NOW());
+SET @plan1 = LAST_INSERT_ID();
+
+-- ============ 2. 草稿计划:暑期卫生专项巡检(未发布,权重未达100%符合草稿状态) ============
+INSERT INTO bu_inspect_plan
+  (PLAN_NAME, PLAN_TYPE, CYCLE_TYPE, START_DATE, END_DATE, NEXT_EXECUTE_DATE,
+   INSPECTOR_ID, INSPECTOR_NAME, AUTO_COLLECT_FLAG, STORE_COUNT, STATUS,
+   PUBLISH_DATE, FINISH_DATE, RMK, CREATE_DATE)
+VALUES
+  ('暑期卫生专项巡检(草稿)', 2, 3, '2026-08-15', '2026-09-15', NULL,
+   1, '管理员', 1, 2, 0,
+   NULL, NULL, '草稿计划:仅勾选人工巡检指标,权重合计30%(未达发布要求100%)', NOW());
+SET @plan2 = LAST_INSERT_ID();
+
+-- ============ 3. 计划关联指标 ============
+-- I02-R02:至少1个指标。计划1关联全部7项(权重和=100,满足发布校验);计划2仅3项人工指标
+INSERT INTO bu_inspect_plan_indicator
+  (PLAN_ID, INDICATOR_ID, INDICATOR_NAME, INDICATOR_TYPE, MAX_SCORE, PASS_SCORE, WEIGHT, SCORE_RULE, SORT, STATUS, CREATE_DATE)
+SELECT @plan1, INDICATOR_ID, INDICATOR_NAME, INDICATOR_TYPE, MAX_SCORE, PASS_SCORE, WEIGHT, SCORE_RULE, SORT, 1, NOW()
+FROM bu_inspect_indicator WHERE STATUS = 1;
+
+INSERT INTO bu_inspect_plan_indicator
+  (PLAN_ID, INDICATOR_ID, INDICATOR_NAME, INDICATOR_TYPE, MAX_SCORE, PASS_SCORE, WEIGHT, SCORE_RULE, SORT, STATUS, CREATE_DATE)
+SELECT @plan2, INDICATOR_ID, INDICATOR_NAME, INDICATOR_TYPE, MAX_SCORE, PASS_SCORE, WEIGHT, SCORE_RULE, SORT, 1, NOW()
+FROM bu_inspect_indicator WHERE INDICATOR_CODE IN ('HYGIENE','SERVICE','PACKAGING');
+
+-- ============ 4. 计划关联门店(I02-R03:至少1家门店) ============
+INSERT INTO bu_inspect_plan_store (PLAN_ID, STORE_ID, STORE_NAME, STORE_BELONGTO, PROVINCE, CITY, STATUS, CREATE_DATE)
+SELECT @plan1, STORE_ID, STORE_NAME, STORE_BELONGTO, PROVINCE, CITY, 1, NOW()
+FROM bu_store WHERE STORE_ID IN (1,2,3);
+
+INSERT INTO bu_inspect_plan_store (PLAN_ID, STORE_ID, STORE_NAME, STORE_BELONGTO, PROVINCE, CITY, STATUS, CREATE_DATE)
+SELECT @plan2, STORE_ID, STORE_NAME, STORE_BELONGTO, PROVINCE, CITY, 1, NOW()
+FROM bu_store WHERE STORE_ID IN (1,2);
+
+-- ============ 5. 生成巡检任务(I03-R01:每个门店每个执行日期一条任务) ============
+-- 任务日期 2026-08-01(计划首个执行日)
+-- T1 门店1 特色美食店666:已提交、不及格(卫生/服务 2项不达标)
+INSERT INTO bu_inspect_task
+  (PLAN_ID, PLAN_NAME, STORE_ID, STORE_NAME, TASK_DATE, INSPECTOR_ID, INSPECTOR_NAME,
+   TOTAL_SCORE, MAX_SCORE, PASS_SCORE, PASS_FLAG, UNPASS_ITEM_COUNT,
+   START_DATE, SUBMIT_DATE, CANCEL_DATE, STATUS, RMK, CREATE_DATE)
+VALUES
+  (@plan1, '2026年8月食品安全常规巡检', 1, '特色美食店666', '2026-08-01', 1, '管理员',
+   72.25, 100.00, 60.00, 0, 2,
+   '2026-08-01 09:30:00', '2026-08-01 11:00:00', NULL, 3, '卫生、服务未达标,已自动生成整改', NOW());
+SET @t1 = LAST_INSERT_ID();
+
+-- T2 门店2 水饺店铺:已提交、不及格(卫生/服务/包装 3项不达标)
+INSERT INTO bu_inspect_task
+  (PLAN_ID, PLAN_NAME, STORE_ID, STORE_NAME, TASK_DATE, INSPECTOR_ID, INSPECTOR_NAME,
+   TOTAL_SCORE, MAX_SCORE, PASS_SCORE, PASS_FLAG, UNPASS_ITEM_COUNT,
+   START_DATE, SUBMIT_DATE, CANCEL_DATE, STATUS, RMK, CREATE_DATE)
+VALUES
+  (@plan1, '2026年8月食品安全常规巡检', 2, '水饺店铺', '2026-08-01', 1, '管理员',
+   70.00, 100.00, 60.00, 0, 3,
+   '2026-08-01 09:40:00', '2026-08-01 11:20:00', NULL, 3, '卫生、服务、包装未达标,已自动生成整改', NOW());
+SET @t2 = LAST_INSERT_ID();
+
+-- T3 门店3 烤肉拌饭:已提交、及格(全部达标)
+INSERT INTO bu_inspect_task
+  (PLAN_ID, PLAN_NAME, STORE_ID, STORE_NAME, TASK_DATE, INSPECTOR_ID, INSPECTOR_NAME,
+   TOTAL_SCORE, MAX_SCORE, PASS_SCORE, PASS_FLAG, UNPASS_ITEM_COUNT,
+   START_DATE, SUBMIT_DATE, CANCEL_DATE, STATUS, RMK, CREATE_DATE)
+VALUES
+  (@plan1, '2026年8月食品安全常规巡检', 3, '烤肉拌饭', '2026-08-01', 1, '管理员',
+   81.25, 100.00, 60.00, 1, 0,
+   '2026-08-01 10:00:00', '2026-08-01 11:40:00', NULL, 3, '全部指标达标', NOW());
+SET @t3 = LAST_INSERT_ID();
+
+-- ============ 6. 任务评分明细(I03-R02 自动采集预填;I03-R03 人工打分;I03-R04 加权) ============
+-- T1 门店1:自动项全80分;人工 卫生50(不达标) 服务45(不达标) 包装85
+INSERT INTO bu_inspect_task_item
+  (TASK_ID, PLAN_ID, STORE_ID, PLAN_INDICATOR_ID, INDICATOR_ID, SCORE_LEVEL,
+   INDICATOR_CODE, INDICATOR_NAME, INDICATOR_TYPE, DATA_SOURCE,
+   WEIGHT, MAX_SCORE, PASS_SCORE, RAW_VALUE, RAW_VALUE_TEXT, SCORE, WEIGHTED_SCORE, PASS_FLAG,
+   PICTURE_URLS, RMK, SORT, STATUS, CREATE_DATE)
+SELECT
+  @t1, @plan1, 1, pi.PLAN_INDICATOR_ID, pi.INDICATOR_ID, 2,
+  i.INDICATOR_CODE, i.INDICATOR_NAME, i.INDICATOR_TYPE, i.DATA_SOURCE,
+  pi.WEIGHT, pi.MAX_SCORE, pi.PASS_SCORE,
+  CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 15 WHEN 'GOOD_RATE' THEN 92 WHEN 'REJECT_RATE' THEN 1.2 WHEN 'OVERTIME_RATE' THEN 2.5 END,
+  CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN '15分钟' WHEN 'GOOD_RATE' THEN '92%' WHEN 'REJECT_RATE' THEN '1.2%' WHEN 'OVERTIME_RATE' THEN '2.5%' END,
+  CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 80 WHEN 'GOOD_RATE' THEN 80 WHEN 'REJECT_RATE' THEN 80 WHEN 'OVERTIME_RATE' THEN 80
+                        WHEN 'HYGIENE' THEN 50 WHEN 'SERVICE' THEN 45 WHEN 'PACKAGING' THEN 85 END,
+  ROUND(CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 80 WHEN 'GOOD_RATE' THEN 80 WHEN 'REJECT_RATE' THEN 80 WHEN 'OVERTIME_RATE' THEN 80
+                             WHEN 'HYGIENE' THEN 50 WHEN 'SERVICE' THEN 45 WHEN 'PACKAGING' THEN 85 END * pi.WEIGHT / 100, 2),
+  CASE WHEN (CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 80 WHEN 'GOOD_RATE' THEN 80 WHEN 'REJECT_RATE' THEN 80 WHEN 'OVERTIME_RATE' THEN 80
+                                  WHEN 'HYGIENE' THEN 50 WHEN 'SERVICE' THEN 45 WHEN 'PACKAGING' THEN 85 END) >= pi.PASS_SCORE THEN 1 ELSE 0 END,
+  NULL,
+  CASE i.INDICATOR_CODE WHEN 'HYGIENE' THEN '厨房地面有油污' WHEN 'SERVICE' THEN '服务态度需提升' WHEN 'PACKAGING' THEN '包装不规范' END,
+  pi.SORT, 1, NOW()
+FROM bu_inspect_plan_indicator pi
+JOIN bu_inspect_indicator i ON i.INDICATOR_ID = pi.INDICATOR_ID
+WHERE pi.PLAN_ID = @plan1 AND pi.STATUS = 1;
+
+-- T2 门店2:自动项全80分;人工 卫生50(不达标) 服务45(不达标) 包装40(不达标)
+INSERT INTO bu_inspect_task_item
+  (TASK_ID, PLAN_ID, STORE_ID, PLAN_INDICATOR_ID, INDICATOR_ID, SCORE_LEVEL,
+   INDICATOR_CODE, INDICATOR_NAME, INDICATOR_TYPE, DATA_SOURCE,
+   WEIGHT, MAX_SCORE, PASS_SCORE, RAW_VALUE, RAW_VALUE_TEXT, SCORE, WEIGHTED_SCORE, PASS_FLAG,
+   PICTURE_URLS, RMK, SORT, STATUS, CREATE_DATE)
+SELECT
+  @t2, @plan1, 2, pi.PLAN_INDICATOR_ID, pi.INDICATOR_ID, 2,
+  i.INDICATOR_CODE, i.INDICATOR_NAME, i.INDICATOR_TYPE, i.DATA_SOURCE,
+  pi.WEIGHT, pi.MAX_SCORE, pi.PASS_SCORE,
+  CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 15 WHEN 'GOOD_RATE' THEN 92 WHEN 'REJECT_RATE' THEN 1.2 WHEN 'OVERTIME_RATE' THEN 2.5 END,
+  CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN '15分钟' WHEN 'GOOD_RATE' THEN '92%' WHEN 'REJECT_RATE' THEN '1.2%' WHEN 'OVERTIME_RATE' THEN '2.5%' END,
+  CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 80 WHEN 'GOOD_RATE' THEN 80 WHEN 'REJECT_RATE' THEN 80 WHEN 'OVERTIME_RATE' THEN 80
+                        WHEN 'HYGIENE' THEN 50 WHEN 'SERVICE' THEN 45 WHEN 'PACKAGING' THEN 40 END,
+  ROUND(CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 80 WHEN 'GOOD_RATE' THEN 80 WHEN 'REJECT_RATE' THEN 80 WHEN 'OVERTIME_RATE' THEN 80
+                             WHEN 'HYGIENE' THEN 50 WHEN 'SERVICE' THEN 45 WHEN 'PACKAGING' THEN 40 END * pi.WEIGHT / 100, 2),
+  CASE WHEN (CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 80 WHEN 'GOOD_RATE' THEN 80 WHEN 'REJECT_RATE' THEN 80 WHEN 'OVERTIME_RATE' THEN 80
+                                  WHEN 'HYGIENE' THEN 50 WHEN 'SERVICE' THEN 45 WHEN 'PACKAGING' THEN 40 END) >= pi.PASS_SCORE THEN 1 ELSE 0 END,
+  NULL,
+  CASE i.INDICATOR_CODE WHEN 'HYGIENE' THEN '食材未分类存放' WHEN 'SERVICE' THEN '态度冷淡' WHEN 'PACKAGING' THEN '外卖包装破损' END,
+  pi.SORT, 1, NOW()
+FROM bu_inspect_plan_indicator pi
+JOIN bu_inspect_indicator i ON i.INDICATOR_ID = pi.INDICATOR_ID
+WHERE pi.PLAN_ID = @plan1 AND pi.STATUS = 1;
+
+-- T3 门店3:自动项全80分;人工 卫生85 服务80 包装90(全部及格)
+INSERT INTO bu_inspect_task_item
+  (TASK_ID, PLAN_ID, STORE_ID, PLAN_INDICATOR_ID, INDICATOR_ID, SCORE_LEVEL,
+   INDICATOR_CODE, INDICATOR_NAME, INDICATOR_TYPE, DATA_SOURCE,
+   WEIGHT, MAX_SCORE, PASS_SCORE, RAW_VALUE, RAW_VALUE_TEXT, SCORE, WEIGHTED_SCORE, PASS_FLAG,
+   PICTURE_URLS, RMK, SORT, STATUS, CREATE_DATE)
+SELECT
+  @t3, @plan1, 3, pi.PLAN_INDICATOR_ID, pi.INDICATOR_ID, 2,
+  i.INDICATOR_CODE, i.INDICATOR_NAME, i.INDICATOR_TYPE, i.DATA_SOURCE,
+  pi.WEIGHT, pi.MAX_SCORE, pi.PASS_SCORE,
+  CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 15 WHEN 'GOOD_RATE' THEN 92 WHEN 'REJECT_RATE' THEN 1.2 WHEN 'OVERTIME_RATE' THEN 2.5 END,
+  CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN '15分钟' WHEN 'GOOD_RATE' THEN '92%' WHEN 'REJECT_RATE' THEN '1.2%' WHEN 'OVERTIME_RATE' THEN '2.5%' END,
+  CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 80 WHEN 'GOOD_RATE' THEN 80 WHEN 'REJECT_RATE' THEN 80 WHEN 'OVERTIME_RATE' THEN 80
+                        WHEN 'HYGIENE' THEN 85 WHEN 'SERVICE' THEN 80 WHEN 'PACKAGING' THEN 90 END,
+  ROUND(CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 80 WHEN 'GOOD_RATE' THEN 80 WHEN 'REJECT_RATE' THEN 80 WHEN 'OVERTIME_RATE' THEN 80
+                             WHEN 'HYGIENE' THEN 85 WHEN 'SERVICE' THEN 80 WHEN 'PACKAGING' THEN 90 END * pi.WEIGHT / 100, 2),
+  CASE WHEN (CASE i.INDICATOR_CODE WHEN 'COOK_TIME' THEN 80 WHEN 'GOOD_RATE' THEN 80 WHEN 'REJECT_RATE' THEN 80 WHEN 'OVERTIME_RATE' THEN 80
+                                  WHEN 'HYGIENE' THEN 85 WHEN 'SERVICE' THEN 80 WHEN 'PACKAGING' THEN 90 END) >= pi.PASS_SCORE THEN 1 ELSE 0 END,
+  NULL,
+  CASE i.INDICATOR_CODE WHEN 'HYGIENE' THEN '卫生状况良好' WHEN 'SERVICE' THEN '服务热情' WHEN 'PACKAGING' THEN '包装规范' END,
+  pi.SORT, 1, NOW()
+FROM bu_inspect_plan_indicator pi
+JOIN bu_inspect_indicator i ON i.INDICATOR_ID = pi.INDICATOR_ID
+WHERE pi.PLAN_ID = @plan1 AND pi.STATUS = 1;
+
+-- ============ 7. 整改记录(I04-R01 不及格项生成整改;I04-R02 截止=提交+7天) ============
+-- R1 门店1-卫生 第1轮:复检不通过(5) → 生成第2轮
+INSERT INTO bu_rectification
+  (TASK_ID, TASK_ITEM_ID, PLAN_ID, STORE_ID, STORE_NAME, INDICATOR_ID, INDICATOR_NAME,
+   RECT_ROUND, PARENT_RECT_ID, INSPECT_SCORE, PASS_SCORE, NOTICE_CONTENT, DEADLINE_DATE, START_DATE,
+   FEEDBACK_CONTENT, FEEDBACK_PICTURES, FEEDBACK_DATE,
+   RECHECK_SCORE, RECHECK_RESULT, RECHECK_PICTURES, RECHECK_RMK, RECHECK_USER_ID, RECHECK_USER_NAME, RECHECK_DATE,
+   URGE_COUNT, LAST_URGE_DATE, STATUS, CREATE_DATE)
+VALUES
+  (@t1, (SELECT TASK_ITEM_ID FROM bu_inspect_task_item WHERE TASK_ID=@t1 AND INDICATOR_CODE='HYGIENE'),
+   @plan1, 1, '特色美食店666', (SELECT INDICATOR_ID FROM bu_inspect_indicator WHERE INDICATOR_CODE='HYGIENE'), '卫生评分',
+   1, NULL, 50, 60, '指标【卫生评分】未达标,请完成整改', '2026-08-08 00:00:00', '2026-08-02 10:00:00',
+   '已全面清洁厨房,更换防滑垫', NULL, '2026-08-04 14:00:00',
+   50, 0, NULL, '厨房仍不达标,储物间有杂物', 1, '管理员', '2026-08-05 09:00:00',
+   1, '2026-08-03 10:00:00', 5, NOW());
+SET @r1 = LAST_INSERT_ID();
+
+-- R2 门店1-卫生 第2轮:待整改(1)(I04-R03 复检不通过自动生成下一轮)
+INSERT INTO bu_rectification
+  (TASK_ID, TASK_ITEM_ID, PLAN_ID, STORE_ID, STORE_NAME, INDICATOR_ID, INDICATOR_NAME,
+   RECT_ROUND, PARENT_RECT_ID, INSPECT_SCORE, PASS_SCORE, NOTICE_CONTENT, DEADLINE_DATE, START_DATE,
+   FEEDBACK_CONTENT, FEEDBACK_PICTURES, FEEDBACK_DATE,
+   RECHECK_SCORE, RECHECK_RESULT, RECHECK_PICTURES, RECHECK_RMK, RECHECK_USER_ID, RECHECK_USER_NAME, RECHECK_DATE,
+   URGE_COUNT, LAST_URGE_DATE, STATUS, CREATE_DATE)
+VALUES
+  (@t1, (SELECT TASK_ITEM_ID FROM bu_inspect_task_item WHERE TASK_ID=@t1 AND INDICATOR_CODE='HYGIENE'),
+   @plan1, 1, '特色美食店666', (SELECT INDICATOR_ID FROM bu_inspect_indicator WHERE INDICATOR_CODE='HYGIENE'), '卫生评分',
+   2, @r1, 50, 60, '指标【卫生评分】仍未达标,请继续整改', '2026-08-28 00:00:00', NULL,
+   NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   0, NULL, 1, NOW());
+
+-- R3 门店1-服务 第1轮:整改中(2)
+INSERT INTO bu_rectification
+  (TASK_ID, TASK_ITEM_ID, PLAN_ID, STORE_ID, STORE_NAME, INDICATOR_ID, INDICATOR_NAME,
+   RECT_ROUND, PARENT_RECT_ID, INSPECT_SCORE, PASS_SCORE, NOTICE_CONTENT, DEADLINE_DATE, START_DATE,
+   FEEDBACK_CONTENT, FEEDBACK_PICTURES, FEEDBACK_DATE,
+   RECHECK_SCORE, RECHECK_RESULT, RECHECK_PICTURES, RECHECK_RMK, RECHECK_USER_ID, RECHECK_USER_NAME, RECHECK_DATE,
+   URGE_COUNT, LAST_URGE_DATE, STATUS, CREATE_DATE)
+VALUES
+  (@t1, (SELECT TASK_ITEM_ID FROM bu_inspect_task_item WHERE TASK_ID=@t1 AND INDICATOR_CODE='SERVICE'),
+   @plan1, 1, '特色美食店666', (SELECT INDICATOR_ID FROM bu_inspect_indicator WHERE INDICATOR_CODE='SERVICE'), '服务态度',
+   1, NULL, 45, 60, '指标【服务态度】未达标,请完成整改', '2026-08-28 00:00:00', '2026-08-02 11:00:00',
+   NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   0, NULL, 2, NOW());
+
+-- R4 门店2-包装 第1轮:待复检(3)(门店已提交反馈,等待复检)
+INSERT INTO bu_rectification
+  (TASK_ID, TASK_ITEM_ID, PLAN_ID, STORE_ID, STORE_NAME, INDICATOR_ID, INDICATOR_NAME,
+   RECT_ROUND, PARENT_RECT_ID, INSPECT_SCORE, PASS_SCORE, NOTICE_CONTENT, DEADLINE_DATE, START_DATE,
+   FEEDBACK_CONTENT, FEEDBACK_PICTURES, FEEDBACK_DATE,
+   RECHECK_SCORE, RECHECK_RESULT, RECHECK_PICTURES, RECHECK_RMK, RECHECK_USER_ID, RECHECK_USER_NAME, RECHECK_DATE,
+   URGE_COUNT, LAST_URGE_DATE, STATUS, CREATE_DATE)
+VALUES
+  (@t2, (SELECT TASK_ITEM_ID FROM bu_inspect_task_item WHERE TASK_ID=@t2 AND INDICATOR_CODE='PACKAGING'),
+   @plan1, 2, '水饺店铺', (SELECT INDICATOR_ID FROM bu_inspect_indicator WHERE INDICATOR_CODE='PACKAGING'), '包装规范',
+   1, NULL, 40, 60, '指标【包装规范】未达标,请完成整改', '2026-08-28 00:00:00', '2026-08-02 10:30:00',
+   '已更换为环保密封包装', NULL, '2026-08-06 15:00:00',
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   0, NULL, 3, NOW());
+
+-- R5 门店2-卫生 第1轮:复检通过(4)
+INSERT INTO bu_rectification
+  (TASK_ID, TASK_ITEM_ID, PLAN_ID, STORE_ID, STORE_NAME, INDICATOR_ID, INDICATOR_NAME,
+   RECT_ROUND, PARENT_RECT_ID, INSPECT_SCORE, PASS_SCORE, NOTICE_CONTENT, DEADLINE_DATE, START_DATE,
+   FEEDBACK_CONTENT, FEEDBACK_PICTURES, FEEDBACK_DATE,
+   RECHECK_SCORE, RECHECK_RESULT, RECHECK_PICTURES, RECHECK_RMK, RECHECK_USER_ID, RECHECK_USER_NAME, RECHECK_DATE,
+   URGE_COUNT, LAST_URGE_DATE, STATUS, CREATE_DATE)
+VALUES
+  (@t2, (SELECT TASK_ITEM_ID FROM bu_inspect_task_item WHERE TASK_ID=@t2 AND INDICATOR_CODE='HYGIENE'),
+   @plan1, 2, '水饺店铺', (SELECT INDICATOR_ID FROM bu_inspect_indicator WHERE INDICATOR_CODE='HYGIENE'), '卫生评分',
+   1, NULL, 50, 60, '指标【卫生评分】未达标,请完成整改', '2026-08-28 00:00:00', '2026-08-02 09:00:00',
+   '已按要求完成卫生整改', NULL, '2026-08-05 16:00:00',
+   75, 1, NULL, '整改到位', 1, '管理员', '2026-08-07 10:00:00',
+   0, NULL, 4, NOW());
+
+-- R6 门店2-服务 第1轮:已超期(6)(截止日期已过且未完成整改)
+INSERT INTO bu_rectification
+  (TASK_ID, TASK_ITEM_ID, PLAN_ID, STORE_ID, STORE_NAME, INDICATOR_ID, INDICATOR_NAME,
+   RECT_ROUND, PARENT_RECT_ID, INSPECT_SCORE, PASS_SCORE, NOTICE_CONTENT, DEADLINE_DATE, START_DATE,
+   FEEDBACK_CONTENT, FEEDBACK_PICTURES, FEEDBACK_DATE,
+   RECHECK_SCORE, RECHECK_RESULT, RECHECK_PICTURES, RECHECK_RMK, RECHECK_USER_ID, RECHECK_USER_NAME, RECHECK_DATE,
+   URGE_COUNT, LAST_URGE_DATE, STATUS, CREATE_DATE)
+VALUES
+  (@t2, (SELECT TASK_ITEM_ID FROM bu_inspect_task_item WHERE TASK_ID=@t2 AND INDICATOR_CODE='SERVICE'),
+   @plan1, 2, '水饺店铺', (SELECT INDICATOR_ID FROM bu_inspect_indicator WHERE INDICATOR_CODE='SERVICE'), '服务态度',
+   1, NULL, 45, 60, '指标【服务态度】未达标,请完成整改', '2026-08-03 00:00:00', NULL,
+   NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   0, NULL, 6, NOW());
+
+-- ============ 8. 月度考核结果(I05-R01 综合=巡检×0.6+自动×0.4;I05-R03 等级;I05-R02 排名) ============
+-- 门店3 综合80.75 → 良好(2) 第1名;门店1 综合75.35 → 合格(3) 第2名;门店2 综合74.00 → 合格(3) 第3名
+INSERT INTO bu_assess_result
+  (STORE_ID, STORE_NAME, STORE_BELONGTO, PROVINCE, CITY,
+   PERIOD_TYPE, PERIOD_CODE, PERIOD_START_DATE, PERIOD_END_DATE,
+   INSPECT_SCORE, AUTO_SCORE, TOTAL_SCORE, ASSESS_LEVEL, RANK_NO,
+   TASK_COUNT, FINISH_TASK_COUNT, RECT_COUNT, OVERTIME_RECT_COUNT,
+   SUMMARY_DATE, STATUS, CREATE_DATE)
+VALUES
+  (3, '烤肉拌饭', 1, 14, 15, 2, '2026-08', '2026-08-01', '2026-08-31',
+   81.25, 80.00, 80.75, 2, 1, 1, 1, 0, 0, '2026-08-31 23:59:59', 1, NOW()),
+  (1, '特色美食店666', 2, 18, 19, 2, '2026-08', '2026-08-01', '2026-08-31',
+   72.25, 80.00, 75.35, 3, 2, 1, 1, 3, 0, '2026-08-31 23:59:59', 1, NOW()),
+  (2, '水饺店铺', 1, 18, 20, 2, '2026-08', '2026-08-01', '2026-08-31',
+   70.00, 80.00, 74.00, 3, 3, 1, 1, 3, 1, '2026-08-31 23:59:59', 1, NOW());
+
+-- ============ 9. 汇总校验 ============
+SELECT '--- 计划 ---' AS label;
+SELECT PLAN_ID, PLAN_NAME, PLAN_TYPE, CYCLE_TYPE, START_DATE, END_DATE, NEXT_EXECUTE_DATE, STORE_COUNT, STATUS FROM bu_inspect_plan WHERE PLAN_ID IN (@plan1,@plan2);
+SELECT '--- 计划1 指标权重合计 ---';
+SELECT ROUND(SUM(WEIGHT),0) AS weight_total FROM bu_inspect_plan_indicator WHERE PLAN_ID=@plan1 AND STATUS=1;
+SELECT '--- 任务 ---';
+SELECT TASK_ID, STORE_NAME, TASK_DATE, TOTAL_SCORE, MAX_SCORE, PASS_SCORE, PASS_FLAG, UNPASS_ITEM_COUNT, STATUS FROM bu_inspect_task WHERE PLAN_ID=@plan1 ORDER BY TASK_ID;
+SELECT '--- 任务1 明细(应7条,加权和=72.25) ---';
+SELECT COUNT(*) AS item_cnt, ROUND(SUM(WEIGHTED_SCORE),2) AS wsum, SUM(PASS_FLAG) AS pass_cnt FROM bu_inspect_task_item WHERE TASK_ID=@t1;
+SELECT '--- 整改状态分布 ---';
+SELECT STATUS, COUNT(*) AS cnt FROM bu_rectification WHERE PLAN_ID=@plan1 GROUP BY STATUS ORDER BY STATUS;
+SELECT '--- 考核排名 ---';
+SELECT RANK_NO, STORE_NAME, INSPECT_SCORE, AUTO_SCORE, TOTAL_SCORE, ASSESS_LEVEL FROM bu_assess_result WHERE PERIOD_CODE='2026-08' ORDER BY RANK_NO;

Datei-Diff unterdrückt, da er zu groß ist
+ 21 - 0
frontend-inspect-portal.vue


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.