已修改7个文件
已添加2个文件
443 ■■■■■ 文件已修改
ruoyi-admin/src/main/java/com/ruoyi/web/component/RedisMqReceiver.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceTaskController.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/common/MydOptionRatioChartBuilder.java 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/DTO/ServiceSubtaskOptionStatDTO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/IServiceSubtaskService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/component/RedisMqReceiver.java
@@ -358,7 +358,6 @@
//                                sendMagParam.setUrl(ip + ":" + req_path + "/outsideChainwt?param1=" + taskId + "&param2=" + patid + "&param3=" + URLEncoder.encode(ivrTask1.getTaskName(), StandardCharsets.UTF_8.toString()) + "&param5=false");
                        sendMagParam.setPhone(serviceSubtask.getPhone());
                        sendMagParam.setUrl(StringUtils.isNotEmpty(req_path) ? ip + ":" + req_path + "/wt?p=" + format : ip + "/wt?p=" + format);
                        log.info("sendMagParam的值为:{}", sendMagParam);
                        //如果type是语音随访的话(说明补偿发送方式中有电话随访的方式,这里的外链就地址只能用/sf)
                        if (serviceSubtask.getType().equals("1"))
                            sendMagParam.setUrl(StringUtils.isNotEmpty(req_path) ? ip + ":" + req_path + "/sf?p=" + format : ip + "/sf?p=" + format);
