package com.ruoyi.satisfaction.service.impl; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import java.util.stream.Collectors; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUserDept; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.Arith; import com.ruoyi.common.utils.ArrayListUtils; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.DtoConversionUtils; 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.ruoyi.satisfaction.domain.PatSatisfaction; import com.ruoyi.satisfaction.service.IPatSatisfactionService; import static com.ruoyi.common.utils.SecurityUtils.getLoginUser; /** * 患者满意度Service业务层处理 * * @author lihu * @date 2025-06-30 */ @Service public class PatSatisfactionServiceImpl implements IPatSatisfactionService { @Autowired private PatSatisfactionMapper patSatisfactionMapper; @Autowired private SatisfactionMapper satisfactionMapper; @Autowired private SatisfactionScriptMapper satisfactionScriptMapper; /** * 站点口径策略解析器。 *

* 本类的 {@code averageScoreByStat} 就是「通用口径(南华)」的综合满意度得分口径, * 现改为委托 {@link com.ruoyi.satisfaction.service.strategy.CommonSatisfactionScoreStrategy}, * 与省立同德口径({@code SltdSatisfactionScoreStrategy})通过同一策略接口并列, * 避免同一个概念在两处各写一份实现。 */ @Autowired private SatisfactionScoreStrategyResolver satisfactionScoreStrategyResolver; /** * 查询患者满意度 * * @param id 患者满意度主键 * @return 患者满意度 */ @Override public PatSatisfaction selectPatSatisfactionById(Long id) { PatSatisfaction ps = patSatisfactionMapper.selectPatSatisfactionById(id); double total = ps.getDoctorSatisfaction() + ps.getAuthenticity() + ps.getWeekFinish() + ps.getStandard() + ps.getTimeliness() + ps.getLibrary() + ps.getEnvironment() + ps.getNurseSatisfaction(); ps.setTotal(total); return ps; } /** * 查询患者满意度列表 * * @param patSatisfaction 患者满意度 * @return 患者满意度 */ @Override public List selectPatSatisfactionList(PatSatisfaction patSatisfaction) { List patSatisfactions = patSatisfactionMapper.selectPatSatisfactionList(patSatisfaction); if (CollectionUtils.isNotEmpty(patSatisfactions)) { patSatisfactions.forEach(ps -> { double total = ps.getDoctorSatisfaction() + ps.getAuthenticity() + ps.getWeekFinish() + ps.getStandard() + ps.getTimeliness() + ps.getLibrary() + ps.getEnvironment() + ps.getNurseSatisfaction(); ps.setTotal(total); }); } return patSatisfactions; } /** * 新增患者满意度 * * @param patSatisfaction 患者满意度 * @return 结果 */ @Override public int insertPatSatisfaction(PatSatisfaction patSatisfaction) { patSatisfaction.setCreateTime(DateUtils.getNowDate()); patSatisfaction.setUpdateTime(DateUtils.getNowDate()); return patSatisfactionMapper.insertPatSatisfaction(patSatisfaction); } /** * 新增患者满意度 * * @param patSatisfactions 患者满意度 * @return 结果 */ @Override public int batchInsert(List patSatisfactions) { if(CollectionUtils.isEmpty(patSatisfactions)){ return 0; } List ids = patSatisfactions.stream().map(PatSatisfaction::getId). distinct().collect(Collectors.toList()); List patSatisfactionsOld = new ArrayList<>(); PatSatisfaction patSatisfactionVo = new PatSatisfaction(); patSatisfactionVo.setIds(ids); patSatisfactionsOld = patSatisfactionMapper.selectPatSatisfactionList(patSatisfactionVo); Map> maps = ArrayListUtils.splitListsById(patSatisfactions, patSatisfactionsOld, PatSatisfaction::getId); //只在patSatisfactions的数据,执行新增操作 List addItems = maps.get("onlyIn1"); //只在patSatisfactionsOld的数据,执行删除操作 // List delItems = maps.get("onlyIn2"); //重叠的数据,执行更新操作 List editItems = maps.get("common"); Integer count = 0; LoginUser loginUser = getLoginUser(); SysUser user = loginUser.getUser(); for (PatSatisfaction patSatisfaction0 : editItems) { patSatisfaction0.setUpdateBy(user.getNickName()); patSatisfaction0.setUpdateTime(DateUtils.getNowDate()); count = count + patSatisfactionMapper.updatePatSatisfaction(patSatisfaction0); } for (PatSatisfaction patSatisfaction1 : addItems) { patSatisfaction1.setCreateBy(user.getNickName()); patSatisfaction1.setOrgid(user.getOrgid()); patSatisfaction1.setCreateTime(DateUtils.getNowDate()); patSatisfaction1.setUpdateTime(DateUtils.getNowDate()); count = count + patSatisfactionMapper.insertPatSatisfaction(patSatisfaction1); } return count; } /** * 修改患者满意度 * * @param patSatisfaction 患者满意度 * @return 结果 */ @Override public int updatePatSatisfaction(PatSatisfaction patSatisfaction) { patSatisfaction.setUpdateTime(DateUtils.getNowDate()); return patSatisfactionMapper.updatePatSatisfaction(patSatisfaction); } /** * 批量删除患者满意度 * * @param ids 需要删除的患者满意度主键 * @return 结果 */ @Override public int deletePatSatisfactionByIds(Long[] ids) { return patSatisfactionMapper.deletePatSatisfactionByIds(ids); } /** * 删除患者满意度信息 * * @param id 患者满意度主键 * @return 结果 */ @Override public int deletePatSatisfactionById(Long id) { return patSatisfactionMapper.deletePatSatisfactionById(id); } /** * 患者满意度统计 * * @param patSatisfactionReqVO 患者满意度统计条件 * @return 患者满意度统计结果 */ @Override public PatSatisfactionResVO statistics(PatSatisfactionReqVO patSatisfactionReqVO) { //定义返参 PatSatisfactionResVO result = new PatSatisfactionResVO(); //患者满意度统详情 List patSatisfactionDetailEntities = new ArrayList<>(); // [0]=答题总人数(已经回答问题的) [1]=总人数(包含未回答的) int[] personTotals = new int[2]; // 只查本次条件下实际存在哪些 type(原来是把接近全表的上百个字段查回来,仅用于判断下面两个分支是否为空) List types = satisfactionMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO); boolean hasVoice = types != null && types.contains("1"); boolean hasQuestionnaire = types != null && types.contains("2"); if (hasVoice) { List ivrLibaScriptAssorts = satisfactionScriptMapper.selectIvrCategoryList(ivrCategoryQuery(patSatisfactionReqVO)); List mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList()); List ivrLibaTemplateScripts = satisfactionScriptMapper.queryIvrScriptByAssort(assortQuery(mydIdList)); //3 一次统计全部问题的发送量/填报量/选项分值(原来是每个问题各查 3 次) List scriptRefs = new ArrayList<>(); for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScripts) { scriptRefs.add(new SatScriptRef(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptid(), ivrLibaTemplateScript.getScriptContent(), ivrLibaTemplateScript.getDimension())); } buildSatisfactionDetailEntities(patSatisfactionReqVO, "1", true, scriptRefs, patSatisfactionDetailEntities, personTotals); } if (hasQuestionnaire) { List svyLibScriptCategories = satisfactionScriptMapper.selectSvyCategoryList(svyCategoryQuery(patSatisfactionReqVO)); List mydIdList = svyLibScriptCategories.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList()); List svyLibTemplateScripts = satisfactionScriptMapper.querySvyScriptByCategory(categoryQuery(mydIdList)); //3 一次统计全部问题的发送量/填报量/选项分值(原来是每个问题各查 3 次) List scriptRefs = new ArrayList<>(); for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScripts) { // 问卷的选项表按 svy_lib_template_script 的主键关联 scriptRefs.add(new SatScriptRef(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getId(), svyLibTemplateScript.getScriptContent(), svyLibTemplateScript.getDimension())); } buildSatisfactionDetailEntities(patSatisfactionReqVO, "2", false, scriptRefs, patSatisfactionDetailEntities, personTotals); } result.setTotalAnswerRate(Arith.div(personTotals[0], personTotals[1])); result.setPatSatisfactionDetailEntities(patSatisfactionDetailEntities); result.setTotalPerson(personTotals[0]); return result; } /** * 患者满意度统计(维度) * * @param patSatisfactionReqVO 患者满意度统计条件 * @return 患者满意度统计结果 */ @Override public PatSatisfactionResVO statisticsByDimension(PatSatisfactionReqVO patSatisfactionReqVO) { //定义返参 PatSatisfactionResVO result = new PatSatisfactionResVO(); //患者满意度统详情 List patSatisfactionDetailEntities = new ArrayList<>(); // [0]=答题总人数(已经回答问题的) [1]=总人数(包含未回答的) int[] personTotals = new int[2]; // 只查本次条件下实际存在哪些 type(原来是把接近全表的上百个字段查回来,仅用于判断下面两个分支是否为空) List types = satisfactionMapper.selectSatisfactionSubtaskTypes(patSatisfactionReqVO); boolean hasVoice = types != null && types.contains("1"); boolean hasQuestionnaire = types != null && types.contains("2"); if (hasVoice) { //1. 先获取入参中的维护集合 List ivrLibaTemplateScripts = satisfactionScriptMapper.selectIvrScriptByDimension(dimensionQuery(patSatisfactionReqVO)); if (CollectionUtils.isNotEmpty(ivrLibaTemplateScripts)) { //3 一次统计全部问题的发送量/填报量/选项分值(原来是每个问题各查 3 次) List scriptRefs = new ArrayList<>(); for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScripts) { scriptRefs.add(new SatScriptRef(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptid(), ivrLibaTemplateScript.getScriptContent(), ivrLibaTemplateScript.getDimension())); } buildSatisfactionDetailEntities(patSatisfactionReqVO, "1", true, scriptRefs, patSatisfactionDetailEntities, personTotals); } } if (hasQuestionnaire) { //1. 先获取入参中的满意度维度 List svyLibTemplateScripts = satisfactionScriptMapper.selectSvyScriptByDimension(dimensionQuery(patSatisfactionReqVO)); if (CollectionUtils.isNotEmpty(svyLibTemplateScripts)) { //3 一次统计全部问题的发送量/填报量/选项分值(原来是每个问题各查 3 次) List scriptRefs = new ArrayList<>(); for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScripts) { // 问卷的选项表按 svy_lib_template_script 的主键关联 scriptRefs.add(new SatScriptRef(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getId(), svyLibTemplateScript.getScriptContent(), svyLibTemplateScript.getDimension())); } buildSatisfactionDetailEntities(patSatisfactionReqVO, "2", false, scriptRefs, patSatisfactionDetailEntities, personTotals); } } result.setTotalAnswerRate(Arith.div(personTotals[0], personTotals[1])); result.setPatSatisfactionDetailEntities(patSatisfactionDetailEntities); result.setTotalPerson(personTotals[0]); return result; } @Override public Map satisfactionGraph(PatSatisfactionReqVO patSatisfactionReqVO) { //用来记录柱状图信息(key=满意度分类名称,value=满意度分类下的总人数和填报人数) Map zzt = new HashMap<>(); // 记录一下每种分类的填报情况(只记分值与条数,不再把明细行全部查回 Java) Map> assortNameFillCountMap = new HashMap<>(); if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 1) { List ivrLibaScriptAssorts = satisfactionScriptMapper.selectIvrCategoryList(ivrCategoryQuery(patSatisfactionReqVO)); List mydIdList = ivrLibaScriptAssorts.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList()); List ivrLibaTemplateScripts = satisfactionScriptMapper.queryIvrScriptByAssort(assortQuery(mydIdList)); //2.1 一次性把本次请求全部模板的发送量、回收人数查回来(原来是每个模板问题各查两次) SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1", ivrLibaTemplateScripts.stream().map(SatisfactionIvrScriptRow::getTemplateid).collect(Collectors.toList())); Map sendCountMap = mapSatisfactionSendCount(statQuery); SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery); Map fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery); //3 按scriptAssortname分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数 Map> scriptGroupByAssortName = ivrLibaTemplateScripts.stream().filter(item -> item.getScriptAssortname() != null).collect(Collectors.groupingBy(SatisfactionIvrScriptRow::getScriptAssortname)); if (scriptGroupByAssortName != null && !scriptGroupByAssortName.isEmpty()) { for (String assortNameKey : scriptGroupByAssortName.keySet()) { // 该分类涉及的去重模板:发送人数/回收人数都按模板统计, // 按题目循环累加会把同一个模板的量重复计成「题量」 Set categoryTemplateIds = new LinkedHashSet<>(); //获取该分类的填报总数量(只用于算平均分,按题目聚合的行数) List allFillCountList = new ArrayList<>(); List ivrLibaTemplateScriptList = scriptGroupByAssortName.get(assortNameKey); for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScriptList) { Long templateId = ivrLibaTemplateScript.getTemplateid(); if (templateId != null) { categoryTemplateIds.add(templateId); } //获取填报分值分布(只用于平均分) List fillCount = fillStat.get(templateId, ivrLibaTemplateScript.getScriptContent()); if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount); } //记录发送总人数、回收总人数 Double subidAll = 0.0; Double fillCountAll = 0.0; for (Long templateId : categoryTemplateIds) { subidAll += sendCountMap.getOrDefault(templateId, 0L); fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L); } // 这里先记录一下每个分类的总人数和填报人数 Map personCount = new HashMap<>(); personCount.put("fillCountAll", fillCountAll); personCount.put("subidAll", subidAll); personCount.put("receiveRate", Arith.div(fillCountAll, subidAll)); personCount.put("averageScore", averageScoreByStat(allFillCountList)); zzt.put(assortNameKey, personCount); assortNameFillCountMap.put(assortNameKey, allFillCountList); } } } if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 2) { List svyLibScriptCategories = satisfactionScriptMapper.selectSvyCategoryList(svyCategoryQuery(patSatisfactionReqVO)); List mydIdList = svyLibScriptCategories.stream().filter(item -> item.getType() != null && item.getType().contains("myd")).map(SatisfactionCategoryRow::getId).collect(Collectors.toList()); List svyLibTemplateScripts = satisfactionScriptMapper.querySvyScriptByCategory(categoryQuery(mydIdList)); //2.1 一次性把本次请求全部模板的发送量、回收人数查回来(原来是每个模板问题各查两次) SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2", svyLibTemplateScripts.stream().map(SatisfactionSvyScriptRow::getSvyid).collect(Collectors.toList())); Map sendCountMap = mapSatisfactionSendCount(statQuery); SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery); Map fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery); //3 按scriptAssortname分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数 Map> scriptGroupByatCegoryName = svyLibTemplateScripts.stream().filter(item -> item.getCategoryName() != null).collect(Collectors.groupingBy(SatisfactionSvyScriptRow::getCategoryName)); if (scriptGroupByatCegoryName != null && !scriptGroupByatCegoryName.isEmpty()) { for (String categoryNameKey : scriptGroupByatCegoryName.keySet()) { // 该分类涉及的去重模板:发送人数/回收人数都按模板统计, // 按题目循环累加会把同一个模板的量重复计成「题量」 Set categoryTemplateIds = new LinkedHashSet<>(); //获取该分类的填报总数量(只用于算平均分,按题目聚合的行数) List allFillCountList = new ArrayList<>(); List svyLibTemplateScriptList = scriptGroupByatCegoryName.get(categoryNameKey); for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScriptList) { Long templateId = svyLibTemplateScript.getSvyid(); if (templateId != null) { categoryTemplateIds.add(templateId); } //获取填报分值分布(只用于平均分) List fillCount = fillStat.get(templateId, svyLibTemplateScript.getScriptContent()); if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount); } //记录发送总人数、回收总人数 Double subidAll = 0.0; Double fillCountAll = 0.0; for (Long templateId : categoryTemplateIds) { subidAll += sendCountMap.getOrDefault(templateId, 0L); fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L); } // 这里先记录一下每个分类的总人数和填报人数 Map personCount = (Map) zzt.get(categoryNameKey); if (!Objects.isNull(personCount)) { Double fillCountAll2 = (Double) personCount.get("fillCountAll"); Double subidAll2 = (Double) personCount.get("subidAll"); personCount.put("fillCountAll", fillCountAll2 + fillCountAll); personCount.put("subidAll", subidAll2 + subidAll); personCount.put("receiveRate", Arith.div((Double) personCount.get("fillCountAll"), (Double) personCount.get("subidAll"))); //看一下之前有没有该分类的填报量 List serviceSubtaskDetailDTOS = assortNameFillCountMap.get(categoryNameKey); if (CollectionUtils.isNotEmpty(serviceSubtaskDetailDTOS)) allFillCountList.addAll(serviceSubtaskDetailDTOS); personCount.put("averageScore", averageScoreByStat(allFillCountList)); } else { personCount = new HashMap<>(); personCount.put("fillCountAll", fillCountAll); personCount.put("subidAll", subidAll); personCount.put("receiveRate", Arith.div(fillCountAll, subidAll)); personCount.put("averageScore", averageScoreByStat(allFillCountList)); zzt.put(categoryNameKey, personCount); } } } } return zzt; } @Override public Map satisfactionGraphDimension(PatSatisfactionReqVO patSatisfactionReqVO) { //用来记录柱状图信息(key=满意度维度名称,value=该维度下的总人数和填报人数) Map zzt = new HashMap<>(); // 记录一下每个维度的填报情况(只记分值与条数,不再把明细行全部查回 Java) Map> assortNameFillCountMap = new HashMap<>(); if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 1) { List ivrLibaTemplateScripts = satisfactionScriptMapper.selectIvrScriptByDimension(dimensionQuery(patSatisfactionReqVO)); if (ivrLibaTemplateScripts != null && !ivrLibaTemplateScripts.isEmpty()) { //3 按dimension维度分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数 List dimensionScripts = ivrLibaTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList()); Map> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(SatisfactionIvrScriptRow::getDimension)); if (groupByDimension != null && !groupByDimension.isEmpty()) { //3.1 一次性把本次请求全部模板的发送量、回收人数查回来(原来是每个模板问题各查两次) SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1", dimensionScripts.stream().map(SatisfactionIvrScriptRow::getTemplateid).collect(Collectors.toList())); Map sendCountMap = mapSatisfactionSendCount(statQuery); SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery); Map fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery); for (String dimension : groupByDimension.keySet()) { // 该维度涉及的去重模板:发送人数/回收人数都按模板统计,按题目累加会变成题量 Set dimensionTemplateIds = new LinkedHashSet<>(); //获取该维度的填报分值分布(只用于算平均分) List allFillCountList = new ArrayList<>(); List ivrLibaTemplateScriptList = groupByDimension.get(dimension); for (SatisfactionIvrScriptRow ivrLibaTemplateScript : ivrLibaTemplateScriptList) { Long templateId = ivrLibaTemplateScript.getTemplateid(); if (templateId != null) { dimensionTemplateIds.add(templateId); } //获取填报分值分布(只用于平均分) List fillCount = fillStat.get(templateId, ivrLibaTemplateScript.getScriptContent()); if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount); } //记录发送总人数、回收总人数 Double subidAll = 0.0; Double fillCountAll = 0.0; for (Long templateId : dimensionTemplateIds) { subidAll += sendCountMap.getOrDefault(templateId, 0L); fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L); } // 这里先记录一下每个分类的总人数和填报人数 Map personCount = new HashMap<>(); personCount.put("fillCountAll", fillCountAll); personCount.put("subidAll", subidAll); personCount.put("receiveRate", Arith.div(fillCountAll, subidAll)); personCount.put("averageScore", averageScoreByStat(allFillCountList)); zzt.put(dimension, personCount); assortNameFillCountMap.put(dimension, allFillCountList); } } } } if (patSatisfactionReqVO.getType() == null || patSatisfactionReqVO.getType() == 2) { //1. 从入参中获取满意度维维度 List svyLibTemplateScripts = satisfactionScriptMapper.selectSvyScriptByDimension(dimensionQuery(patSatisfactionReqVO)); if (svyLibTemplateScripts != null && !svyLibTemplateScripts.isEmpty()) { //3 按dimension维度分组,并获取每个集合进行遍历,计算每个分类的总人数和填报人数 List dimensionScripts = svyLibTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList()); Map> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(SatisfactionSvyScriptRow::getDimension)); if (groupByDimension != null && !groupByDimension.isEmpty()) { //3.1 一次性把本次请求全部模板的发送量、回收人数查回来(原来是每个模板问题各查两次) SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2", dimensionScripts.stream().map(SatisfactionSvyScriptRow::getSvyid).collect(Collectors.toList())); Map sendCountMap = mapSatisfactionSendCount(statQuery); SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery); Map fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery); for (String dimension : groupByDimension.keySet()) { // 该维度涉及的去重模板:发送人数/回收人数都按模板统计,按题目累加会变成题量 Set dimensionTemplateIds = new LinkedHashSet<>(); //获取该分类的填报分值分布(只用于算平均分) List allFillCountList = new ArrayList<>(); List svyLibTemplateScriptList = groupByDimension.get(dimension); for (SatisfactionSvyScriptRow svyLibTemplateScript : svyLibTemplateScriptList) { Long templateId = svyLibTemplateScript.getSvyid(); if (templateId != null) { dimensionTemplateIds.add(templateId); } //获取填报分值分布(只用于平均分) List fillCount = fillStat.get(templateId, svyLibTemplateScript.getScriptContent()); if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount); } //记录发送总人数、回收总人数 Double subidAll = 0.0; Double fillCountAll = 0.0; for (Long templateId : dimensionTemplateIds) { subidAll += sendCountMap.getOrDefault(templateId, 0L); fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L); } // 这里先记录一下每个分类的总人数和填报人数 Map personCount = (Map) zzt.get(dimension); if (!Objects.isNull(personCount)) { Double fillCountAll2 = (Double) personCount.get("fillCountAll"); Double subidAll2 = (Double) personCount.get("subidAll"); personCount.put("fillCountAll", fillCountAll2 + fillCountAll); personCount.put("subidAll", subidAll2 + subidAll); personCount.put("receiveRate", Arith.div((Double) personCount.get("fillCountAll"), (Double) personCount.get("subidAll"))); //看一下之前有没有该分类的填报量 List serviceSubtaskDetailDTOS = assortNameFillCountMap.get(dimension); if (CollectionUtils.isNotEmpty(serviceSubtaskDetailDTOS)) allFillCountList.addAll(serviceSubtaskDetailDTOS); personCount.put("averageScore", averageScoreByStat(allFillCountList)); } else { personCount = new HashMap<>(); personCount.put("fillCountAll", fillCountAll); personCount.put("subidAll", subidAll); personCount.put("receiveRate", Arith.div(fillCountAll, subidAll)); personCount.put("averageScore", averageScoreByStat(allFillCountList)); zzt.put(dimension, personCount); } } } } } return zzt; } // ==================== satisfactionGraph 聚合统计(替代逐个模板查库) ==================== /** * 构建聚合统计入参(取值来源与原 DtoConversionUtils.sourceToTarget(patSatisfactionReqVO, ServiceSubtaskSatisfactionEntity.class) 一致) * * @param reqVO 原始请求 * @param type service_subtask.type(1 语音、2 问卷),用字符串比较避免隐式转换 * @param templateIds 本次请求涉及的模板id(ivr 用 templateid,问卷用 svyid) */ private SatisfactionStatQuery buildSatisfactionStatQuery(PatSatisfactionReqVO reqVO, String type, List templateIds) { SatisfactionStatQuery statQuery = new SatisfactionStatQuery(); statQuery.setOrgid(reqVO.getOrgid()); statQuery.setCampusid(reqVO.getCampusid()); statQuery.setType(type); statQuery.setStartTime(reqVO.getStartTime()); statQuery.setEndTime(reqVO.getEndTime()); statQuery.setDeptcodes(reqVO.getDeptcodes()); statQuery.setHospitaldistrictcodes(reqVO.getHospitaldistrictcodes()); statQuery.setLibTemplateIds(CollectionUtils.isEmpty(templateIds) ? templateIds : templateIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList())); return statQuery; } /** * 一次查出全部模板的发送量 * * @return key=模板id,value=发送量(等价于原 querySendCount 结果的 size) */ private Map mapSatisfactionSendCount(SatisfactionStatQuery statQuery) { Map sendCountMap = new HashMap<>(); if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return sendCountMap; List> rows = satisfactionMapper.selectSatisfactionSendCount(statQuery); if (CollectionUtils.isEmpty(rows)) return sendCountMap; for (Map row : rows) { Long templateId = toLong(row.get("libtemplateid")); if (templateId == null) continue; sendCountMap.put(templateId, toLong(row.get("cnt"), 0L)); } return sendCountMap; } /** * 一次查出全部模板的「回收人数」(按模板去重,同一个人答多道题只算一人) * * @return key=模板id,value=回收人数 */ private Map mapSatisfactionFillPersonCount(SatisfactionStatQuery statQuery) { Map fillPersonCountMap = new HashMap<>(); if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillPersonCountMap; List> rows = satisfactionMapper.selectSatisfactionFillPersonCount(statQuery); if (CollectionUtils.isEmpty(rows)) return fillPersonCountMap; for (Map row : rows) { Long templateId = toLong(row.get("libtemplateid")); if (templateId == null) continue; fillPersonCountMap.put(templateId, toLong(row.get("cnt"), 0L)); } return fillPersonCountMap; } /** * 一次查出全部模板的填报量(按 模板+问题+分值 分组) */ private SatisfactionFillStat querySatisfactionFillStat(SatisfactionStatQuery statQuery) { SatisfactionFillStat fillStat = new SatisfactionFillStat(); if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillStat; List> rows = satisfactionMapper.selectSatisfactionFillStat(statQuery); if (CollectionUtils.isEmpty(rows)) return fillStat; for (Map row : rows) { Long templateId = toLong(row.get("libtemplateid")); if (templateId == null) continue; long cnt = toLong(row.get("cnt"), 0L); if (cnt <= 0L) continue; Object scoreValue = row.get("score"); Object questiontextValue = row.get("questiontext"); ScoreCount scoreCount = new ScoreCount(scoreValue == null ? null : String.valueOf(scoreValue), cnt); fillStat.add(templateId, questiontextValue == null ? null : String.valueOf(questiontextValue), scoreCount); } return fillStat; } /** * 获取平均分(聚合版:分值与条数) *

* 口径与原 {@code averageScore(List)} 完全一致:分子为可解析分值之和,分母为明细行数(包含分值为空的行)。 * 注意不能改成 sql 的 avg() 或「有分值的行数」做分母,否则返回的 averageScore 会变。 *

* 策略化(2026):真正的算法已收敛到「通用口径(南华)」策略 * {@link com.ruoyi.satisfaction.service.strategy.CommonSatisfactionScoreStrategy}, * 本方法只做入参适配(把内部 {@code ScoreCount} 转成统一的占比行)。 * 计算结果与迁移前逐位相同(保留 1 位、HALF_UP;无行时 0.0)。 */ private Double averageScoreByStat(List fillCount) { if (CollectionUtils.isEmpty(fillCount)) return 0.0; // 适配成策略的统一入参:只带 score 与 count 两个字段(策略只用到这两个) List rows = new ArrayList<>(fillCount.size()); for (ScoreCount scoreCount : fillCount) { if (scoreCount.getCount() <= 0L) continue; // 与原文一致:非正数的聚合行不参与 ServiceSubtaskDetailRatioExport row = new ServiceSubtaskDetailRatioExport(); row.setScore(scoreCount.getScore()); row.setCount(String.valueOf(scoreCount.getCount())); rows.add(row); } BigDecimal average = satisfactionScoreStrategyResolver .resolve(SatisfactionSites.COMMON) .comprehensiveScore(rows); return average == null ? 0.0 : average.doubleValue(); } /** * 分值分组键:模板id + 分隔符 + 规范化后的问题内容 *

* MySQL 里 questiontext = #{questiontext} 是「忽略大小写、忽略尾部空格」的比较, * 这里用 trim + 小写做等价匹配。 */ private static String statKey(Long templateId, String questiontext) { return templateId + "\u0001" + (questiontext == null ? "" : questiontext.trim().toLowerCase(Locale.ROOT)); } private static Long toLong(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 = toLong(value); return result == null ? defaultValue : result; } /** * 满意度分值聚合结果:分值 + 该分值对应的明细行数 */ private static class ScoreCount { private final String score; private final long count; ScoreCount(String score, long count) { this.score = score; this.count = count; } String getScore() { return score; } long getCount() { return count; } } /** * 满意度填报量聚合结果 *

* byTemplateQuestion:按 模板+问题 分组; * byTemplate:按模板分组(该模板全部问题),用于兼容原 queryFillCount 在 * script_content 为 null 时不拼 questiontext 条件的行为。 */ private static class SatisfactionFillStat { private final Map> byTemplateQuestion = new HashMap<>(); private final Map> byTemplate = new HashMap<>(); void add(Long templateId, String questiontext, ScoreCount scoreCount) { byTemplate.computeIfAbsent(templateId, key -> new ArrayList<>()).add(scoreCount); if (questiontext != null) { byTemplateQuestion.computeIfAbsent(statKey(templateId, questiontext), key -> new ArrayList<>()).add(scoreCount); } } List get(Long templateId, String scriptContent) { if (templateId == null) return null; if (scriptContent == null) return byTemplate.get(templateId); return byTemplateQuestion.get(statKey(templateId, scriptContent)); } } // ==================== statistics / statisticsByDimension 批量统计(替代逐个问题查库) ==================== /** * 满意度明细统计(statistics、statisticsByDimension 共用) *

* 把原来「每个问题 3 条 SQL」改成「整个请求 3 条查询 + 内存组装」: * ① 发送量按 libtemplateid 分组;② 填报量按 模板+问题+matchedtext+score 分组;③ 选项分值按 scriptid 分组。 * * @param reqVO 原始请求 * @param type service_subtask.type(1 语音、2 问卷) * @param voice true=语音问题(选项分值取 ivr_liba_template_targetoption) * @param scriptRefs 本次要统计的问题(顺序即返回顺序) * @param out 明细结果 * @param totals [0]=totalPerson, [1]=allPerson */ private void buildSatisfactionDetailEntities(PatSatisfactionReqVO reqVO, String type, boolean voice, List scriptRefs, List out, int[] totals) { if (CollectionUtils.isEmpty(scriptRefs)) return; List templateIds = scriptRefs.stream().map(SatScriptRef::getTemplateId) .filter(Objects::nonNull).distinct().collect(Collectors.toList()); SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(reqVO, type, templateIds); Map sendCountMap = mapSatisfactionSendCount(statQuery); SatisfactionFillStatByMatchedtext fillStat = querySatisfactionFillStatByMatchedtext(statQuery); //答题【人数】:按 模板+问题 的 distinct sub_id(与「发送人数」同口径) Map answerPersonMap = mapSatisfactionFillPersonCountByQuestion(statQuery); Map> optionScores = loadTargetOptionScores(voice, scriptRefs); for (SatScriptRef scriptRef : scriptRefs) { Long templateId = scriptRef.getTemplateId(); //发送总量(原来是把全部 sub_id 查回 Java 再取 size) long sendCount = templateId == null ? 0L : sendCountMap.getOrDefault(templateId, 0L); //填报【行数】:用于平均分 / 最高分 / 最低分 / 各选项占比(这些口径本来就按明细行计) List fillRows = fillStat.get(templateId, scriptRef.getScriptContent()); long fillCount = countFillRowsByMatchedtext(fillRows); //答题【人数】:用于 答题人数 / 未答题人数 / 答题率 long answerPersonCount = answerPersonMap.getOrDefault( statKey(templateId, scriptRef.getScriptContent()), 0L); PatSatisfactionDetailEntity detail = new PatSatisfactionDetailEntity(); detail.setScriptContent(scriptRef.getScriptContent()); //维度:原本查询里已取到,但构造 SatScriptRef 时被丢掉,导致前端「维度」列一直为空 detail.setDimension(scriptRef.getDimension()); // ⚠️ 人数口径修正:原来这里用的是 fillCount(明细行数), // 而发送人数是 count(distinct id)(人数),两者相减会出现负数(实测 -120 = 15 − 135)。 // 一条 (sub_id, questiontext) 可能有多行明细,所以答题人数必须取 distinct sub_id。 detail.setAnswerPerson((int) answerPersonCount); detail.setNoAnswerPerson(answerPersonCount > 0L && sendCount > 0L ? (int) (sendCount - answerPersonCount) : sendCount > 0L ? (int) sendCount : 0); detail.setAverageScore(String.valueOf(averageScoreByMatchedtext(fillRows))); List scores = optionScores.get(scriptRef.getOptionScriptId()); detail.setMaxScore(String.valueOf(maxScoreOf(scores))); detail.setMinScore(String.valueOf(minScoreOf(scores))); detail.setAnswerRate(sendCount == 0L ? 0 : answerPersonCount == 0L ? 0 : answerPersonCount * 1.0 / sendCount); detail.setMatchedtextStats(buildMatchedtextStats(fillRows, fillCount)); out.add(detail); totals[0] += (int) answerPersonCount; totals[1] += (int) sendCount; } } /** * 一次查出「全部 模板+问题」的答题人数({@code count(distinct ssd.sub_id)})。 *

* key 用与 {@link #statKey(Long, String)} 相同的规则构造,保证与调用方的查找一致 * ({@code statKey} 已做 trim + 小写,等价于 MySQL 的忽略大小写与尾部空格比较)。 */ private Map mapSatisfactionFillPersonCountByQuestion(SatisfactionStatQuery statQuery) { Map answerPersonMap = new HashMap<>(); if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return answerPersonMap; List> rows = satisfactionMapper.selectSatisfactionFillPersonCountByQuestion(statQuery); if (CollectionUtils.isEmpty(rows)) return answerPersonMap; for (Map row : rows) { Long templateId = toLong(row.get("libtemplateid")); if (templateId == null) continue; long cnt = toLong(row.get("cnt"), 0L); if (cnt <= 0L) continue; Object questiontext = row.get("questiontext"); answerPersonMap.merge( statKey(templateId, questiontext == null ? null : String.valueOf(questiontext)), cnt, Long::sum); } return answerPersonMap; } /** * 一次查出全部问题的填报量(按 模板+问题+matchedtext+score 分组,含 targetvalue) */ private SatisfactionFillStatByMatchedtext querySatisfactionFillStatByMatchedtext(SatisfactionStatQuery statQuery) { SatisfactionFillStatByMatchedtext fillStat = new SatisfactionFillStatByMatchedtext(); if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillStat; List> rows = satisfactionMapper.selectSatisfactionFillStatByMatchedtext(statQuery); if (CollectionUtils.isEmpty(rows)) return fillStat; for (Map row : rows) { Long templateId = toLong(row.get("libtemplateid")); if (templateId == null) continue; long cnt = toLong(row.get("cnt"), 0L); if (cnt <= 0L) continue; Object questiontext = row.get("questiontext"); Object matchedtext = row.get("matchedtext"); Object score = row.get("score"); Object targetvalue = row.get("targetvalue"); fillStat.add(templateId, questiontext == null ? null : String.valueOf(questiontext), new MatchedtextCount(matchedtext == null ? null : String.valueOf(matchedtext), score == null ? null : String.valueOf(score), targetvalue == null ? null : String.valueOf(targetvalue), cnt)); } return fillStat; } /** * 一次查出全部问题的选项分值(只取 scriptid、score 两列) *

* 原实现是每个问题查一次整行,而调用方只用到 score 算最高/最低分; * 问题 id 为 null 时原实现不拼 scriptid 条件(等于取该表全部分值),这里用 null 作为 key 存这一份。 */ private Map> loadTargetOptionScores(boolean voice, List scriptRefs) { Map> optionScores = new HashMap<>(); List scriptIds = scriptRefs.stream().map(SatScriptRef::getOptionScriptId) .filter(Objects::nonNull).distinct().collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(scriptIds)) { addTargetOptionScores(optionScores, voice ? satisfactionScriptMapper.selectIvrTargetOptionScores(scriptIds) : satisfactionScriptMapper.selectSvyTargetOptionScores(scriptIds)); } if (scriptRefs.stream().anyMatch(ref -> ref.getOptionScriptId() == null)) { optionScores.put(null, collectTargetOptionScores(voice ? satisfactionScriptMapper.selectIvrTargetOptionScores(null) : satisfactionScriptMapper.selectSvyTargetOptionScores(null))); } return optionScores; } private void addTargetOptionScores(Map> optionScores, List rows) { if (CollectionUtils.isEmpty(rows)) return; for (SatisfactionOptionScoreRow row : rows) { Long scriptId = row.getScriptid(); if (scriptId == null) continue; optionScores.computeIfAbsent(scriptId, key -> new ArrayList<>()) .add(row.getScore()); } } private List collectTargetOptionScores(List rows) { List scores = new ArrayList<>(); if (CollectionUtils.isEmpty(rows)) return scores; for (SatisfactionOptionScoreRow row : rows) { scores.add(row.getScore()); } return scores; } /** * 各选项(matchedtext)数量及占比 *

* 口径与原实现一致:分母是全部明细行数(含 matchedtext 为 null 的行); * targetvalue 用于补全未出现的选项(count=0、ratio=0)。 */ private Map> buildMatchedtextStats(List fillRows, long totalFill) { Map> matchedtextStats = new HashMap<>(); if (CollectionUtils.isEmpty(fillRows) || totalFill <= 0L) return matchedtextStats; Map matchedtextCounts = new LinkedHashMap<>(); String targetvalue = null; for (MatchedtextCount row : fillRows) { if (row.getMatchedtext() != null) { matchedtextCounts.merge(row.getMatchedtext(), row.getCount(), Long::sum); } if (targetvalue == null && row.getTargetvalue() != null) { targetvalue = row.getTargetvalue(); } } for (Map.Entry entry : matchedtextCounts.entrySet()) { Map stat = new HashMap<>(); stat.put("count", entry.getValue()); stat.put("ratio", new BigDecimal((double) entry.getValue() / totalFill * 100).setScale(1, RoundingMode.HALF_UP).doubleValue()); matchedtextStats.put(entry.getKey(), stat); } // 从明细里取targetvalue(&分隔的当前问题的所有选项),补全未出现的选项,count和ratio均为0 if (targetvalue != null) { for (String option : targetvalue.split("&")) { String optionTrim = option.trim(); if (!matchedtextStats.containsKey(optionTrim)) { Map emptyStat = new HashMap<>(); emptyStat.put("count", 0L); emptyStat.put("ratio", 0.0); matchedtextStats.put(optionTrim, emptyStat); } } } return matchedtextStats; } /** * 取填报明细行数(原 fillCount.size()) */ private long countFillRowsByMatchedtext(List fillRows) { if (CollectionUtils.isEmpty(fillRows)) return 0L; long rows = 0L; for (MatchedtextCount row : fillRows) { rows += row.getCount(); } return rows; } /** * 获取平均分(聚合版:分值与条数) *

* 分子为可解析分值之和,分母为明细行数(含分值为空的行)。 */ private Double averageScoreByMatchedtext(List fillRows) { if (CollectionUtils.isEmpty(fillRows)) return 0.0; double sum = 0.0; long rows = 0L; for (MatchedtextCount row : fillRows) { if (row.getCount() <= 0L) continue; rows += row.getCount(); if (row.getScore() != null && !row.getScore().isEmpty()) { sum += Double.parseDouble(row.getScore()) * row.getCount(); } } if (rows == 0L) return 0.0; return new BigDecimal(sum / rows).setScale(1, RoundingMode.HALF_UP).doubleValue(); } /** * 获取最高分(聚合版,空列表返回 0.0) */ private Double maxScoreOf(List scores) { if (CollectionUtils.isEmpty(scores)) return 0.0; return scores.stream().filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty()) .mapToDouble(Double::parseDouble).max().orElse(0.0); } /** * 获取最低分(聚合版,口径同 {空列表返回 0.0) */ private Double minScoreOf(List scores) { if (CollectionUtils.isEmpty(scores)) return 0.0; return scores.stream().filter(scoreStr -> scoreStr != null && !scoreStr.isEmpty()) .mapToDouble(Double::parseDouble).min().orElse(0.0); } /** * 满意度统计用的问题引用:模板id、选项表关联的问题id(可能为 null)、问题内容、维度 */ private static class SatScriptRef { private final Long templateId; private final Long optionScriptId; private final String scriptContent; private final String dimension; SatScriptRef(Long templateId, Long optionScriptId, String scriptContent, String dimension) { this.templateId = templateId; this.optionScriptId = optionScriptId; this.scriptContent = scriptContent; this.dimension = dimension; } Long getTemplateId() { return templateId; } Long getOptionScriptId() { return optionScriptId; } String getScriptContent() { return scriptContent; } String getDimension() { return dimension; } } /** * 满意度填报聚合结果:matchedtext + score + targetvalue + 条数 */ private static class MatchedtextCount { private final String matchedtext; private final String score; private final String targetvalue; private final long count; MatchedtextCount(String matchedtext, String score, String targetvalue, long count) { this.matchedtext = matchedtext; this.score = score; this.targetvalue = targetvalue; this.count = count; } String getMatchedtext() { return matchedtext; } String getScore() { return score; } String getTargetvalue() { return targetvalue; } long getCount() { return count; } } /** * 满意度填报聚合结果(按 matchedtext 版) *

* byTemplateQuestion:按 模板+问题 分组; * byTemplate:按模板分组(该模板全部问题),用于兼容原 queryFillCount 在 * script_content 为 null 时不拼 questiontext 条件的行为。 */ private static class SatisfactionFillStatByMatchedtext { private final Map> byTemplateQuestion = new HashMap<>(); private final Map> byTemplate = new HashMap<>(); void add(Long templateId, String questiontext, MatchedtextCount row) { byTemplate.computeIfAbsent(templateId, key -> new ArrayList<>()).add(row); if (questiontext != null) { byTemplateQuestion.computeIfAbsent(statKey(templateId, questiontext), key -> new ArrayList<>()).add(row); } } List get(Long templateId, String scriptContent) { if (templateId == null) return null; if (scriptContent == null) return byTemplate.get(templateId); return byTemplateQuestion.get(statKey(templateId, scriptContent)); } } // ==================== 满意度模块化:查询对象构造 ==================== // 原实现直接 new smartor 的实体当入参;模块化后统一用 SatisfactionScriptQuery, // 这里把三处构造收敛为帮助方法,条件与原来逐字对应。 /** * 满意度分类查询(语音侧)。 *

* ⚠️ 与原文一致:只传 orgid,不传 type。原实现是 *

     * IvrLibaScriptAssort e = new IvrLibaScriptAssort();
     * e.setOrgid(reqVO.getOrgid());      // ← 只有 orgid
     * mapper.selectIvrLibaScriptAssortList(e);
     * 
* 分类的「是不是满意度(myd)」是在 Java 侧用 * {@code item.getType().contains("myd")} 过滤的(见 {@link #statistics} / * {@link #satisfactionGraph})。 *

* 如果在这里顺手加上 {@code type="myd"},SQL 就会多出一个 * {@code type like '%myd%'} 条件 —— 而 MySQL 的 LIKE 默认不区分大小写, * 会比 Java 的 {@code contains("myd")} 多命中分类,导致统计结果出现差异。 */ private SatisfactionScriptQuery ivrCategoryQuery(PatSatisfactionReqVO reqVO) { SatisfactionScriptQuery q = new SatisfactionScriptQuery(); q.setOrgid(reqVO.getOrgid()); return q; } /** 满意度分类查询(问卷侧,同样只传 orgid,见 {@link #ivrCategoryQuery}) */ private SatisfactionScriptQuery svyCategoryQuery(PatSatisfactionReqVO reqVO) { SatisfactionScriptQuery q = new SatisfactionScriptQuery(); q.setOrgid(reqVO.getOrgid()); return q; } /** 按语音分类 id 取问题 */ private SatisfactionScriptQuery assortQuery(List scriptAssortids) { SatisfactionScriptQuery q = new SatisfactionScriptQuery(); q.setScriptAssortids(scriptAssortids); return q; } /** 按问卷分类 id 取问题 */ private SatisfactionScriptQuery categoryQuery(List 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; } }