liusheng
2026-10-12 0c1f8c479065739f3c7a5126de8e667035d00b58
ruoyi-satisfaction/src/main/java/com/ruoyi/satisfaction/service/impl/PatSatisfactionServiceImpl.java
ÎļþÃû´Ó smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package com.smartor.service.impl;
package com.ruoyi.satisfaction.service.impl;
import java.math.BigDecimal;
import java.math.RoundingMode;
@@ -12,18 +12,27 @@
import com.ruoyi.common.utils.ArrayListUtils;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.DtoConversionUtils;
import com.smartor.domain.*;
import com.smartor.domain.DTO.ServiceSubtaskDetailDTO;
import com.smartor.domain.VO.PatSatisfactionReqVO;
import com.smartor.domain.VO.PatSatisfactionResVO;
import com.smartor.domain.entity.PatSatisfactionDetailEntity;
import com.smartor.domain.entity.SatisfactionStatQuery;
import com.smartor.domain.entity.ServiceSubtaskSatisfactionEntity;
import com.smartor.mapper.*;
import com.ruoyi.satisfaction.domain.vo.PatSatisfactionReqVO;
import com.ruoyi.satisfaction.domain.vo.PatSatisfactionResVO;
import com.ruoyi.satisfaction.domain.entity.PatSatisfactionDetailEntity;
import com.ruoyi.satisfaction.domain.entity.SatisfactionStatQuery;
import com.ruoyi.satisfaction.domain.entity.ServiceSubtaskSatisfactionEntity;
import com.ruoyi.satisfaction.mapper.PatSatisfactionMapper;
import com.ruoyi.satisfaction.mapper.SatisfactionMapper;
import com.ruoyi.satisfaction.mapper.SatisfactionScriptMapper;
import com.ruoyi.satisfaction.domain.row.SatisfactionCategoryRow;
import com.ruoyi.satisfaction.domain.row.SatisfactionIvrScriptRow;
import com.ruoyi.satisfaction.domain.row.SatisfactionOptionScoreRow;
import com.ruoyi.satisfaction.domain.row.SatisfactionScriptQuery;
import com.ruoyi.satisfaction.domain.row.SatisfactionSvyScriptRow;
import com.ruoyi.satisfaction.domain.ServiceSubtaskDetailRatioExport;
import com.ruoyi.satisfaction.service.strategy.SatisfactionScoreStrategyResolver;
import com.ruoyi.satisfaction.service.strategy.SatisfactionSites;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.smartor.service.IPatSatisfactionService;
import com.ruoyi.satisfaction.domain.PatSatisfaction;
import com.ruoyi.satisfaction.service.IPatSatisfactionService;
import static com.ruoyi.common.utils.SecurityUtils.getLoginUser;
@@ -39,28 +48,21 @@
    private PatSatisfactionMapper patSatisfactionMapper;
    @Autowired
    private ServiceSubtaskMapper serviceSubtaskMapper;
    private SatisfactionMapper satisfactionMapper;
    @Autowired
    private ServiceSubtaskDetailMapper serviceSubtaskDetailMapper;
    private SatisfactionScriptMapper satisfactionScriptMapper;
    /**
     * ç«™ç‚¹å£å¾„策略解析器。
     * <p>
     * æœ¬ç±»çš„ {@code averageScoreByStat} å°±æ˜¯ã€Œé€šç”¨å£å¾„(南华)」的综合满意度得分口径,
     * çŽ°æ”¹ä¸ºå§”æ‰˜ {@link com.ruoyi.satisfaction.service.strategy.CommonSatisfactionScoreStrategy},
     * ä¸Žçœç«‹åŒå¾·å£å¾„({@code SltdSatisfactionScoreStrategy})通过同一策略接口并列,
     * é¿å…åŒä¸€ä¸ªæ¦‚念在两处各写一份实现。
     */
    @Autowired
    private IvrLibaTemplateScriptMapper ivrLibaTemplateScriptMapper;
    @Autowired
    private IvrLibaScriptAssortMapper ivrLibaScriptAssortMapper;
    @Autowired
    private SvyLibScriptCategoryMapper svyLibScriptCategoryMapper;
    @Autowired
    private IvrLibaTemplateTargetoptionMapper ivrLibaTemplateTargetoptionMapper;
    @Autowired
    private SvyLibTemplateTargetoptionMapper svyLibTemplateTargetoptionMapper;
    @Autowired
    private SvyLibTemplateScriptMapper svyLibTemplateScriptMapper;
    private SatisfactionScoreStrategyResolver satisfactionScoreStrategyResolver;
    /**
     * æŸ¥è¯¢æ‚£è€…满意度
@@ -201,41 +203,33 @@
        int[] personTotals = new int[2];
        // åªæŸ¥æœ¬æ¬¡æ¡ä»¶ä¸‹å®žé™…存在哪些 type(原来是把接近全表的上百个字段查回来,仅用于判断下面两个分支是否为空)
        List<String> types = serviceSubtaskMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO);
        List<String> types = satisfactionMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO);
        boolean hasVoice = types != null && types.contains("1");
        boolean hasQuestionnaire = types != null && types.contains("2");
        if (hasVoice) {
            //1. å…ˆèŽ·å–ivr_liba_script_assort的满意度分类ID
            IvrLibaScriptAssort ivrLibaScriptAssort = new IvrLibaScriptAssort();
            ivrLibaScriptAssort.setOrgid(patSatisfactionReqVO.getOrgid());
            List<IvrLibaScriptAssort> ivrLibaScriptAssorts = ivrLibaScriptAssortMapper.selectIvrLibaScriptAssortList(ivrLibaScriptAssort);
            List<Long> mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(IvrLibaScriptAssort::getId).collect(Collectors.toList());
            List<SatisfactionCategoryRow> ivrLibaScriptAssorts = satisfactionScriptMapper.selectIvrCategoryList(ivrCategoryQuery(patSatisfactionReqVO));
            List<Long> mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList());
            //2 å†èŽ·å–è¯¥é—®é¢˜æ»¡æ„åº¦åˆ†ç±»ä¸‹çš„IvrLibaTemplateScript的集合
            List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.queryLibTemplateIds(mydIdList);
            List<SatisfactionIvrScriptRow> ivrLibaTemplateScripts = satisfactionScriptMapper.queryIvrScriptByAssort(assortQuery(mydIdList));
            //3 ä¸€æ¬¡ç»Ÿè®¡å…¨éƒ¨é—®é¢˜çš„发送量/填报量/选项分值(原来是每个问题各查 3 æ¬¡ï¼‰
            List<SatScriptRef> scriptRefs = new ArrayList<>();
            for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScripts) {
            for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScripts) {
                scriptRefs.add(new SatScriptRef(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptid(), ivrLibaTemplateScript.getScriptContent()));
            }
            buildSatisfactionDetailEntities(patSatisfactionReqVO, "1", true, scriptRefs, patSatisfactionDetailEntities, personTotals);
        }
        if (hasQuestionnaire) {
            //1. å…ˆèŽ·å–svy_lib_script_category的满意度分类ID
            SvyLibScriptCategory svyLibScriptCategory = new SvyLibScriptCategory();
            svyLibScriptCategory.setOrgid(patSatisfactionReqVO.getOrgid());
            List<SvyLibScriptCategory> svyLibScriptCategories = svyLibScriptCategoryMapper.selectSvyLibScriptCategoryList(svyLibScriptCategory);
            List<Long> mydIdList = svyLibScriptCategories.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SvyLibScriptCategory::getId).collect(Collectors.toList());
            List<SatisfactionCategoryRow> svyLibScriptCategories = satisfactionScriptMapper.selectSvyCategoryList(svyCategoryQuery(patSatisfactionReqVO));
            List<Long> mydIdList = svyLibScriptCategories.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList());
            //2 å†èŽ·å–è¯¥é—®é¢˜æ»¡æ„åº¦åˆ†ç±»ä¸‹çš„IvrLibaTemplateScript的集合
            List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.querySvyLibTemplateIds(mydIdList);
            List<SatisfactionSvyScriptRow> svyLibTemplateScripts = satisfactionScriptMapper.querySvyScriptByCategory(categoryQuery(mydIdList));
            //3 ä¸€æ¬¡ç»Ÿè®¡å…¨éƒ¨é—®é¢˜çš„发送量/填报量/选项分值(原来是每个问题各查 3 æ¬¡ï¼‰
            List<SatScriptRef> scriptRefs = new ArrayList<>();
            for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScripts) {
            for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScripts) {
                // é—®å·çš„选项表按 svy_lib_template_script çš„主键关联
                scriptRefs.add(new SatScriptRef(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getId(), svyLibTemplateScript.getScriptContent()));
            }
@@ -264,21 +258,18 @@
        int[] personTotals = new int[2];
        // åªæŸ¥æœ¬æ¬¡æ¡ä»¶ä¸‹å®žé™…存在哪些 type(原来是把接近全表的上百个字段查回来,仅用于判断下面两个分支是否为空)
        List<String> types = serviceSubtaskMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO);
        List<String> types = satisfactionMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO);
        boolean hasVoice = types != null && types.contains("1");
        boolean hasQuestionnaire = types != null && types.contains("2");
        if (hasVoice) {
            //1. å…ˆèŽ·å–å…¥å‚ä¸­çš„ç»´æŠ¤é›†åˆ
            IvrLibaTemplateScript iltscript = new IvrLibaTemplateScript();
            iltscript.setDimensionList(patSatisfactionReqVO.getDimensionList());
            //2 å†èŽ·å–è¯¥é—®é¢˜æ»¡æ„åº¦åˆ†ç±»ä¸‹çš„IvrLibaTemplateScript的集合
            List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.selectILTSBydimension(iltscript);
            List<SatisfactionIvrScriptRow> ivrLibaTemplateScripts = satisfactionScriptMapper.selectIvrScriptByDimension(dimensionQuery(patSatisfactionReqVO));
            if (CollectionUtils.isNotEmpty(ivrLibaTemplateScripts)) {
                //3 ä¸€æ¬¡ç»Ÿè®¡å…¨éƒ¨é—®é¢˜çš„发送量/填报量/选项分值(原来是每个问题各查 3 æ¬¡ï¼‰
                List<SatScriptRef> scriptRefs = new ArrayList<>();
                for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScripts) {
                for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScripts) {
                    scriptRefs.add(new SatScriptRef(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptid(), ivrLibaTemplateScript.getScriptContent()));
                }
                buildSatisfactionDetailEntities(patSatisfactionReqVO, "1", true, scriptRefs, patSatisfactionDetailEntities, personTotals);
@@ -287,14 +278,11 @@
        if (hasQuestionnaire) {
            //1. å…ˆèŽ·å–å…¥å‚ä¸­çš„æ»¡æ„åº¦ç»´åº¦
            SvyLibTemplateScriptVO svyLibTemplateScriptVO = new SvyLibTemplateScriptVO();
            svyLibTemplateScriptVO.setDimensionList(patSatisfactionReqVO.getDimensionList());
            //2 å†èŽ·å–è¯¥é—®é¢˜æ»¡æ„åº¦åˆ†ç±»ä¸‹çš„IvrLibaTemplateScript的集合
            List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.selectSLTScriptListBydimension(svyLibTemplateScriptVO);
            List<SatisfactionSvyScriptRow> svyLibTemplateScripts = satisfactionScriptMapper.selectSvyScriptByDimension(dimensionQuery(patSatisfactionReqVO));
            if (CollectionUtils.isNotEmpty(svyLibTemplateScripts)) {
                //3 ä¸€æ¬¡ç»Ÿè®¡å…¨éƒ¨é—®é¢˜çš„发送量/填报量/选项分值(原来是每个问题各查 3 æ¬¡ï¼‰
                List<SatScriptRef> scriptRefs = new ArrayList<>();
                for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScripts) {
                for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScripts) {
                    // é—®å·çš„选项表按 svy_lib_template_script çš„主键关联
                    scriptRefs.add(new SatScriptRef(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getId(), svyLibTemplateScript.getScriptContent()));
                }
@@ -317,24 +305,20 @@
        Map<String, List<ScoreCount>> assortNameFillCountMap = new HashMap<>();
        if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 1) {
            //1. å…ˆèŽ·å–ivr_liba_script_assort的满意度分类ID
            IvrLibaScriptAssort ivrLibaScriptAssort = new IvrLibaScriptAssort();
            ivrLibaScriptAssort.setOrgid(patSatisfactionReqVO.getOrgid());
            List<IvrLibaScriptAssort> ivrLibaScriptAssorts = ivrLibaScriptAssortMapper.selectIvrLibaScriptAssortList(ivrLibaScriptAssort);
            List<Long> mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(IvrLibaScriptAssort::getId).collect(Collectors.toList());
            List<SatisfactionCategoryRow> ivrLibaScriptAssorts = satisfactionScriptMapper.selectIvrCategoryList(ivrCategoryQuery(patSatisfactionReqVO));
            List<Long> mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList());
            //2 å†èŽ·å–è¯¥é—®é¢˜æ»¡æ„åº¦åˆ†ç±»ä¸‹çš„IvrLibaTemplateScript的集合
            List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.queryLibTemplateIds(mydIdList);
            List<SatisfactionIvrScriptRow> ivrLibaTemplateScripts = satisfactionScriptMapper.queryIvrScriptByAssort(assortQuery(mydIdList));
            //2.1 ä¸€æ¬¡æ€§æŠŠæœ¬æ¬¡è¯·æ±‚全部模板的发送量、回收人数查回来(原来是每个模板问题各查两次)
            SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1",
                    ivrLibaTemplateScripts.stream().map(IvrLibaTemplateScript::getTemplateid).collect(Collectors.toList()));
                    ivrLibaTemplateScripts.stream().map(SatisfactionIvrScriptRow::getTemplateid).collect(Collectors.toList()));
            Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
            SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
            Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
            //3 æŒ‰scriptAssortname分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数
            Map<String, List<IvrLibaTemplateScript>> scriptGroupByAssortName = ivrLibaTemplateScripts.stream().filter(item -> item.getScriptAssortname() != null).collect(Collectors.groupingBy(IvrLibaTemplateScript::getScriptAssortname));
            Map<String, List<SatisfactionIvrScriptRow>> scriptGroupByAssortName = ivrLibaTemplateScripts.stream().filter(item -> item.getScriptAssortname() != null).collect(Collectors.groupingBy(SatisfactionIvrScriptRow::getScriptAssortname));
            if (scriptGroupByAssortName != null && !scriptGroupByAssortName.isEmpty()) {
                for (String assortNameKey : scriptGroupByAssortName.keySet()) {
                    // è¯¥åˆ†ç±»æ¶‰åŠçš„去重模板:发送人数/回收人数都按模板统计,
@@ -343,8 +327,8 @@
                    //获取该分类的填报总数量(只用于算平均分,按题目聚合的行数)
                    List<ScoreCount> allFillCountList = new ArrayList<>();
                    List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = scriptGroupByAssortName.get(assortNameKey);
                    for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
                    List<SatisfactionIvrScriptRow> ivrLibaTemplateScriptList = scriptGroupByAssortName.get(assortNameKey);
                    for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
                        Long templateId = ivrLibaTemplateScript.getTemplateid();
                        if (templateId != null) {
                            categoryTemplateIds.add(templateId);
@@ -375,24 +359,20 @@
        }
        if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 2) {
            //1. å…ˆèŽ·å–svy_lib_script_category的满意度分类ID
            SvyLibScriptCategory svyLibScriptCategory = new SvyLibScriptCategory();
            svyLibScriptCategory.setOrgid(patSatisfactionReqVO.getOrgid());
            List<SvyLibScriptCategory> svyLibScriptCategories = svyLibScriptCategoryMapper.selectSvyLibScriptCategoryList(svyLibScriptCategory);
            List<Long> mydIdList = svyLibScriptCategories.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SvyLibScriptCategory::getId).collect(Collectors.toList());
            List<SatisfactionCategoryRow> svyLibScriptCategories = satisfactionScriptMapper.selectSvyCategoryList(svyCategoryQuery(patSatisfactionReqVO));
            List<Long> mydIdList = svyLibScriptCategories.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList());
            //2 å†èŽ·å–è¯¥é—®é¢˜æ»¡æ„åº¦åˆ†ç±»ä¸‹çš„IvrLibaTemplateScript的集合
            List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.querySvyLibTemplateIds(mydIdList);
            List<SatisfactionSvyScriptRow> svyLibTemplateScripts = satisfactionScriptMapper.querySvyScriptByCategory(categoryQuery(mydIdList));
            //2.1 ä¸€æ¬¡æ€§æŠŠæœ¬æ¬¡è¯·æ±‚全部模板的发送量、回收人数查回来(原来是每个模板问题各查两次)
            SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2",
                    svyLibTemplateScripts.stream().map(SvyLibTemplateScript::getSvyid).collect(Collectors.toList()));
                    svyLibTemplateScripts.stream().map(SatisfactionSvyScriptRow::getSvyid).collect(Collectors.toList()));
            Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
            SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
            Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
            //3 æŒ‰scriptAssortname分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数
            Map<String, List<SvyLibTemplateScript>> scriptGroupByatCegoryName = svyLibTemplateScripts.stream().filter(item -> item.getCategoryName() != null).collect(Collectors.groupingBy(SvyLibTemplateScript::getCategoryName));
            Map<String, List<SatisfactionSvyScriptRow>> scriptGroupByatCegoryName = svyLibTemplateScripts.stream().filter(item -> item.getCategoryName() != null).collect(Collectors.groupingBy(SatisfactionSvyScriptRow::getCategoryName));
            if (scriptGroupByatCegoryName != null && !scriptGroupByatCegoryName.isEmpty()) {
                for (String categoryNameKey : scriptGroupByatCegoryName.keySet()) {
                    // è¯¥åˆ†ç±»æ¶‰åŠçš„去重模板:发送人数/回收人数都按模板统计,
@@ -401,8 +381,8 @@
                    //获取该分类的填报总数量(只用于算平均分,按题目聚合的行数)
                    List<ScoreCount> allFillCountList = new ArrayList<>();
                    List<SvyLibTemplateScript> svyLibTemplateScriptList = scriptGroupByatCegoryName.get(categoryNameKey);
                    for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) {
                    List<SatisfactionSvyScriptRow> svyLibTemplateScriptList = scriptGroupByatCegoryName.get(categoryNameKey);
                    for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScriptList) {
                        Long templateId = svyLibTemplateScript.getSvyid();
                        if (templateId != null) {
                            categoryTemplateIds.add(templateId);
@@ -419,7 +399,6 @@
                        subidAll += sendCountMap.getOrDefault(templateId, 0L);
                        fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                    }
                    // è¿™é‡Œå…ˆè®°å½•一下每个分类的总人数和填报人数
                    Map<String, Object> personCount = (Map<String, Object>) zzt.get(categoryNameKey);
@@ -449,7 +428,6 @@
        return zzt;
    }
    @Override
    public Map<String, Object> satisfactionGraphDimension(PatSatisfactionReqVO patSatisfactionReqVO) {
        //用来记录柱状图信息(key=满意度维度名称,value=该维度下的总人数和填报人数)
@@ -459,21 +437,17 @@
        Map<String, List<ScoreCount>> assortNameFillCountMap = new HashMap<>();
        if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 1) {
            //1. ä»Žå…¥å‚中获取满意度维维度
            IvrLibaTemplateScript ivrLibaTemplateScript2 = new IvrLibaTemplateScript();
            ivrLibaTemplateScript2.setDimensionList(patSatisfactionReqVO.getDimensionList());
            //2 å†èŽ·å–è¯¥é—®é¢˜æ»¡æ„åº¦åˆ†ç±»ä¸‹çš„IvrLibaTemplateScript的集合
            List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.selectILTSBydimension(ivrLibaTemplateScript2);
            List<SatisfactionIvrScriptRow> ivrLibaTemplateScripts = satisfactionScriptMapper.selectIvrScriptByDimension(dimensionQuery(patSatisfactionReqVO));
            if (ivrLibaTemplateScripts != null && !ivrLibaTemplateScripts.isEmpty()) {
                //3 æŒ‰dimension维度分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数
                List<IvrLibaTemplateScript> dimensionScripts = ivrLibaTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList());
                Map<String, List<IvrLibaTemplateScript>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(IvrLibaTemplateScript::getDimension));
                List<SatisfactionIvrScriptRow> dimensionScripts = ivrLibaTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList());
                Map<String, List<SatisfactionIvrScriptRow>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(SatisfactionIvrScriptRow::getDimension));
                if (groupByDimension != null && !groupByDimension.isEmpty()) {
                    //3.1 ä¸€æ¬¡æ€§æŠŠæœ¬æ¬¡è¯·æ±‚全部模板的发送量、回收人数查回来(原来是每个模板问题各查两次)
                    SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1",
                            dimensionScripts.stream().map(IvrLibaTemplateScript::getTemplateid).collect(Collectors.toList()));
                            dimensionScripts.stream().map(SatisfactionIvrScriptRow::getTemplateid).collect(Collectors.toList()));
                    Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
                    SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
                    Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
@@ -484,8 +458,8 @@
                        //获取该维度的填报分值分布(只用于算平均分)
                        List<ScoreCount> allFillCountList = new ArrayList<>();
                        List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = groupByDimension.get(dimension);
                        for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
                        List<SatisfactionIvrScriptRow> ivrLibaTemplateScriptList = groupByDimension.get(dimension);
                        for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
                            Long templateId = ivrLibaTemplateScript.getTemplateid();
                            if (templateId != null) {
                                dimensionTemplateIds.add(templateId);
@@ -518,19 +492,16 @@
        if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 2) {
            //1.  ä»Žå…¥å‚中获取满意度维维度
            SvyLibTemplateScriptVO svyLibTemplateScriptVO = new SvyLibTemplateScriptVO();
            svyLibTemplateScriptVO.setDimensionList(patSatisfactionReqVO.getDimensionList());
            //2 å†èŽ·å–è¯¥é—®é¢˜æ»¡æ„åº¦åˆ†ç±»ä¸‹çš„IvrLibaTemplateScript的集合
            List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.selectSLTScriptListBydimension(svyLibTemplateScriptVO);
            List<SatisfactionSvyScriptRow> svyLibTemplateScripts = satisfactionScriptMapper.selectSvyScriptByDimension(dimensionQuery(patSatisfactionReqVO));
            if (svyLibTemplateScripts != null && !svyLibTemplateScripts.isEmpty()) {
                //3 æŒ‰dimension维度分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数
                List<SvyLibTemplateScript> dimensionScripts = svyLibTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList());
                Map<String, List<SvyLibTemplateScript>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(SvyLibTemplateScript::getDimension));
                List<SatisfactionSvyScriptRow> dimensionScripts = svyLibTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList());
                Map<String, List<SatisfactionSvyScriptRow>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(SatisfactionSvyScriptRow::getDimension));
                if (groupByDimension != null && !groupByDimension.isEmpty()) {
                    //3.1 ä¸€æ¬¡æ€§æŠŠæœ¬æ¬¡è¯·æ±‚全部模板的发送量、回收人数查回来(原来是每个模板问题各查两次)
                    SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2",
                            dimensionScripts.stream().map(SvyLibTemplateScript::getSvyid).collect(Collectors.toList()));
                            dimensionScripts.stream().map(SatisfactionSvyScriptRow::getSvyid).collect(Collectors.toList()));
                    Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
                    SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
                    Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
@@ -541,8 +512,8 @@
                        //获取该分类的填报分值分布(只用于算平均分)
                        List<ScoreCount> allFillCountList = new ArrayList<>();
                        List<SvyLibTemplateScript> svyLibTemplateScriptList = groupByDimension.get(dimension);
                        for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) {
                        List<SatisfactionSvyScriptRow> svyLibTemplateScriptList = groupByDimension.get(dimension);
                        for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScriptList) {
                            Long templateId = svyLibTemplateScript.getSvyid();
                            if (templateId != null) {
                                dimensionTemplateIds.add(templateId);
@@ -559,7 +530,6 @@
                            subidAll += sendCountMap.getOrDefault(templateId, 0L);
                            fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                        }
                        // è¿™é‡Œå…ˆè®°å½•一下每个分类的总人数和填报人数
                        Map<String, Object> personCount = (Map<String, Object>) zzt.get(dimension);
@@ -588,52 +558,6 @@
            }
        }
        return zzt;
    }
    /**
     * èŽ·å–æœ€é«˜åˆ†
     *
     * @param options
     * @return
     */
    private Double maxScore(List<IvrLibaTemplateTargetoption> options) {
        if (CollectionUtils.isEmpty(options)) return 0.0;
        Double maxScore = options.stream()
                .map(IvrLibaTemplateTargetoption::getScore)
                .filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty())
                .mapToDouble(Double::parseDouble)
                .max()
                .orElse(0.0);
        return maxScore;
    }
    /**
     * èŽ·å–æœ€ä½Žåˆ†
     *
     * @param options
     * @return
     */
    private Double minScore(List<IvrLibaTemplateTargetoption> options) {
        if (CollectionUtils.isEmpty(options)) return 0.0;
        Double minScore = options.stream()
                .map(IvrLibaTemplateTargetoption::getScore)
                .filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty())
                .mapToDouble(Double::parseDouble)
                .min()
                .orElse(0.0);
        return minScore;
    }
    /**
     * èŽ·å–å¹³å‡åˆ†
     *
     * @param fillCount
     * @return
     */
    private Double averageScore(List<ServiceSubtaskDetailDTO> fillCount) {
        if (CollectionUtils.isEmpty(fillCount)) return 0.0;
        double sum = fillCount.stream().filter(dto -> dto.getScore() != null && !dto.getScore().isEmpty()).mapToDouble(dto -> Double.parseDouble(dto.getScore())).sum();
        return new BigDecimal(sum / fillCount.size()).setScale(1, RoundingMode.HALF_UP).doubleValue();
    }
    // ==================== satisfactionGraph èšåˆç»Ÿè®¡ï¼ˆæ›¿ä»£é€ä¸ªæ¨¡æ¿æŸ¥åº“) ====================
