liusheng
2026-09-29 60ade5154522a042c816abaa1840751011ae0371
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java
@@ -3,6 +3,7 @@
import java.util.*;
import java.util.stream.Collectors;
import com.alibaba.fastjson2.JSON;
import com.github.pagehelper.util.StringUtil;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
@@ -10,6 +11,7 @@
import com.smartor.domain.DTO.DetailTraceDealDTO;
import com.smartor.domain.VO.DetailTraceDealVO;
import com.smartor.domain.VO.SatisfactionExceptConfigQuestionReqVO;
import com.smartor.domain.VO.ServiceSubtaskDetailTraceVO;
import com.smartor.mapper.ServiceSubtaskDetailMapper;
import com.smartor.mapper.ServiceSubtaskDetailTraceMapper;
import com.smartor.mapper.SvyLibScriptCategoryMapper;
@@ -62,97 +64,168 @@
    /**
     * 查询随访任务异常记录跟踪列表
     *
     * @param serviceSubtaskDetailTrace 随访任务异常记录跟踪
     * @param serviceSubtaskDetailTraceVO 随访任务异常记录跟踪
     * @return 随访任务异常记录跟踪
     */
    @Override
    public List<ServiceSubtaskDetailTrace> selectServiceSubtaskDetailTtraceList(ServiceSubtaskDetailTrace serviceSubtaskDetailTrace) {
        return serviceSubtaskDetailTraceMapper.selectServiceSubtaskDetailTtraceList(serviceSubtaskDetailTrace);
    public List<ServiceSubtaskDetailTrace> selectServiceSubtaskDetailTtraceList(ServiceSubtaskDetailTraceVO serviceSubtaskDetailTraceVO) {
        List<ServiceSubtaskDetailTrace> serviceSubtaskDetailTraces = serviceSubtaskDetailTraceMapper.selectServiceSubtaskDetailTtraceList(serviceSubtaskDetailTraceVO);
        for (ServiceSubtaskDetailTrace serviceSubtaskDetailTrace : serviceSubtaskDetailTraces) {
            if (StringUtils.isNotEmpty(serviceSubtaskDetailTrace.getPatdesc())) {
                serviceSubtaskDetailTrace.setPatdescJson(JSON.parseObject(serviceSubtaskDetailTrace.getPatdesc()));
            }
        }
        return serviceSubtaskDetailTraces;
    }
    @Override
    public List<DetailTraceDealDTO> tracedeallist(DetailTraceDealVO detailTraceDealVO) {
        // 获取所有需要处理的记录
        List<ServiceSubtaskDetailTrace> tracedeallist = serviceSubtaskDetailTraceMapper.tracedeallist(detailTraceDealVO);
        // 按 questiontext 分组
        Map<String, List<ServiceSubtaskDetailTrace>> groupByQuestion = tracedeallist.stream()
                .filter(t -> t.getQuestiontext() != null)
                .collect(Collectors.groupingBy(ServiceSubtaskDetailTrace::getQuestiontext));
    public Map<String, Object> tracedeallist(DetailTraceDealVO detailTraceDealVO) {
        // 优化前:① 这条 SQL 没有 limit,每次都把整表 trace(含 patdesc/recordurl 大字段)拉回 Java 再分组;
        //        ② 每个问题都拿 scriptid 去 service_subtask_detail 查一次全表只为取 size(N+1);
        //        ③ controller 把这个方法调了两遍(第二遍为了总数),等于上面两步各跑两次。
        // 优化后:固定的 5 条聚合 SQL,DB 只处理当前页的题目。
        //  1) 当前页的问题分组聚合(分页下推到 SQL)
        List<Map<String, Object>> groupRows = serviceSubtaskDetailTraceMapper.selectTraceDealGroups(detailTraceDealVO);
        //  2) 问题总数(分页 total,全量口径)
        long questionTotal = serviceSubtaskDetailTraceMapper.countTraceDealQuestions(detailTraceDealVO);
        //  3) 汇总统计(全量口径,不受分页影响)
        Map<String, Object> sumRow = serviceSubtaskDetailTraceMapper.sumTraceDeal(detailTraceDealVO);
        long totalException = toLong(sumRow == null ? null : sumRow.get("totalException"), 0L);
        long yesDealException = toLong(sumRow == null ? null : sumRow.get("yesDeal"), 0L);
        long noDealException = totalException - yesDealException;
        List<DetailTraceDealDTO> detailTraceDealDTOList = new ArrayList<>();
        for (Map.Entry<String, List<ServiceSubtaskDetailTrace>> entry : groupByQuestion.entrySet()) {
            List<ServiceSubtaskDetailTrace> group = entry.getValue();
            DetailTraceDealDTO dto = new DetailTraceDealDTO();
            // 问题内容
            dto.setQuestiontext(entry.getKey());
            // 负责科室(去重)
            List<Map<String, Object>> responsibleDept = group.stream()
                    .filter(t -> t.getTodeptcode() != null)
                    .collect(Collectors.collectingAndThen(
                            Collectors.toMap(
                                    ServiceSubtaskDetailTrace::getTodeptcode,
                                    t -> {
                                        Map<String, Object> deptMap = new HashMap<>();
                                        deptMap.put("deptName", t.getTodeptname());
                                        deptMap.put("deptCode", t.getTodeptcode());
                                        return deptMap;
                                    },
                                    (existing, replacement) -> existing
                            ),
                            map -> new ArrayList<>(map.values())
                    ));
            dto.setResponsibleDept(responsibleDept);
            // 有效填写数:通过组内的 scriptid 去 service_subtask_detail 表查询
            List<Long> scriptIds = group.stream()
                    .map(ServiceSubtaskDetailTrace::getScriptid)
        if (CollectionUtils.isNotEmpty(groupRows)) {
            // 本页涉及的问题文本,后面 3 条查询都按它过滤
            List<String> questiontexts = groupRows.stream()
                    .map(row -> row.get("questiontext"))
                    .filter(Objects::nonNull)
                    .map(String::valueOf)
                    .distinct()
                    .collect(Collectors.toList());
            long effectiveFillNum = 0L;
            if (CollectionUtils.isNotEmpty(scriptIds)) {
                ServiceSubtaskDetail query = new ServiceSubtaskDetail();
                query.setDelFlag("0");
                for (Long scriptId : scriptIds) {
                    query.setScriptid(scriptId);
                    List<ServiceSubtaskDetail> details = serviceSubtaskDetailMapper.selectServiceSubtaskDetailList(query);
                    effectiveFillNum += CollectionUtils.isEmpty(details) ? 0 : details.size();
            //  4) 本页问题的负责科室(按科室编号去重)
            Map<String, List<Map<String, Object>>> deptMap = new HashMap<>();
            //  5) 本页问题的最新处理人/处理时间
            Map<String, Map<String, Object>> latestHandleMap = new HashMap<>();
            if (CollectionUtils.isNotEmpty(questiontexts)) {
                for (Map<String, Object> row : serviceSubtaskDetailTraceMapper.selectTraceDealDepts(detailTraceDealVO, questiontexts)) {
                    String questiontext = row.get("questiontext") == null ? null : String.valueOf(row.get("questiontext"));
                    if (questiontext == null) continue;
                    Map<String, Object> deptMapItem = new HashMap<>();
                    deptMapItem.put("deptName", row.get("deptName"));
                    deptMapItem.put("deptCode", row.get("deptCode"));
                    deptMap.computeIfAbsent(questiontext, key -> new ArrayList<>()).add(deptMapItem);
                }
                for (Map<String, Object> row : serviceSubtaskDetailTraceMapper.selectTraceDealLatestHandle(detailTraceDealVO, questiontexts)) {
                    String questiontext = row.get("questiontext") == null ? null : String.valueOf(row.get("questiontext"));
                    if (questiontext == null) continue;
                    // 同一个最大处理时间可能有多条,保留第一条即可(原实现取组内 handle_time 最大的那条)
                    latestHandleMap.putIfAbsent(questiontext, row);
                }
            }
            // 异常填写数 = 当前分组的 List 数量
            long exceptionFillNum = group.size();
            //  6) 本页问题的有效填写数:一次 group by 查回,替代逐个 scriptid 查询
            // 一个问题可能对应多个 scriptid,所以先把 questiontext -> scriptid 列表解开,
            // 再把该问题所有 scriptid 的填写数相加(与原实现 for 循环累加的口径一致)
            Map<String, List<Long>> questionScriptIds = new LinkedHashMap<>();
            Set<Long> scriptIdSet = new LinkedHashSet<>();
            for (Map<String, Object> row : groupRows) {
                String questiontext = row.get("questiontext") == null ? null : String.valueOf(row.get("questiontext"));
                if (questiontext == null) continue;
                List<Long> ids = parseScriptIds(row.get("scriptids"));
                questionScriptIds.put(questiontext, ids);
                scriptIdSet.addAll(ids);
            }
            Map<Long, Long> effectiveFillMap = new HashMap<>();
            if (CollectionUtils.isNotEmpty(scriptIdSet)) {
                for (Map<String, Object> row : serviceSubtaskDetailMapper.countDetailByScriptIds(new ArrayList<>(scriptIdSet))) {
                    Long scriptId = toLongObj(row.get("scriptid"));
                    if (scriptId == null) continue;
                    effectiveFillMap.put(scriptId, toLong(row.get("cnt"), 0L));
                }
            }
            Map<String, Object> fillSituation = new HashMap<>();
            fillSituation.put("effectiveFillNum", effectiveFillNum);
            fillSituation.put("exceptionFillNum", exceptionFillNum);
            dto.setFillSituation(fillSituation);
            //  7) 组装返回结构(顺序 = SQL 的 order by questiontext,保证分页稳定)
            for (Map<String, Object> row : groupRows) {
                String questiontext = row.get("questiontext") == null ? null : String.valueOf(row.get("questiontext"));
                if (questiontext == null) continue;
            // 异常问题处理数量:从 handleFlag 统计
            long yesDeal = group.stream().filter(t -> "1".equals(t.getHandleFlag())).count();
            long noDeal = group.stream().filter(t -> !"1".equals(t.getHandleFlag())).count();
            Map<String, Object> exceptionQuesNum = new HashMap<>();
            exceptionQuesNum.put("yesDeal", yesDeal);
            exceptionQuesNum.put("noDeal", noDeal);
            exceptionQuesNum.put("all", exceptionFillNum);
            dto.setExceptionQuesNum(exceptionQuesNum);
                DetailTraceDealDTO dto = new DetailTraceDealDTO();
                dto.setQuestiontext(questiontext);
                Long scriptId = toLongObj(row.get("scriptid"));
                dto.setScriptid(scriptId);
                dto.setScriptids(parseScriptIds(row.get("scriptids")));
                Object templateType = row.get("templateType");
                dto.setTemplateType(templateType == null ? null : Integer.valueOf(String.valueOf(templateType)));
                dto.setResponsibleDept(deptMap.getOrDefault(questiontext, new ArrayList<>()));
            // 最新处理人和处理时间(取 handleTime 最新的一条)
            group.stream()
                    .filter(t -> t.getHandleTime() != null)
                    .max(Comparator.comparing(ServiceSubtaskDetailTrace::getHandleTime))
                    .ifPresent(latest -> {
                        dto.setHandleBy(latest.getHandleBy());
                        dto.setHandleTime(latest.getHandleTime());
                    });
                long exceptionFillNum = toLong(row.get("exceptionFillNum"), 0L);
                // 该问题下所有 scriptid 的有效填写数之和(一个 questiontext 可能对应多个 scriptid)
                long effectiveFillNum = 0L;
                for (Long sid : questionScriptIds.getOrDefault(questiontext, new ArrayList<>())) {
                    effectiveFillNum += effectiveFillMap.getOrDefault(sid, 0L);
                }
                Map<String, Object> fillSituation = new HashMap<>();
                fillSituation.put("effectiveFillNum", effectiveFillNum);
                fillSituation.put("exceptionFillNum", exceptionFillNum);
                dto.setFillSituation(fillSituation);
            detailTraceDealDTOList.add(dto);
                long yesDeal = toLong(row.get("yesDeal"), 0L);
                long noDeal = toLong(row.get("noDeal"), 0L);
                Map<String, Object> exceptionQuesNum = new HashMap<>();
                exceptionQuesNum.put("yesDeal", yesDeal);
                exceptionQuesNum.put("noDeal", noDeal);
                exceptionQuesNum.put("all", exceptionFillNum);
                dto.setExceptionQuesNum(exceptionQuesNum);
                Map<String, Object> latestHandle = latestHandleMap.get(questiontext);
                if (latestHandle != null) {
                    dto.setHandleBy(latestHandle.get("handleBy") == null ? null : String.valueOf(latestHandle.get("handleBy")));
                    Object handleTime = latestHandle.get("handleTime");
                    dto.setHandleTime(handleTime instanceof Date ? (Date) handleTime : null);
                }
                detailTraceDealDTOList.add(dto);
            }
        }
        return detailTraceDealDTOList;
        Map<String, Object> result = new HashMap<>();
        result.put("detailTraceDealDTOList", detailTraceDealDTOList);
        result.put("totalException", totalException);
        result.put("noDealException", noDealException);
        result.put("yesDealException", yesDealException);
        // 分页总数(按问题维度),controller 直接用它,不用再全量查一遍
        result.put("questionTotal", questionTotal);
        return result;
    }
    private static Long toLongObj(Object value) {
        if (value == null) return null;
        if (value instanceof Number) return ((Number) value).longValue();
        String text = String.valueOf(value).trim();
        return text.isEmpty() ? null : Long.valueOf(text);
    }
    private static long toLong(Object value, long defaultValue) {
        Long result = toLongObj(value);
        return result == null ? defaultValue : result;
    }
    /**
     * 解析 group_concat(distinct scriptid) 的结果(如 "101,102")为 scriptid 列表
     */
    private static List<Long> parseScriptIds(Object value) {
        List<Long> scriptIds = new ArrayList<>();
        if (value == null) return scriptIds;
        for (String item : String.valueOf(value).split(",")) {
            Long scriptId = toLongObj(item);
            if (scriptId != null && !scriptIds.contains(scriptId)) {
                scriptIds.add(scriptId);
            }
        }
        return scriptIds;
    }
    /**