liusheng
2026-10-12 0c1f8c479065739f3c7a5126de8e667035d00b58
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java
@@ -10,7 +10,6 @@
import com.smartor.domain.*;
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;
@@ -80,78 +79,115 @@
    @Override
    public Map<String, Object> 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));
        // 优化前:① 这条 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<>();
        long totalException = 0L;
        long noDealException = 0L;
        long yesDealException = 0L;
        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());
        for (Map.Entry<String, List<ServiceSubtaskDetailTrace>> entry : groupByQuestion.entrySet()) {
            List<ServiceSubtaskDetailTrace> group = entry.getValue();
            DetailTraceDealDTO dto = new DetailTraceDealDTO();
            //  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);
                }
            // 问题内容
            dto.setQuestiontext(entry.getKey());
            if (CollectionUtils.isNotEmpty(group)) {
                dto.setScriptid(group.get(0).getScriptid());
                dto.setTemplateType(group.get(0).getTemplateType());
            }
            // 负责科室(去重)
            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).filter(Objects::nonNull).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();
                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<>()));
            // 汇总统计累加
            totalException += exceptionFillNum;
            noDealException += noDeal;
            yesDealException += yesDeal;
                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);
            // 最新处理人和处理时间(取 handleTime 最新的一条)
            group.stream().filter(t -> t.getHandleTime() != null).max(Comparator.comparing(ServiceSubtaskDetailTrace::getHandleTime)).ifPresent(latest -> {
                dto.setHandleBy(latest.getHandleBy());
                dto.setHandleTime(latest.getHandleTime());
            });
                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);
            detailTraceDealDTOList.add(dto);
                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);
            }
        }
        Map<String, Object> result = new HashMap<>();
@@ -159,7 +195,36 @@
        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;
    }
    /**
@@ -207,79 +272,4 @@
    public int deleteServiceSubtaskDetailTtraceById(Long id) {
        return serviceSubtaskDetailTraceMapper.deleteServiceSubtaskDetailTtraceById(id);
    }
    @Override
    public Map<String, Object> getSatisfactionExceptConfigQuestion(SatisfactionExceptConfigQuestionReqVO questionReqVO) {
        // 初始化结果集
        Map<String, Object> result = new HashMap<>();
        if (StringUtils.isEmpty(questionReqVO.getScriptType()) || questionReqVO.getScriptType().equals("1")) {
            List<Map<String, Object>> voicetDataList = new ArrayList<>();
            //获取满意度的分类
            IvrLibaScriptAssort ivrLibaScriptAssort = new IvrLibaScriptAssort();
            ivrLibaScriptAssort.setType("myd");
            if (!StringUtils.isEmpty(questionReqVO.getType())) ivrLibaScriptAssort.setType(questionReqVO.getType());
            List<IvrLibaScriptAssortVO> ivrLibaScriptAssortVOS = iIvrLibaScriptAssortService.selectIvrLibaScriptAssortList(ivrLibaScriptAssort);
            List<Long> assortIdList = Optional.ofNullable(ivrLibaScriptAssortVOS).filter(list -> !list.isEmpty()).map(list -> list.stream().map(IvrLibaScriptAssortVO::getId).collect(Collectors.toList())).orElse(Collections.emptyList());
            //用来记录满意度的所有问题
            List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(assortIdList)) {
                for (Long assortId : assortIdList) {
                    IvrLibaTemplateScript ivrLibaTemplateScript = new IvrLibaTemplateScript();
                    ivrLibaTemplateScript.setScriptAssortid(assortId);
                    List<IvrLibaTemplateScript> ivrLibaTemplateScripts = iIvrLibaTemplateScriptService.selectIvrLibaTemplateScriptList(ivrLibaTemplateScript);
                    ivrLibaTemplateScriptList.addAll(ivrLibaTemplateScripts);
                }
                for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
                    Map<String, Object> voicetData = new HashMap<>();
                    voicetData.put("questionId", ivrLibaTemplateScript.getId());
                    voicetData.put("libtemplateid", ivrLibaTemplateScript.getTemplateid());
                    voicetData.put("questionContent", ivrLibaTemplateScript.getScriptContent());
                    voicetData.put("questionTopic", ivrLibaTemplateScript.getScriptTopic());
                    voicetData.put("questionType", ivrLibaTemplateScript.getScriptType());
                    voicetData.put("categoryName", ivrLibaTemplateScript.getScriptAssortname());
                    voicetData.put("categoryId", ivrLibaTemplateScript.getScriptAssortid());
                    voicetDataList.add(voicetData);
                }
            }
            //语音数据返回集合
            result.put("voiceDataList", voicetDataList);
        }
        if (StringUtils.isEmpty(questionReqVO.getScriptType()) || questionReqVO.getScriptType().equals("2")) {
            List<Map<String, Object>> questionnaireDataList = new ArrayList<>();
            //获取满意度的分类
            SvyLibScriptCategory svyLibScriptCategory = new SvyLibScriptCategory();
            svyLibScriptCategory.setType("myd");
            if (!StringUtils.isEmpty(questionReqVO.getType())) svyLibScriptCategory.setType(questionReqVO.getType());
            List<SvyLibScriptCategory> svyLibScriptCategoryVOS = svyLibScriptCategoryMapper.selectSvyLibScriptCategoryList(svyLibScriptCategory);
            List<Long> assortIdList = Optional.ofNullable(svyLibScriptCategoryVOS).filter(list -> !list.isEmpty()).map(list -> list.stream().map(SvyLibScriptCategory::getId).collect(Collectors.toList())).orElse(Collections.emptyList());
            //用来记录满意度的所有问题
            List<SvyLibTemplateScript> svyLibTemplateScriptList = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(assortIdList)) {
                for (Long categoryid : assortIdList) {
                    SvyLibTemplateScript svyLibTemplateScript = new SvyLibTemplateScript();
                    svyLibTemplateScript.setCategoryid(categoryid);
                    List<SvyLibTemplateScript> svyLibTemplateScripts = isvyLibTemplateScriptService.selectSvyLibTemplateScriptList(svyLibTemplateScript);
                    svyLibTemplateScriptList.addAll(svyLibTemplateScripts);
                }
                for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) {
                    Map<String, Object> voicetData = new HashMap<>();
                    voicetData.put("questionId", svyLibTemplateScript.getId());
                    voicetData.put("libtemplateid", svyLibTemplateScript.getSvyid());
                    voicetData.put("questionContent", svyLibTemplateScript.getScriptContent());
                    voicetData.put("questionTopic", svyLibTemplateScript.getScriptTopic());
                    voicetData.put("questionType", svyLibTemplateScript.getScriptType());
                    voicetData.put("categoryName", svyLibTemplateScript.getCategoryName());
                    voicetData.put("categoryId", svyLibTemplateScript.getCategoryid());
                    questionnaireDataList.add(voicetData);
                }
            }
            //问卷数据返回集合
            result.put("questionnaireDataList", questionnaireDataList);
        }
        return result;
    }
}
}