Parcourir la source

feat(check): 添加设备类型筛选和批量新增保养标准功能

- 在CheckJobDTO中新增typeId字段用于设备类型筛选
- 在CheckJobMapper.xml中添加typeId查询条件和改进用户搜索逻辑
- 修复数据权限拦截器中的排序处理逻辑
- 添加批量新增保养标准接口和实现方法
- 在保养标准控制器中增加批量保存和按设备查询接口
- 修复保养项导入验证逻辑和范围值处理
- 优化维修申请表服务中的排序字段处理
dazhaxie il y a 3 jours
Parent
commit
da732a0e28

+ 41 - 15
platform-dao/src/main/java/com/platform/dao/config/DataScopeInterceptor.java

@@ -52,13 +52,22 @@ public class DataScopeInterceptor implements Interceptor {
             return invocation.proceed();
         } else {
             String sortBy = (String) dataScope.get("sortBy");
-            if (StringUtils.isNotEmpty(sortBy)) {
+            String sortName = (String) dataScope.get("sortName");
+            // PageHelper 会为分页查询额外生成一条以 _COUNT 结尾的统计语句,
+            // 统计语句是聚合查询,不能拼接 ORDER BY,否则在 ONLY_FULL_GROUP_BY 模式下会报错
+            boolean isCountQuery = mappedStatement.getId().toUpperCase().endsWith("_COUNT");
+            if (!isCountQuery && StringUtils.isNotEmpty(sortBy) && StringUtils.isNotEmpty(sortName)) {
+                String[] sortBys = sortBy.split(",");
+                String[] sortNames = sortName.split(",");
+                // sortBy 与 sortName 数量可能不一致,取较小值防止数组越界
+                int sortSize = Math.min(sortBys.length, sortNames.length);
+                // 拆出 PageHelper 追加在末尾的 LIMIT 子句,ORDER BY 需要拼接在 LIMIT 之前
                 String selectSql = originalSql;
                 String limitSql = "";
-                int index = originalSql.indexOf("LIMIT");
-                if (index > 0) {
-                    selectSql = originalSql.substring(0, index - 1);
-                    limitSql = originalSql.substring(index - 1, originalSql.length() - 1);
+                int limitIndex = originalSql.toUpperCase().lastIndexOf("LIMIT");
+                if (limitIndex > 0) {
+                    selectSql = originalSql.substring(0, limitIndex).trim();
+                    limitSql = " " + originalSql.substring(limitIndex).trim();
                 }
                 // 去掉已有的外层 ORDER BY,避免拼接出两个 ORDER BY 导致 SQL 语法错误
                 int orderByIndex = selectSql.toUpperCase().lastIndexOf("ORDER BY");
@@ -69,17 +78,16 @@ public class DataScopeInterceptor implements Interceptor {
                         selectSql = selectSql.substring(0, selectSql.length() - 1).trim();
                     }
                 }
-                String[] sortBys = sortBy.split(",");
-                String[] sortNames = ((String) dataScope.get("sortName")).split(",");
-                StringBuffer sortStr = new StringBuffer(" order by ");
-                for (int i = 0; i < sortBys.length; i++) {
-                    String sort = sortBys[i];
-                    String sortName = sortNames[i];
-                    sortStr.append(sortName).append(" ").append(sort).append(",");
+                StringBuilder sortStr = new StringBuilder(" order by ");
+                for (int i = 0; i < sortSize; i++) {
+                    if (i > 0) {
+                        sortStr.append(",");
+                    }
+                    sortStr.append(sortNames[i].trim()).append(" ").append(sortBys[i].trim());
                 }
-                originalSql = selectSql + sortStr.toString().substring(0, sortStr.length() - 1) + limitSql;
+                originalSql = selectSql + sortStr + limitSql;
+                metaObject.setValue("delegate.boundSql.sql", originalSql);
             }
-            metaObject.setValue("delegate.boundSql.sql", originalSql);
             return invocation.proceed();
 //			String scopeName = dataScope.getScopeName();
 //			List<Integer> deptIds = dataScope.getDeptIds();
@@ -123,14 +131,32 @@ public class DataScopeInterceptor implements Interceptor {
      * @return DataScope
      */
     private HashMap findDataScopeObject(Object parameterObj) {
+        if (parameterObj == null) {
+            return null;
+        }
         if (parameterObj instanceof DataScope) {
             return (HashMap) parameterObj;
-        } else if (parameterObj instanceof Map) {
+        }
+        if (parameterObj instanceof Map) {
             for (Object val : ((Map<?, ?>) parameterObj).values()) {
                 if (val instanceof DataScope) {
                     return (HashMap) val;
                 }
             }
+            return null;
+        }
+        // 单个 JavaBean(各类 DTO)作为 Mapper 参数时,MyBatis 不会将其包装成 Map,
+        // 需要反射读取其 dataScope 属性,否则前端传入的排序条件会被整体忽略
+        try {
+            MetaObject paramMetaObject = SystemMetaObject.forObject(parameterObj);
+            if (paramMetaObject.hasGetter("dataScope")) {
+                Object val = paramMetaObject.getValue("dataScope");
+                if (val instanceof DataScope) {
+                    return (HashMap) val;
+                }
+            }
+        } catch (Exception e) {
+            log.warn("读取参数 dataScope 属性失败: {}", e.getMessage());
         }
         return null;
     }

+ 4 - 0
platform-dao/src/main/java/com/platform/dao/dto/check/CheckJobDTO.java

@@ -102,6 +102,10 @@ public class CheckJobDTO extends BaseDTO implements Serializable {
      */
     private Integer type;
     /**
+     * 设备类型ID(对应 t_sb_info.type_id,用于按设备类型筛选保养任务/记录)
+     */
+    private String typeId;
+    /**
      * 点检结果反馈
      */
     private String feedback;

+ 36 - 2
platform-dao/src/main/resources/mapper/check/CheckJobMapper.xml

@@ -229,6 +229,9 @@
         <if test="sbLevel != null and sbLevel != ''">
             and sbinfo.level = #{sbLevel}
         </if>
+        <if test="typeId != null and typeId != ''">
+            and sbinfo.type_id = #{typeId}
+        </if>
         <if test="periodType != null and periodType != ''">
             and standard.period_type = #{periodType}
         </if>
@@ -277,7 +280,20 @@
             </foreach>
         </if>
         <if test="checkUserName != null and checkUserName != ''">
-            and u.real_name like concat('%',#{checkUserName},'%')
+            and (
+                u.real_name like concat('%',#{checkUserName},'%')
+                OR (
+                    checkjob.check_dept_id IS NOT NULL
+                    AND EXISTS (
+                        SELECT 1
+                        FROM t_sys_user su
+                        INNER JOIN t_sys_user_dept sud ON sud.user_id = su.user_id
+                        INNER JOIN t_sys_dept_relation dr ON dr.descendant = sud.dept_id
+                        WHERE su.real_name like concat('%',#{checkUserName},'%')
+                        AND dr.ancestor = checkjob.check_dept_id
+                    )
+                )
+            )
         </if>
         <if test="actualUser != null and actualUser != ''">
             and checkjob.actual_user like concat('%',#{actualUser},'%')
@@ -416,6 +432,9 @@
         <if test="sbLevel != null and sbLevel != ''">
             and sbinfo_inner.level = #{sbLevel}
         </if>
+        <if test="typeId != null and typeId != ''">
+            and sbinfo_inner.type_id = #{typeId}
+        </if>
         <if test="periodType != null and periodType != ''">
             and standard_inner.period_type = #{periodType}
         </if>
@@ -464,7 +483,20 @@
             </foreach>
         </if>
         <if test="checkUserName != null and checkUserName != ''">
-            and u.real_name like concat('%',#{checkUserName},'%')
+            and (
+                u.real_name like concat('%',#{checkUserName},'%')
+                OR (
+                    checkjob_inner.check_dept_id IS NOT NULL
+                    AND EXISTS (
+                        SELECT 1
+                        FROM t_sys_user su
+                        INNER JOIN t_sys_user_dept sud ON sud.user_id = su.user_id
+                        INNER JOIN t_sys_dept_relation dr ON dr.descendant = sud.dept_id
+                        WHERE su.real_name like concat('%',#{checkUserName},'%')
+                        AND dr.ancestor = checkjob_inner.check_dept_id
+                    )
+                )
+            )
         </if>
         <if test="actualUser != null and actualUser != ''">
             and checkjob_inner.actual_user like concat('%',#{actualUser},'%')
@@ -570,6 +602,7 @@
                 FROM t_check_job checkjob_inner
                 LEFT JOIN t_sb_info sbinfo_inner ON sbinfo_inner.id = checkjob_inner.sb_id
                 LEFT JOIN t_check_standard standard_inner ON standard_inner.id = checkjob_inner.standard_id
+                LEFT JOIN t_sys_user u ON checkjob_inner.check_user_id = u.user_id
                 <where>
                     <include refid="List_Condition_Inner"/>
                 </where>
@@ -885,6 +918,7 @@
         FROM t_check_job checkjob_inner
         LEFT JOIN t_sb_info sbinfo_inner ON sbinfo_inner.id = checkjob_inner.sb_id
         LEFT JOIN t_check_standard standard_inner ON standard_inner.id = checkjob_inner.standard_id
+        LEFT JOIN t_sys_user u ON checkjob_inner.check_user_id = u.user_id
         <where>
             <include refid="List_Condition_Inner"/>
         </where>

+ 4 - 1
platform-rest/src/main/java/com/platform/rest/controller/check/CheckRequirementController.java

@@ -206,7 +206,10 @@ public class CheckRequirementController {
     @PostMapping("/import/execute")
     @ApiOperation(value = "导入保养项", notes = "通过 Excel 文件导入保养项标准,支持新增、修改、删除操作")
     public R importList( @RequestParam("file") MultipartFile file , @RequestParam("sbType") @NotBlank String sbType,@RequestParam(value = "scope",required = false) String scope, @RequestParam("type") @NotBlank Integer type) throws Exception {
-        if (StringUtils.isBlank( scope) && type == 1){
+        if (type == 1) {
+            if (StringUtils.isBlank(scope)) {
+                return R.error("内容项填报类型为指定填写值范围时,录入范围值不能为空");
+            }
             // 检查scope字段格式必须符合 “A-B-...这种要求”
             String errorMsg = scopeValidator.validateScope(scope);
             if (errorMsg != null) {

+ 26 - 0
platform-rest/src/main/java/com/platform/rest/controller/check/CheckStandardController.java

@@ -3,6 +3,7 @@ package com.platform.rest.controller.check;
 import cn.hutool.core.collection.CollectionUtil;
 import com.platform.common.util.R;
 import com.platform.dao.dto.check.CheckJobDTO;
+import com.platform.dao.dto.check.CheckStandardBatchDTO;
 import com.platform.dao.dto.check.CheckStandardBatchUpdateParams;
 import com.platform.dao.dto.check.CheckStandardDTO;
 import com.platform.dao.entity.check.CheckStandard;
@@ -295,4 +296,29 @@ public class CheckStandardController {
         return new R<>();
     }
 
+    /**
+     * 批量新增保养标准(同一设备下一次性创建多条标准)
+     *
+     * @param params 批量新增参数(sbId + list)
+     * @return R
+     */
+    @SysLog("批量新增保养标准")
+    @PostMapping("/batch")
+    @PreAuthorize("@pms.hasPermission('check-spot-standards-add') or @pms.hasPermission('check-polling-standards-add')")
+    public R batchSave(@RequestBody CheckStandardBatchDTO params) {
+        int count = checkStandardService.batchSave(params);
+        return new R<>("批量新增成功,共 " + count + " 条");
+    }
+
+    /**
+     * 根据设备ID查询该设备下的所有保养标准(用于批量新增界面回显已有数据)
+     *
+     * @param sbId 设备ID
+     * @return R
+     */
+    @GetMapping("/sb/{sbId}")
+    public R<List<CheckStandardVO>> getListBySbId(@PathVariable("sbId") String sbId) {
+        return new R<>(checkStandardService.selectListBySbId(sbId));
+    }
+
 }

+ 17 - 0
platform-service/src/main/java/com/platform/service/check/CheckStandardService.java

@@ -106,4 +106,21 @@ public interface CheckStandardService extends IBaseService<CheckStandard, CheckS
     AbstractPageResultBean<CheckStandardSpareVO> selectSpareWarnVOPage(int month, int type, int pageNum, int pageSize);
 
     void batchUpdateByIdsAndDeviceType(List<String> ids, String deviceType);
+
+    /**
+     * 批量新增保养标准(同一设备下一次性创建多条标准)
+     * 复用单条新增逻辑:自动生成编码、处理执行人、关联保养项、生成首期任务
+     *
+     * @param batchDTO 批量新增参数(sbId + list)
+     * @return 新增成功的记录数
+     */
+    int batchSave(CheckStandardBatchDTO batchDTO);
+
+    /**
+     * 根据设备ID查询该设备下的所有保养标准(用于批量新增界面回显已有数据)
+     *
+     * @param sbId 设备ID
+     * @return 保养标准列表
+     */
+    List<CheckStandardVO> selectListBySbId(String sbId);
 }

+ 5 - 18
platform-service/src/main/java/com/platform/service/check/impl/CheckRequirementServiceImpl.java

@@ -114,15 +114,9 @@ public class CheckRequirementServiceImpl extends BaseServiceImpl<CheckRequiremen
                 existingReq.setContent(vo.getContent());
                 // 设置保养周期
                 existingReq.setPeriod(getPeriod(vo.getMonth(), vo.getQuarter(), vo.getHalfYear(), vo.getYear()));
-                if (type == 1) {
-                    String scopeValue = StringUtils.isNotEmpty(vo.getScope()) ? scope : null;
-                    if (StringUtils.isEmpty(scopeValue)){
-                        existingReq.setType("0");
-                    }
-                    existingReq.setScope(scopeValue);
-                } else {
-                    existingReq.setScope("");
-                }
+                // 填报类型与范围值以导入弹窗为准,统一作用于本文件所有行:指定填写值范围时使用弹窗录入的范围值
+                existingReq.setType(type.toString());
+                existingReq.setScope(type == 1 ? scope : "");
                 existingReq.setUpdateTime(now);
                 existingReq.setUpdateUserName(currentUserName);
                 requirementsToUpdate.add(existingReq);
@@ -137,16 +131,9 @@ public class CheckRequirementServiceImpl extends BaseServiceImpl<CheckRequiremen
                 requirement.setContent(vo.getContent());
                 // 设置保养周期
                 requirement.setPeriod(getPeriod(vo.getMonth(), vo.getQuarter(), vo.getHalfYear(), vo.getYear()));
+                // 填报类型与范围值以导入弹窗为准,统一作用于本文件所有行:指定填写值范围时使用弹窗录入的范围值
                 requirement.setType(type.toString());
-                if (type == 1) {
-                    String scopeValue = StringUtils.isNotEmpty(vo.getScope()) ? scope : null;
-                    if (StringUtils.isEmpty(scopeValue)){
-                        requirement.setType("0");
-                    }
-                    requirement.setScope(scopeValue);
-                } else {
-                    requirement.setScope("");
-                }
+                requirement.setScope(type == 1 ? scope : "");
                 requirement.setCreatedTime(now);
                 requirement.setCreatedUserName(currentUserName);
                 requirement.setUpdateTime(now);

+ 75 - 0
platform-service/src/main/java/com/platform/service/check/impl/CheckStandardServiceImpl.java

@@ -13,6 +13,7 @@ import com.platform.common.util.*;
 import com.platform.dao.bean.MyPage;
 import com.platform.dao.bean.MyVOPage;
 import com.platform.dao.dto.check.CheckJobDTO;
+import com.platform.dao.dto.check.CheckStandardBatchDTO;
 import com.platform.dao.dto.check.CheckStandardDTO;
 import com.platform.dao.dto.check.CheckStandardSpareDTO;
 import com.platform.dao.dto.part.PartInfoDTO;
@@ -1254,6 +1255,80 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
         }
     }
 
+    /**
+     * 批量新增保养标准(同一设备下一次性创建多条标准)。
+     * 逐条复用 {@link #saveModelByDTO} 的完整逻辑:自动生成编码、处理执行人方式、
+     * 关联保养项、生成首期保养任务,确保与单条新增行为一致。
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int batchSave(CheckStandardBatchDTO batchDTO) {
+        if (batchDTO == null || StringUtils.isBlank(batchDTO.getSbId())) {
+            throw new BusinessException("请选择设备");
+        }
+        if (CollectionUtil.isEmpty(batchDTO.getList())) {
+            throw new BusinessException("请至少添加一条保养标准");
+        }
+        SbInfo sbInfo = sbInfoMapper.selectByPrimaryKey(batchDTO.getSbId());
+        if (sbInfo == null) {
+            throw new BusinessException("所选设备已不存在!");
+        }
+        int count = 0;
+        for (CheckStandardDTO item : batchDTO.getList()) {
+            if (item == null) {
+                continue;
+            }
+            int rowNo = count + 1;
+            // 基础校验,与前端批量新增表单的必填项保持一致
+            if (item.getLevel() == null) {
+                throw new BusinessException("第" + rowNo + "行维护等级不能为空");
+            }
+            if (item.getCheckUserType() == null) {
+                throw new BusinessException("第" + rowNo + "行执行人方式不能为空");
+            }
+            if (item.getPeriod() == null || item.getPeriodType() == null) {
+                throw new BusinessException("第" + rowNo + "行计划周期不能为空");
+            }
+            if (CollectionUtil.isEmpty(item.getRequirementList())) {
+                throw new BusinessException("第" + rowNo + "行检查项目不能为空");
+            }
+            // 班组模式必须选择执行班组
+            if (CheckUserTypeEnum.TEAM.getValue().equals(item.getCheckUserType())
+                    && StringUtils.isBlank(item.getCheckDeptId())) {
+                throw new BusinessException("第" + rowNo + "行执行人方式为班组时,必须选择执行班组");
+            }
+            // 补齐设备信息与默认值,强制走新增分支
+            item.setId(null);
+            item.setSbId(batchDTO.getSbId());
+            item.setSbNo(sbInfo.getNo());
+            item.setSbName(sbInfo.getName());
+            if (item.getType() == null) {
+                item.setType(CheckStandardTypeEnum.POLLING.getValue());
+            }
+            if (item.getEnable() == null) {
+                item.setEnable(1);
+            }
+            if (item.getDetailList() == null) {
+                item.setDetailList(new ArrayList<>());
+            }
+            // 复用单条新增逻辑(含编码生成、执行人处理、关联保养项、首期任务生成)
+            this.saveModelByDTO(item);
+            count++;
+        }
+        log.info("设备 {} 批量新增保养标准成功,共 {} 条", batchDTO.getSbId(), count);
+        return count;
+    }
+
+    @Override
+    public List<CheckStandardVO> selectListBySbId(String sbId) {
+        if (StringUtils.isBlank(sbId)) {
+            return new ArrayList<>();
+        }
+        CheckStandardDTO dto = new CheckStandardDTO();
+        dto.setSbId(sbId);
+        return mapper.selectList(dto);
+    }
+
 
     /**
      * 根据身份获取用户

+ 21 - 0
platform-service/src/main/java/com/platform/service/repair/impl/RepairApplicationFormServiceImpl.java

@@ -291,6 +291,27 @@ public class RepairApplicationFormServiceImpl
             record.setStatus(null);
         }
 
+        // 多表 JOIN 查询,为排序字段补充主表别名,避免列名歧义(如 status 同时存在于多张表)
+        if (record.getDataScope() != null && record.getDataScope().get("sortName") != null) {
+            String sortName = (String) record.getDataScope().get("sortName");
+            String[] sortNames = sortName.split(",");
+            StringBuilder aliasedSortName = new StringBuilder();
+            for (int i = 0; i < sortNames.length; i++) {
+                String name = sortNames[i].trim();
+                if (name.isEmpty()) {
+                    continue;
+                }
+                if (!name.contains(".")) {
+                    name = "application." + name;
+                }
+                if (aliasedSortName.length() > 0) {
+                    aliasedSortName.append(",");
+                }
+                aliasedSortName.append(name);
+            }
+            record.getDataScope().put("sortName", aliasedSortName.toString());
+        }
+
         return new MyVOPage<>(mapper.selectPageList(record));
     }