@@ -667,7 +591,7 @@
    private Map<Long, Long> mapSatisfactionSendCount(SatisfactionStatQuery statQuery) {
        Map<Long, Long> sendCountMap = new HashMap<>();
        if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return sendCountMap;
        List<Map<String, Object>> rows = serviceSubtaskMapper.selectSatisfactionSendCount(statQuery);
        List<Map<String, Object>> rows = satisfactionMapper.selectSatisfactionSendCount(statQuery);
        if (CollectionUtils.isEmpty(rows)) return sendCountMap;
        for (Map<String, Object> row : rows) {
            Long templateId = toLong(row.get("libtemplateid"));
@@ -685,7 +609,7 @@
    private Map<Long, Long> mapSatisfactionFillPersonCount(SatisfactionStatQuery statQuery) {
        Map<Long, Long> fillPersonCountMap = new HashMap<>();
        if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillPersonCountMap;
        List<Map<String, Object>> rows = serviceSubtaskDetailMapper.selectSatisfactionFillPersonCount(statQuery);
        List<Map<String, Object>> rows = satisfactionMapper.selectSatisfactionFillPersonCount(statQuery);
        if (CollectionUtils.isEmpty(rows)) return fillPersonCountMap;
        for (Map<String, Object> row : rows) {
            Long templateId = toLong(row.get("libtemplateid"));
@@ -701,7 +625,7 @@
    private SatisfactionFillStat querySatisfactionFillStat(SatisfactionStatQuery statQuery) {
        SatisfactionFillStat fillStat = new SatisfactionFillStat();
        if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillStat;
        List<Map<String, Object>> rows = serviceSubtaskDetailMapper.selectSatisfactionFillStat(statQuery);
        List<Map<String, Object>> rows = satisfactionMapper.selectSatisfactionFillStat(statQuery);
        if (CollectionUtils.isEmpty(rows)) return fillStat;
        for (Map<String, Object> row : rows) {
            Long templateId = toLong(row.get("libtemplateid"));
@@ -719,22 +643,31 @@
    /**
     * èŽ·å–å¹³å‡åˆ†ï¼ˆèšåˆç‰ˆï¼šåˆ†å€¼ä¸Žæ¡æ•°ï¼‰
     * <p>
     * å£å¾„与 {@link #averageScore(List)} å®Œå…¨ä¸€è‡´ï¼šåˆ†å­ä¸ºå¯è§£æžåˆ†å€¼ä¹‹å’Œï¼Œåˆ†æ¯ä¸ºæ˜Žç»†è¡Œæ•°ï¼ˆåŒ…含分值为空的行)。
     * å£å¾„与原 {@code averageScore(List)} å®Œå…¨ä¸€è‡´ï¼šåˆ†å­ä¸ºå¯è§£æžåˆ†å€¼ä¹‹å’Œï¼Œåˆ†æ¯ä¸ºæ˜Žç»†è¡Œæ•°ï¼ˆåŒ…含分值为空的行)。
     * æ³¨æ„ä¸èƒ½æ”¹æˆ sql çš„ avg() æˆ–「有分值的行数」做分母,否则返回的 averageScore ä¼šå˜ã€‚
     * <p>
     * <b>策略化(2026)</b>:真正的算法已收敛到「通用口径(南华)」策略
     * {@link com.ruoyi.satisfaction.service.strategy.CommonSatisfactionScoreStrategy},
     * æœ¬æ–¹æ³•只做入参适配(把内部 {@code ScoreCount} è½¬æˆç»Ÿä¸€çš„占比行)。
     * <b>计算结果与迁移前逐位相同</b>(保留 1 ä½ã€HALF_UP;无行时 0.0)。
     */
    private Double averageScoreByStat(List<ScoreCount> fillCount) {
        if (CollectionUtils.isEmpty(fillCount)) return 0.0;
        double sum = 0.0;
        long rows = 0L;
        // é€‚配成策略的统一入参:只带 score ä¸Ž count ä¸¤ä¸ªå­—段(策略只用到这两个)
        List<ServiceSubtaskDetailRatioExport> rows = new ArrayList<>(fillCount.size());
        for (ScoreCount scoreCount : fillCount) {
            if (scoreCount.getCount() <= 0L) continue;
            rows += scoreCount.getCount();
            if (scoreCount.getScore() != null && !scoreCount.getScore().isEmpty()) {
                sum += Double.parseDouble(scoreCount.getScore()) * scoreCount.getCount();
            if (scoreCount.getCount() <= 0L) continue;   // ä¸ŽåŽŸæ–‡ä¸€è‡´ï¼šéžæ­£æ•°çš„èšåˆè¡Œä¸å‚ä¸Ž
            ServiceSubtaskDetailRatioExport row = new ServiceSubtaskDetailRatioExport();
            row.setScore(scoreCount.getScore());
            row.setCount(String.valueOf(scoreCount.getCount()));
            rows.add(row);
            }
        }
        if (rows == 0L) return 0.0;
        return new BigDecimal(sum / rows).setScale(1, RoundingMode.HALF_UP).doubleValue();
        BigDecimal average = satisfactionScoreStrategyResolver
                .resolve(SatisfactionSites.COMMON)
                .comprehensiveScore(rows);
        return average == null ? 0.0 : average.doubleValue();
    }
    /**
@@ -863,7 +796,7 @@
    private SatisfactionFillStatByMatchedtext querySatisfactionFillStatByMatchedtext(SatisfactionStatQuery statQuery) {
        SatisfactionFillStatByMatchedtext fillStat = new SatisfactionFillStatByMatchedtext();
        if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillStat;
        List<Map<String, Object>> rows = serviceSubtaskDetailMapper.selectSatisfactionFillStatByMatchedtext(statQuery);
        List<Map<String, Object>> rows = satisfactionMapper.selectSatisfactionFillStatByMatchedtext(statQuery);
        if (CollectionUtils.isEmpty(rows)) return fillStat;
        for (Map<String, Object> row : rows) {
            Long templateId = toLong(row.get("libtemplateid"));
@@ -894,34 +827,33 @@
                .filter(Objects::nonNull).distinct().collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(scriptIds)) {
            addTargetOptionScores(optionScores, voice
                    ? ivrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores(scriptIds)
                    : svyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores(scriptIds));
                    ? satisfactionScriptMapper.selectIvrTargetOptionScores(scriptIds)
                    : satisfactionScriptMapper.selectSvyTargetOptionScores(scriptIds));
        }
        if (scriptRefs.stream().anyMatch(ref -> ref.getOptionScriptId() == null)) {
            optionScores.put(null, collectTargetOptionScores(voice
                    ? ivrLibaTemplateTargetoptionMapper.selectIvrLibaTargetoptionScores(null)
                    : svyLibTemplateTargetoptionMapper.selectSvyLibTargetoptionScores(null)));
                    ? satisfactionScriptMapper.selectIvrTargetOptionScores(null)
                    : satisfactionScriptMapper.selectSvyTargetOptionScores(null)));
        }
        return optionScores;
    }
    private void addTargetOptionScores(Map<Long, List<String>> optionScores, List<Map<String, Object>> rows) {
    private void addTargetOptionScores(Map<Long, List<String>> optionScores,
                                      List<SatisfactionOptionScoreRow> rows) {
        if (CollectionUtils.isEmpty(rows)) return;
        for (Map<String, Object> row : rows) {
            Long scriptId = toLong(row.get("scriptid"));
        for (SatisfactionOptionScoreRow row : rows) {
            Long scriptId = row.getScriptid();
            if (scriptId == null) continue;
            Object score = row.get("score");
            optionScores.computeIfAbsent(scriptId, key -> new ArrayList<>())
                    .add(score == null ? null : String.valueOf(score));
                    .add(row.getScore());
        }
    }
    private List<String> collectTargetOptionScores(List<Map<String, Object>> rows) {
    private List<String> collectTargetOptionScores(List<SatisfactionOptionScoreRow> rows) {
        List<String> scores = new ArrayList<>();
        if (CollectionUtils.isEmpty(rows)) return scores;
        for (Map<String, Object> row : rows) {
            Object score = row.get("score");
            scores.add(score == null ? null : String.valueOf(score));
        for (SatisfactionOptionScoreRow row : rows) {
            scores.add(row.getScore());
        }
        return scores;
    }
@@ -1101,4 +1033,46 @@
            return byTemplateQuestion.get(statKey(templateId, scriptContent));
        }
    }
    // ==================== æ»¡æ„åº¦æ¨¡å—化:查询对象构造 ====================
    // åŽŸå®žçŽ°ç›´æŽ¥ new smartor çš„实体当入参;模块化后统一用 SatisfactionScriptQuery,
    // è¿™é‡ŒæŠŠä¸‰å¤„构造收敛为帮助方法,条件与原来逐字对应。
    /** æ»¡æ„åº¦åˆ†ç±»æŸ¥è¯¢ï¼ˆorgid + type=myd) */
    private SatisfactionScriptQuery ivrCategoryQuery(PatSatisfactionReqVO reqVO) {
        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
        q.setOrgid(reqVO.getOrgid());
        q.setType("myd");
        return q;
    }
    /** æ»¡æ„åº¦åˆ†ç±»æŸ¥è¯¢ï¼ˆé—®å·ä¾§ï¼Œæ¡ä»¶åŒè¯­éŸ³ä¾§ï¼‰ */
    private SatisfactionScriptQuery svyCategoryQuery(PatSatisfactionReqVO reqVO) {
        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
        q.setOrgid(reqVO.getOrgid());
        q.setType("myd");
        return q;
    }
    /** æŒ‰è¯­éŸ³åˆ†ç±» id å–问题 */
    private SatisfactionScriptQuery assortQuery(List<Long> scriptAssortids) {
        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
        q.setScriptAssortids(scriptAssortids);
        return q;
    }
    /** æŒ‰é—®å·åˆ†ç±» id å–问题 */
    private SatisfactionScriptQuery categoryQuery(List<Long> scriptCategoryids) {
        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
        q.setScriptCategoryids(scriptCategoryids);
        return q;
    }
    /** æŒ‰ç»´åº¦å–问题(语音/问卷共用;两侧「dimensionList ä¸ºç©ºæ—¶æ˜¯å¦æ‹¼ dimension IS NOT NULL」的差异在 SQL é‡Œä¿ç•™ï¼‰ */
    private SatisfactionScriptQuery dimensionQuery(PatSatisfactionReqVO reqVO) {
        SatisfactionScriptQuery q = new SatisfactionScriptQuery();
        q.setDimensionList(reqVO.getDimensionList());
        return q;
    }
}