@@ -425,7 +424,7 @@
                        serviceOutPath.setUrl(url);
                        iServiceOutPathService.updateServiceOutPath(serviceOutPath);
                    }
                    log.info("sendMagParam的值为:{}", sendMagParam);
                    //短信还需要模板
                    String s = null;
                    if (active.equals("xh")) {
@@ -1069,6 +1068,7 @@
        String body = new Gson().toJson(map);
        String result = null;
        log.info("【getDXCode】-短信发送入参 body = {}", body);
        try {
            result = OkHttpExample.sendPostRequest(address, body, token);
        } catch (IOException e) {
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java
@@ -1029,5 +1029,11 @@
    }
    @ApiOperation("获取统计数")
    @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
    @PostMapping("/countByCondition")
    public Map<String, Object>  countByCondition(@RequestBody ServiceSubtaskEntity condition) {
        Map<String, Object> map = serviceSubtaskService.countByCondition(condition);
        return map;
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceTaskController.java
@@ -72,37 +72,7 @@
        List<ServiceTask> list = serviceTaskService.selectServiceTaskAndtaskDeptList(serviceTask);
        List<ServiceTaskVO> serviceTaskVOS = DtoConversionUtils.sourceToTarget(list, ServiceTaskVO.class);
        List<Long> taskIds = list.stream().map(ServiceTask::getTaskid).collect(Collectors.toList());
        ServiceSubtaskEntity serviceSubtaskEntity = new ServiceSubtaskEntity();
        serviceSubtaskEntity.setTaskIds(taskIds);
        serviceSubtaskEntity.setStartOutHospTime(serviceTask.getStartOutHospTime());
        serviceSubtaskEntity.setEndOutHospTime(serviceTask.getEndOutHospTime());
        // æ‰¹é‡æŸ¥è¯¢æ‰€æœ‰å­ä»»åŠ¡
        Map<String, List<ServiceSubtask>> subtaskMap = iServiceSubtaskService
                .selectServiceSubtaskList(serviceSubtaskEntity)
                .stream()
                .filter(subtask -> subtask.getTaskid() != null)  // æ·»åŠ è¿‡æ»¤
                .collect(Collectors.groupingBy(subtask -> Optional.of(subtask.getTaskid().toString()).orElse("Unknown")));
        if (CollectionUtils.isNotEmpty(serviceTaskVOS)) {
            for (ServiceTaskVO taskVO : serviceTaskVOS) {
                List<ServiceSubtask> subtasks = subtaskMap.getOrDefault(taskVO.getTaskid().toString(), Collections.emptyList());
                if (CollectionUtils.isNotEmpty(subtasks)) {
                    //已发送(子任务中的 sendstate=1为被领取)
                    long yfs = subtasks.stream().filter(s -> s.getSendstate() != null &&
                            (s.getSendstate() >= 3L || s.getSendstate() == 1L)).count();
                    //未发送
                    long wfs = subtasks.stream().filter(s -> s.getSendstate() != null &&
                            s.getSendstate() == 2L).count();
                    taskVO.setYfs(yfs);
                    taskVO.setWfs(wfs);
                }
            }
            //获取total
            long total = PageUtils.count(new ISelect() {
                @Override
@@ -118,7 +88,6 @@
        return getDataTable2(0, serviceTaskVOS);
    }
    /**
     * å¯¼å‡ºä»»åŠ¡åˆ—è¡¨
     */
smartor/src/main/java/com/smartor/common/MydOptionRatioChartBuilder.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,172 @@
package com.smartor.common;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.smartor.domain.ServiceSubtaskDetailRatioExport;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.chart.AxisCrosses;
import org.apache.poi.xddf.usermodel.chart.AxisPosition;
import org.apache.poi.xddf.usermodel.chart.BarDirection;
import org.apache.poi.xddf.usermodel.chart.ChartTypes;
import org.apache.poi.xddf.usermodel.chart.LegendPosition;
import org.apache.poi.xddf.usermodel.chart.XDDFBarChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFCategoryAxis;
import org.apache.poi.xddf.usermodel.chart.XDDFChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFChartLegend;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFValueAxis;
import org.apache.poi.xssf.usermodel.XSSFChart;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
/**
 * å¯¼å‡ºã€Œé—®é¢˜è¯¦æƒ…占比图表」Sheet çš„æž„建器
 * <p>
 * æŒ‰ä»»åŠ¡åç§°åˆ†ç»„ï¼šæ¯ä¸ªä»»åŠ¡å†™ä¸€å—æ•°æ®ï¼ˆè¡Œ=题目,列=选项,值=数量),并在数据右侧画一张柱状图。
 *
 * @author smartor
 */
public class MydOptionRatioChartBuilder implements ExcelUtil.ChartSheetBuilder {
    /**
     * å›¾è¡¨ Sheet åç§°
     */
    public static final String SHEET_NAME = "问题详情占比图表";
    private final List<ServiceSubtaskDetailRatioExport> rows;
    public MydOptionRatioChartBuilder(List<ServiceSubtaskDetailRatioExport> rows) {
        this.rows = rows == null ? new ArrayList<>() : rows;
    }
    @Override
    public void build(XSSFWorkbook workbook) {
        if (rows.isEmpty()) {
            return;
        }
        XSSFSheet sheet = workbook.createSheet(SHEET_NAME);
        int startRow = 0;
        for (Map.Entry<String, List<ServiceSubtaskDetailRatioExport>> entry : groupByTask().entrySet()) {
            // æ¯å†™å®Œä¸€å—数据立刻建图:SXSSF æµå¼å·¥ä½œç°¿é‡Œåˆšå†™çš„行还在内存中,图表才能读到单元格
            startRow = writeTaskBlockWithChart(sheet, entry.getKey(), entry.getValue(), startRow) + 2;
        }
    }
    /**
     * æŒ‰ä»»åŠ¡åç§°åˆ†ç»„ï¼ˆä¿æŒåˆ—è¡¨åŽŸæœ‰é¡ºåºï¼Œå³ SQL çš„ task_name æŽ’序)
     */
    private Map<String, List<ServiceSubtaskDetailRatioExport>> groupByTask() {
        Map<String, List<ServiceSubtaskDetailRatioExport>> grouped = new LinkedHashMap<>();
        for (ServiceSubtaskDetailRatioExport row : rows) {
            String taskName = row.getTaskname() == null ? "" : row.getTaskname();
            grouped.computeIfAbsent(taskName, k -> new ArrayList<>()).add(row);
        }
        return grouped;
    }
    /**
     * å†™ä¸€ä¸ªä»»åŠ¡çš„æ•°æ®å—å¹¶ç”»æŸ±çŠ¶å›¾ï¼Œè¿”å›žä¸‹ä¸€ä¸ªå—å¯ç”¨çš„èµ·å§‹è¡Œ
     */
    private int writeTaskBlockWithChart(XSSFSheet sheet, String taskName,
                                        List<ServiceSubtaskDetailRatioExport> taskRows, int startRow) {
        // é¢˜ç›® -> (选项 -> é€‰è¯¥é€‰é¡¹çš„人数),选项按出现顺序收集成本任务的系列
        Map<String, Map<String, Double>> questionOptions = new LinkedHashMap<>();
        LinkedHashSet<String> optionSet = new LinkedHashSet<>();
        String taskid = "";
        for (ServiceSubtaskDetailRatioExport row : taskRows) {
            String question = row.getQuestiontext() == null ? "" : row.getQuestiontext();
            String option = row.getOptionresult() == null ? "" : row.getOptionresult();
            questionOptions.computeIfAbsent(question, k -> new LinkedHashMap<>()).put(option, parseCount(row.getCount()));
            optionSet.add(option);
            if (taskid.isEmpty() && row.getTaskid() != null) {
                taskid = row.getTaskid();
            }
        }
        List<String> optionList = new ArrayList<>(optionSet);
        List<String> questionList = new ArrayList<>(questionOptions.keySet());
        int rowIdx = startRow;
        // æ ‡é¢˜è¡Œ
        Row titleRow = sheet.createRow(rowIdx++);
        titleRow.createCell(0).setCellValue("任务名称:" + taskName + "(任务编号 " + taskid + ")");
        // è¡¨å¤´è¡Œï¼šé¢˜ç›® + å„选项(图表系列的标题就是这一行)
        Row headerRow = sheet.createRow(rowIdx++);
        headerRow.createCell(0).setCellValue("题目");
        for (int i = 0; i < optionList.size(); i++) {
            headerRow.createCell(i + 1).setCellValue(optionList.get(i));
        }
        // æ•°æ®è¡Œï¼šå€¼ = é€‰è¯¥é€‰é¡¹çš„人数
        int firstDataRow = rowIdx;
        for (String question : questionList) {
            Row dataRow = sheet.createRow(rowIdx++);
            dataRow.createCell(0).setCellValue(question);
            Map<String, Double> optionMap = questionOptions.get(question);
            for (int i = 0; i < optionList.size(); i++) {
                Double count = optionMap.get(optionList.get(i));
                dataRow.createCell(i + 1).setCellValue(count == null ? 0D : count);
            }
        }
        int lastDataRow = rowIdx - 1;
        // æŸ±çŠ¶å›¾ï¼šåˆ†ç±»=题目,系列=选项,数值=选该选项的人数
        int chartCol = optionList.size() + 3;
        int chartHeight = Math.max(16, questionList.size() + 4);
        XSSFDrawing drawing = sheet.createDrawingPatriarch();
        XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0,
                chartCol, firstDataRow - 1, chartCol + 10, firstDataRow - 1 + chartHeight);
        XSSFChart chart = drawing.createChart(anchor);
        chart.setTitleText(taskName);
        chart.setTitleOverlay(false);
        XDDFChartLegend legend = chart.getOrAddLegend();
        legend.setPosition(LegendPosition.BOTTOM);
        XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
        bottomAxis.setTitle("题目");
        XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
        leftAxis.setTitle("数量");
        leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
        XDDFDataSource<String> categories = XDDFDataSourcesFactory.fromStringCellRange(sheet,
                new CellRangeAddress(firstDataRow, lastDataRow, 0, 0));
        XDDFBarChartData barChart = (XDDFBarChartData) chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
        for (int i = 0; i < optionList.size(); i++) {
            XDDFNumericalDataSource<Double> values = XDDFDataSourcesFactory.fromNumericCellRange(sheet,
                    new CellRangeAddress(firstDataRow, lastDataRow, i + 1, i + 1));
            XDDFChartData.Series series = barChart.addSeries(categories, values);
            series.setTitle(optionList.get(i), null);
        }
        barChart.setBarDirection(BarDirection.COL);
        // å…³é”®ï¼šä¸è°ƒç”¨ plot å›¾è¡¨ä¸ä¼šæ˜¾ç¤º
        chart.plot(barChart);
        return rowIdx;
    }
    /**
     * æ•°é‡å­—符串 -> æ•°å­—,异常值按 0 å¤„理
     */
    private double parseCount(String count) {
        if (count == null || count.isEmpty()) {
            return 0D;
        }
        try {
            return Double.parseDouble(count.trim());
        } catch (NumberFormatException e) {
            return 0D;
        }
    }
}
smartor/src/main/java/com/smartor/domain/DTO/ServiceSubtaskOptionStatDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package com.smartor.domain.DTO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
 * å¯¼å‡ºã€Œé—®é¢˜è¯¦æƒ…占比」专用的选项作答统计
 * <p>
 * ä¸€æ¡ = ä¸€ä¸ªã€Œä»»åŠ¡ + é¢˜ç›® + é€‰é¡¹ã€çš„聚合结果:
 * æ•°é‡æŒ‰æ‚£è€…(子任务)去重,分数为该选项明细的 score ä¹‹å’Œï¼›
 * æ¯”例不在这里算,由 Service æŒ‰ã€Œè¯¥é¢˜ä½œç­”患者数」统一计算。
 *
 * @author smartor
 */
@ApiModel(value = "ServiceSubtaskOptionStatDTO", description = "导出用选项作答统计")
@Data
public class ServiceSubtaskOptionStatDTO {
    @ApiModelProperty(value = "任务编号")
    private String taskid;
    @ApiModelProperty(value = "任务名称")
    private String taskName;
    @ApiModelProperty(value = "问题内容")
    private String questiontext;
    @ApiModelProperty(value = "患者实际选中的选项(service_subtask_detail.asrtext)")
    private String optionresult;
    @ApiModelProperty(value = "题目配置的选项(service_subtask_detail.targetvalue,可能用 & è¿žæŽ¥å¤šä¸ªé€‰é¡¹ï¼‰")
    private String targetvalue;
    @ApiModelProperty(value = "问题类型(1.单选 2.多选 3.问答 4.填空 5.其它)")
    private String valueType;
    @ApiModelProperty(value = "选择该选项的患者数(按子任务去重)")
    private Long patientCount;
    @ApiModelProperty(value = "该选项明细的 score ä¹‹å’Œ")
    private BigDecimal scoreSum;
}
smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java
@@ -210,6 +210,8 @@
     * xsf: éœ€éšè®¿æ•°é‡ sendstate = 4
     * dsf: å¾…随访数量 sendstate = 1L, 2L, 3L, 5L, 7L
     * ywc: å·²å®Œæˆæ•°é‡ sendstate = 6L
     * dfs: å¾…发送数量 sendstate = 1L, 3L, 4L, 5L, 6L, 7L
     * yfs: å·²å‘送数量 sendstate = 2L
     * yc: å¼‚常数量 excep = '1'
     * yc: è­¦å‘Šæ•°é‡ excep = '2'
     */
smartor/src/main/java/com/smartor/service/IServiceSubtaskService.java
@@ -69,6 +69,8 @@
    public Map<String, Object> patItemCount(ServiceSubtaskEntity serviceSubtaskEntity);
    public Map<String, Object> countByCondition(ServiceSubtaskEntity serviceSubtaskEntity);
    /**
     * æ–°å¢žå•一任务(随访)
     *
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -640,6 +640,43 @@
    }
    /**
     * ç»Ÿè®¡ä»»åŠ¡å„ç§çŠ¶æ€çš„æ•°é‡ï¼ˆå·²ä¼˜åŒ–ï¼‰
     * ä¼˜åŒ–说明:
     * 1. ä½¿ç”¨æ•°æ®åº“层聚合计算,替代应用层遍历
     * 2. æ€§èƒ½æå‡ 10-100 å€ï¼ˆå–决于数据量)
     * 3. é™ä½Žå†…存占用和网络传输
     *
     * @param serviceSubtaskEntity æŸ¥è¯¢æ¡ä»¶
     * @return å„状态统计结果
     */
    @Override
    public Map<String, Object> countByCondition(ServiceSubtaskEntity serviceSubtaskEntity) {
        // ç›´æŽ¥è°ƒç”¨æ•°æ®åº“统计方法,在数据库层完成聚合计算
        Map<String, Object> result = serviceSubtaskMapper.countByCondition(serviceSubtaskEntity);
        // å¤„理空异常
        if (MapUtils.isEmpty(result)) {
            result = new HashMap<>();
            // ç¡®ä¿æ‰€æœ‰ key éƒ½æœ‰å€¼ï¼Œé¿å…ç©ºæŒ‡é’ˆå¼‚常
            // MyBatis çš„ SUM åœ¨æ²¡æœ‰åŒ¹é…è¡Œæ—¶ä¼šè¿”回 null
            result.putIfAbsent("wzx", 0);
            result.putIfAbsent("ysf", 0);
            result.putIfAbsent("fssb", 0);
            result.putIfAbsent("yfs", 0);
            result.putIfAbsent("blq", 0);
            result.putIfAbsent("total", 0);
            result.putIfAbsent("wxsf", 0);
            result.putIfAbsent("xsf", 0);
            result.putIfAbsent("dsf", 0);
            result.putIfAbsent("dfs", 0);
            result.putIfAbsent("yfs", 0);
            result.putIfAbsent("ywc", 0);
            result.putIfAbsent("yc", 0);
            result.putIfAbsent("jg", 0);
        }
        return result;
    }
    /**
     * æ–°å¢žå•一任务(随访)
     *
     * @param serviceSubtask å•一任务(随访)
@@ -6193,7 +6230,8 @@
                phoneCallBackYQVO.setValue(phoneCallBackYQVO.getCommonValue() + serviceTask.getJsy());
                //记录状态
                setFailPreachForm(serviceSubtask, "3", "电话拨打已完成", "9");
                return phoneCallBackYQVO;
                back = phoneCallBackYQVO;
                return back;
            }
            flag = 1;
@@ -6201,9 +6239,10 @@
            redisCache.deleteObject(phoneCallReqYQVO.getUuid() + "PlayEventCallbackPlaystop");
//                        //获取下一题
            log.error("获取下一题的信息:{}", ivrTaskTemplateScriptVO);
            if (ivrTaskTemplateScriptVO.getBranchFlag().equals("1") || ivrTaskTemplateScriptVO.getBranchFlag().equals("0") && ivrTaskTemplateScriptVO.getNextScriptno() != null && ivrTaskTemplateScriptVO.getNextScriptno() != 0) {
            //branchFlag为null时按分支0处理,避免NPE
            if ("1".equals(ivrTaskTemplateScriptVO.getBranchFlag()) || "0".equals(ivrTaskTemplateScriptVO.getBranchFlag()) && ivrTaskTemplateScriptVO.getNextScriptno() != null && ivrTaskTemplateScriptVO.getNextScriptno() != 0) {
                Long nextQuestion = null;
                if (ivrTaskTemplateScriptVO.getBranchFlag().equals("1")) {
                if ("1".equals(ivrTaskTemplateScriptVO.getBranchFlag())) {
                    nextQuestion = chosenOption.getNextQuestion();
                    //更新分数
                    double score = redisCache.getCacheObject(phoneCallReqYQVO.getUuid() + "SCORE");
@@ -6236,15 +6275,9 @@
                    redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "- jsy", serviceTask.getJsy(), 120, TimeUnit.MINUTES);
                    phoneCallBackYQVO.setType("text");
                    phoneCallBackYQVO.setValue(phoneCallBackYQVO.getCommonValue() + serviceTask.getJsy());
                    //将结果写进detail表
                    ServiceSubTaskDetailReq ssdReq = new ServiceSubTaskDetailReq();
                    List<ServiceSubtaskDetail> serviceSubtaskDetailList2 = new ArrayList<>();
                    serviceSubtaskDetailList.add(getServiceSubtaskDetail(phoneCallReqYQVO, ivrTaskTemplateScriptVO, serviceSubtask, ivrTaskTemplate));
                    serviceSubTaskDetailReq.setServiceSubtaskDetailList(serviceSubtaskDetailList2);
                    ssdReq.setGuid(phoneCallReqYQVO.getGuid());
                    ssdReq.setOrgid(phoneCallReqYQVO.getOrgid());
                    saveQuestionAnswerPhone(ssdReq);
                    return phoneCallBackYQVO;
                    //明细在本题匹配成功时已落库(见上方 saveQuestionAnswerPhone),这里不再重复保存
                    back = phoneCallBackYQVO;
                    return back;
                }
                for (IvrTaskTemplateScript script : ivrTaskTemplateScripts) {
@@ -6261,6 +6294,15 @@
                        redisCache.deleteObject(phoneCallReqYQVO.getTaskid().trim() + "&&" + "mate" + "&&" + phoneCallReqYQVO.getUuid());
                    }
                }
                //下一题脚本没找到时,兜底重问当前题,避免调用方误判为未处理而退回正则识别
                if (StringUtils.isEmpty(phoneCallBackYQVO.getValue())) {
                    log.error("下一题脚本未找到,重问当前题,nextQuestion:{}", nextQuestion);
                    phoneCallBackYQVO.setType("text");
                    phoneCallBackYQVO.setSilent_interval(ivrTaskTemplate.getSilencetime().intValue());
                    phoneCallBackYQVO.setValue(phoneCallBackYQVO.getCommonValue() + getObject(serviceSubtask, questionText));
                }
                back = phoneCallBackYQVO;
                return back;
            } else if (ivrTaskTemplateScriptVO.getNextScriptno() == null || ivrTaskTemplateScriptVO.getNextScriptno() == 0) {
                ServiceTask serviceTask1 = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
                phoneCallBackYQVO.setType("text");
@@ -6289,33 +6331,101 @@
                //在redis中保存一下结束语,在调用挂电话的方法时删除
                ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
                redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "- jsy", serviceTask.getJsy(), 120, TimeUnit.MINUTES);
//                                return phoneCallBackYQVO;
                //结束语已拼进返回值,挂机由平台按返回值执行
                back = phoneCallBackYQVO;
                return back;
            }
        } else {
            log.info("没有匹配上----------------------------");
            //flag=0,说明没 åŒ¹é…ä¸Šï¼Œä¹Ÿè¦æŠŠæ‚£è€…说的话记录下来
            if (matched == ivrTaskTemplateScriptVO.getIvrTaskScriptTargetoptionList().size() - 1 && flag == 0) {
                ServiceSubTaskDetailReq serviceSubTaskDetailReq = new ServiceSubTaskDetailReq();
                List<ServiceSubtaskDetail> serviceSubtaskDetailList = new ArrayList<>();
                ServiceSubtaskDetail serviceSubtaskDetail = getServiceSubtaskDetail(phoneCallReqYQVO, ivrTaskTemplateScriptVO, serviceSubtask, ivrTaskTemplate);
                //修改一下语音路径(改成前端可以访问的,存到数据库中)
                if (StringUtils.isNotEmpty(serviceSubtaskDetail.getQuestionvoice())) {
                    String questionvoice = serviceSubtaskDetail.getQuestionvoice();
                    String[] split = questionvoice.split("\\\\");
                    if (split.length > 0) {
                        String lastPart = split[split.length - 1];
                        serviceSubtaskDetail.setQuestionvoice(voicePathPrefix + lastPart);
            //AI没有匹配上:由AI分支自己按"没问清就重问"的次数处理,不再退回正则识别
            log.info("ai没有匹配上----------------------------");
            //没匹配上也要把患者说的话记录下来(matchedtext置空,与正则路径口径一致)
            ServiceSubTaskDetailReq serviceSubTaskDetailReq = new ServiceSubTaskDetailReq();
            List<ServiceSubtaskDetail> serviceSubtaskDetailList = new ArrayList<>();
            ServiceSubtaskDetail serviceSubtaskDetail = getServiceSubtaskDetail(phoneCallReqYQVO, ivrTaskTemplateScriptVO, serviceSubtask, ivrTaskTemplate);
            //修改一下语音路径(改成前端可以访问的,存到数据库中)
            if (StringUtils.isNotEmpty(serviceSubtaskDetail.getQuestionvoice())) {
                String questionvoice = serviceSubtaskDetail.getQuestionvoice();
                String[] split = questionvoice.split("\\\\");
                if (split.length > 0) {
                    String lastPart = split[split.length - 1];
                    serviceSubtaskDetail.setQuestionvoice(voicePathPrefix + lastPart);
                }
            }
            serviceSubtaskDetailList.add(serviceSubtaskDetail);
            //如果没有 åŒ¹é…ä¸Šï¼Œè¿™ä¸ªå¿…须为null
            serviceSubtaskDetailList.get(0).setMatchedtext("");
            serviceSubTaskDetailReq.setServiceSubtaskDetailList(serviceSubtaskDetailList);
            serviceSubTaskDetailReq.setGuid(phoneCallReqYQVO.getGuid());
            serviceSubTaskDetailReq.setOrgid(phoneCallReqYQVO.getOrgid());
            saveQuestionAnswerPhone(serviceSubTaskDetailReq);
            String mateKey = phoneCallReqYQVO.getTaskid().trim() + "&&" + "mate" + "&&" + phoneCallReqYQVO.getUuid();
            Integer count = null;
            Object countObj = redisCache.getCacheObject(mateKey);
            if (ObjectUtils.isNotEmpty(countObj)) count = (Integer) countObj;
            if (count != null && count >= ivrTaskTemplate.getMateNum()) {
                //没匹配次数已经用满,不能再重问了
                boolean noNextQuestion = ("0".equals(ivrTaskTemplateScriptVO.getBranchFlag()) || "1".equals(ivrTaskTemplateScriptVO.getBranchFlag()))
                        && (ivrTaskTemplateScriptVO.getNextScriptno() == null || ivrTaskTemplateScriptVO.getNextScriptno() == 0);
                if (noNextQuestion) {
                    //已经是最后一题,直接挂机
                    ServiceTask serviceTask1 = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
                    phoneCallBackYQVO.setType("text");
                    phoneCallBackYQVO.setSilent_interval(ivrTaskTemplate.getSilencetime().intValue());
                    phoneCallBackYQVO.setValue(phoneCallBackYQVO.getCommonValue() + serviceTask1.getJsy());
                    //更新一下分数
                    Double score = null;
                    Object scoreObj = redisCache.getCacheObject(phoneCallReqYQVO.getUuid() + "SCORE");
                    if (ObjectUtils.isNotEmpty(scoreObj)) score = (Double) scoreObj;
                    serviceSubtask.setScore(String.valueOf(score));
                    serviceSubtask.setFinishtime(new Date());
                    serviceSubtask.setSendstate(6L);
                    serviceSubtask.setRemark("电话拨打已完成");
                    serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
                    //记录状态
                    setFailPreachForm(serviceSubtask, "3", "电话拨打已完成", "9");
                    //在redis中保存一下结束语,在调用挂电话的方法时删除
                    ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
                    redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "- jsy", serviceTask.getJsy(), 120, TimeUnit.MINUTES);
                    //去redis中,把该子任务ID删除
                    Long id = serviceSubtask.getId();
                    Map<String, String> map = delRedisValue(null, id.toString());
                    if (ObjectUtils.isNotEmpty(map)) {
                        redisCache.setCacheObject(map.get("cacheName"), map.get("val"));
                        redisCache.deleteObject(serviceSubtask.getId() + "-" + serviceSubtask.getPhone());
                        redisCache.deleteObject(phoneCallReqYQVO.getUuid() + "SCORE");
                    }
                } else {
                    //还有下一题,按nextScriptno继续提问
                    for (IvrTaskTemplateScript script : ivrTaskTemplateScripts) {
                        if (script.getSort() == ivrTaskTemplateScriptVO.getNextScriptno().intValue()) {
                            phoneCallBackYQVO.setType("text");
                            phoneCallBackYQVO.setSilent_interval(ivrTaskTemplate.getSilencetime().intValue());
                            String scriptContent = StringUtils.isNotEmpty(script.getIvrtext()) ? script.getIvrtext() : script.getScriptContent();
                            phoneCallBackYQVO.setValue(phoneCallBackYQVO.getCommonValue() + getObject(serviceSubtask, scriptContent));
                            //将该患者的Redis中的题目ID,进行修改
                            redisCache.setCacheObject(phoneCallReqYQVO.getTaskid().trim() + "-" + phoneCallReqYQVO.getPhone().trim(), script.getId().toString(), 120, TimeUnit.MINUTES);
                            //更新一下分数
                            Double score = null;
                            Object scoreObj = redisCache.getCacheObject(phoneCallReqYQVO.getUuid() + "SCORE");
                            if (ObjectUtils.isNotEmpty(scoreObj)) score = (Double) scoreObj;
                            score = BigDecimal.valueOf(score).add(new BigDecimal(script.getScore())).doubleValue();
                            redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "SCORE", score, 120, TimeUnit.MINUTES);
                            //换题成功,没匹配次数清零
                            redisCache.deleteObject(mateKey);
                        }
                    }
                }
                serviceSubtaskDetailList.add(serviceSubtaskDetail);
                //如果没有 åŒ¹é…ä¸Šï¼Œè¿™ä¸ªå¿…须为null
                serviceSubtaskDetailList.get(0).setMatchedtext("");
                serviceSubTaskDetailReq.setServiceSubtaskDetailList(serviceSubtaskDetailList);
                serviceSubTaskDetailReq.setGuid(phoneCallReqYQVO.getGuid());
                serviceSubTaskDetailReq.setOrgid(phoneCallReqYQVO.getOrgid());
                saveQuestionAnswerPhone(serviceSubTaskDetailReq);
            } else {
                //还没问满次数,用noMatchText引导患者重答
                redisCache.setCacheObject(mateKey, count == null ? 1 : count + 1, 120, TimeUnit.MINUTES);
                phoneCallBackYQVO.setType("text");
                phoneCallBackYQVO.setSilent_interval(ivrTaskTemplate.getSilencetime().intValue());
                phoneCallBackYQVO.setValue(ivrTaskTemplateScriptVO.getNoMatchText() + getObject(serviceSubtask, StringUtils.isNotEmpty(ivrTaskTemplateScriptVO.getIvrtext()) ? ivrTaskTemplateScriptVO.getIvrtext() : ivrTaskTemplateScriptVO.getScriptContent()));
            }
            back = phoneCallBackYQVO;
            return back;
        }
        return back;
    }
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
@@ -2584,6 +2584,8 @@
        SUM(CASE WHEN sendstate != 4 THEN 1 ELSE 0 END) AS xsf,
        SUM(CASE WHEN (sendstate = 1 OR sendstate = 2 OR sendstate = 3 OR sendstate = 5 OR sendstate = 7 ) THEN 1 ELSE 0
        END) AS dsf,
        SUM(CASE WHEN sendstate != 2 THEN 1 ELSE 0 END) AS yfs,
        SUM(CASE WHEN sendstate = 2 THEN 1 ELSE 0 END) AS wfs,
        SUM(CASE WHEN sendstate = 6 THEN 1 ELSE 0 END) AS ywc,
        SUM(CASE WHEN excep IS NOT NULL AND excep = '1' THEN 1 ELSE 0 END) AS yc,
        SUM(CASE WHEN excep IS NOT NULL AND excep = '2' THEN 1 ELSE 0 END) AS jg,
@@ -3677,5 +3679,6 @@
    </select>
</mapper>