陈昶聿
2 天以前 88d4cd31fa5b197a91ed081ba1df292420af2d05
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -14,11 +14,13 @@
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.common.utils.sms.smsUtils;
import com.ruoyi.common.utils.sms.SmsUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysDeptService;
import com.smartor.common.FtpService;
import com.smartor.common.MtSubmitSmUtil;
import com.smartor.common.ScheduleSubtaskBuilder;
@@ -137,6 +139,9 @@
    private IPatArchiveService patArchiveService;
    @Autowired
    private ISysDeptService sysDeptService;
    @Autowired
    private ScheduleSubtaskBuilder scheduleSubtaskBuilder;
    @Autowired
@@ -216,8 +221,14 @@
    private ServiceOutPathMapper serviceOutPathMapper;
    private static final String KEY_SEP = "\u0001";
    /** 二次随访生成锁前缀,防止同一原任务并发生成多条二次随访 */
    private static final String AGAIN_LOCK_KEY_PREFIX = "smartor:again:lock:";
    private static final long AGAIN_LOCK_SECONDS = 30L;
    @Autowired
    private PatMedOperationItemMapper patMedOperationItemMapper;
    @Autowired
    private SysDeptMapper sysDeptMapper;
    /**
     * 查询单一任务(随访)
@@ -558,11 +569,22 @@
    @Override
    public List<ServiceSubtask> patItem2(ServiceSubtaskEntity serviceSubtaskEntity) {
        if (serviceSubtaskEntity.getSendstateView() != null) {
            //            1:待随访(1 被领取、2 待发送、3 已发送、5 发送失败、7、超时);2:已完成( 6 已完成)、3:无需随访(4 不执行)
            if (serviceSubtaskEntity.getSendstateView() == 1)
                serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 5L, 7L)));
            if (serviceSubtaskEntity.getSendstateView() == 2)
                serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(6L)));
            if (serviceSubtaskEntity.getSendstateView() == 3)
                serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(4L)));
        }
        List<ServiceSubtask> serviceSubtaskList = new ArrayList<>();
        for (Long sendstate : serviceSubtaskEntity.getSendstates()) {
            serviceSubtaskEntity.setSendstate(sendstate);
            List<ServiceSubtask> serviceSubtasks = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity);
            serviceSubtaskList.addAll(serviceSubtasks);
        if(CollectionUtils.isNotEmpty(serviceSubtaskEntity.getSendstates())){
            for (Long sendstate : serviceSubtaskEntity.getSendstates()) {
                serviceSubtaskEntity.setSendstate(sendstate);
                List<ServiceSubtask> serviceSubtasks = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity);
                serviceSubtaskList.addAll(serviceSubtasks);
            }
        }
        return serviceSubtaskList;
    }
@@ -633,6 +655,7 @@
            result.putIfAbsent("xsf", 0);
            result.putIfAbsent("dsf", 0);
            result.putIfAbsent("ywc", 0);
            result.putIfAbsent("cs", 0);
            result.putIfAbsent("yc", 0);
            result.putIfAbsent("jg", 0);
        }
@@ -669,6 +692,7 @@
            result.putIfAbsent("dsf", 0);
            result.putIfAbsent("dfs", 0);
            result.putIfAbsent("yfs", 0);
            result.putIfAbsent("cs", 0);
            result.putIfAbsent("ywc", 0);
            result.putIfAbsent("yc", 0);
            result.putIfAbsent("jg", 0);
@@ -769,6 +793,7 @@
     * @return 结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String addSubTaskAgain(ServiceSubtask serviceSubtask) {
        ServiceSubtask serviceSubtask1 = selectServiceSubtaskById(serviceSubtask.getId());
        PatMedInhosp patMedInhosp = new PatMedInhosp();
@@ -778,7 +803,7 @@
        PatArchive patArchive = patArchiveMapper.selectPatArchiveByPatid(serviceSubtask1.getPatid());
        //景宁 新增再次随访不能触发患者再入院
        boolean inhospAgain = true;
        if (serviceSubtask1.getOrgid().equals("47255004333112711A1001")) {
        if ("47255004333112711A1001".equals(serviceSubtask1.getOrgid())) {
            inhospAgain = false;
        }
        if (patMedInhospList.size() > 0 && inhospAgain) {
@@ -792,13 +817,17 @@
            serviceSubtask1.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + ";无需再次随访,任务被标记不在执行" : "无需再次随访,任务被标记不在执行");
            serviceSubtaskMapper.updateServiceSubtask(serviceSubtask1);
            return "无需再次随访,任务被标记不在执行";
        } else if (StringUtils.isNotEmpty(patArchive.getNotrequiredFlag()) && patArchive.getNotrequiredFlag().equals("1")) {
        } else if (patArchive != null && StringUtils.isNotEmpty(patArchive.getNotrequiredFlag()) && patArchive.getNotrequiredFlag().equals("1")) {
            serviceSubtask1.setIsVisitAgain(2);
            serviceSubtaskMapper.updateServiceSubtask(serviceSubtask1);
            serviceSubtask1.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + ";无需再次随访,患者被标记不需要被服务" : "无需再次随访,患者被标记不需要被服务");
            return "无需再次随访,患者被标记不需要被服务";
        } else if (ObjectUtils.isNotEmpty(serviceSubtask1.getIsVisitAgain()) && serviceSubtask1.getIsVisitAgain() == 2) {
            return "无需再次随访,该任务已经再次随访过了";
        }
        //丽水中医院根据下次任务ID生成再次随访
        if ("47231022633110211A2101".equals(serviceSubtask1.getOrgid())) {
            return addSubTaskAgainNextTaskId(serviceSubtask1);
        }
        //新增再次随访任务
        ServiceSubtask serviceSubtask2 = DtoConversionUtils.sourceToTarget(serviceSubtask, ServiceSubtask.class);
@@ -819,6 +848,285 @@
            return "再次随访任务创建成功";
        }
        return "再次随访任务创建失败,再请次重试";
    }
    public String addSubTaskAgainNextTaskId(ServiceSubtask serviceSubtask) {
        if (ObjectUtils.isEmpty(serviceSubtask.getTaskid())) {
            log.error("再次随访任务创建失败,缺少TaskId, 再请次重试");
            return "再次随访任务创建失败,缺少TaskId, 再请次重试";
        }
        // 同一原任务串行生成,防止并发重复插入
        String lockToken = tryLockAgainTask(serviceSubtask.getId());
        if (lockToken == null) {
            return "再次随访任务正在创建中,请勿重复提交";
        }
        try {
            ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
            if (ObjectUtils.isEmpty(serviceTask)) {
                log.error("再次随访任务创建失败,找不到对应任务 taskid = {}, 再请次重试", serviceSubtask.getTaskid());
                return "再次随访任务创建失败,找不到对应任务 taskid = " + serviceSubtask.getTaskid() + ", 再请次重试";
            }
            // 幂等校验:同一原任务只允许生成一条二次随访
            ServiceSubtaskEntity againQuery = new ServiceSubtaskEntity();
            againQuery.setUpid(serviceSubtask.getId().intValue());
            if (CollectionUtils.isNotEmpty(serviceSubtaskMapper.selectServiceSubtaskList(againQuery))) {
                return "无需再次随访,该任务已经再次随访过了";
            }
            // 查询出院记录
            PatMedInhosp patMedInhospVo = new PatMedInhosp();
            patMedInhospVo.setOrgid(serviceSubtask.getOrgid());
            patMedInhospVo.setInhospid(serviceSubtask.getInhospid());
            patMedInhospVo.setCry(1);
            List<PatMedInhosp> patMedInhospList = patMedInhospService.selectPatMedInhospList(patMedInhospVo);
            PatMedInhosp patMedInhosp = new PatMedInhosp();
            if (CollectionUtils.isNotEmpty(patMedInhospList)) {
                patMedInhosp = patMedInhospList.get(0);
            } else {
                log.error("再次随访任务创建失败,任务缺少 对应的出院记录, taskid = {}, 再请次重试", serviceSubtask.getTaskid());
                return "再次随访任务创建失败,任务缺少 对应的出院记录, taskid = " + serviceSubtask.getTaskid() + ", 再请次重试";
            }
            if (ObjectUtils.isEmpty(serviceTask.getNexttaskid())) {
                log.error("再次随访任务创建失败,任务缺少 nexttaskid, taskid = {}, 再请次重试", serviceSubtask.getTaskid());
                return "再次随访任务创建失败,任务缺少 nexttaskid, taskid = " + serviceSubtask.getTaskid() + ", 再请次重试";
            }
            ServiceTask nextServiceTask = serviceTaskService.selectServiceTaskByTaskid(serviceTask.getNexttaskid());
            if (ObjectUtils.isEmpty(nextServiceTask)) {
                log.error("再次随访任务创建失败,找不到对应任务 nexttaskid = {}, 再请次重试", serviceTask.getNexttaskid());
                return "再次随访任务创建失败,找不到对应任务 nexttaskid = " + serviceTask.getNexttaskid() + ", 再请次重试";
            }
            PatArchive patArchive = patArchiveService.selectPatArchiveByPatid(serviceSubtask.getPatid());
            if (ObjectUtils.isEmpty(patArchive)) {
                log.error("再次随访任务创建失败,未找到患者档案 patid = {}, subTaskId = {}", serviceSubtask.getPatid(), serviceSubtask.getId());
                return "再次随访任务创建失败,未找到患者档案, subTaskId = " + serviceSubtask.getId() + ", 再请次重试";
            }
            String config = configService.selectConfigByKey("visit.early.day");
            Integer type = 1;
            // 随访中心科室
            String centerDept = configService.selectConfigByKey("sys.smartor.center.dept", serviceSubtask.getOrgid());
            if (ObjectUtils.isEmpty(centerDept)) {
                log.error("再次随访任务创建失败,未配置随访中心科室参数 sys.smartor.center.dept ,subTaskId = {}, 再请次重试", serviceSubtask.getId());
                return "再次随访任务创建失败,未配置随访中心科室参数 sys.smartor.center.dept ,subTaskId = " + serviceSubtask.getId() + ", 再请次重试";
            }
            SysDept sysDeptVo = new SysDept();
            sysDeptVo.setDeptType("1");
            sysDeptVo.setOrgid(serviceSubtask.getOrgid());
            sysDeptVo.setDeptCode(centerDept);
            List<SysDept> sysDepts = sysDeptService.selectDeptList(sysDeptVo);
            SysDept sysDept = new SysDept();
            if (CollectionUtils.isNotEmpty(sysDepts)) {
                sysDept = sysDepts.get(0);
            } else {
                log.error("再次随访任务创建失败,未找到参数 sys.smartor.center.dept 配置的随访中心科室 deptCode = {}, 再请次重试", centerDept);
                return "再次随访任务创建失败,未找到参数 sys.smartor.center.dept 配置的随访中心科室 deptCode = " + centerDept + ", 再请次重试";
            }
            // 按下一任务生成二次随访子任务(表单、发送方式取自下一任务)
            ServiceSubtask serviceSubtaskAgain = patMedInhospService.boxedServiceSubtask(nextServiceTask,
                    patMedInhosp, patArchive, config, type);
            if (ObjectUtils.isEmpty(serviceSubtaskAgain) || ObjectUtils.isEmpty(serviceSubtaskAgain.getTaskid())) {
                log.error("再次随访任务创建失败,下一任务生成子任务为空, subTaskId = {}, nextTaskId = {}", serviceSubtask.getId(), serviceTask.getNexttaskid());
                return "再次随访任务创建失败,患者出院记录不完整或下一任务配置异常, 再请次重试";
            }
            Date now = new Date();
            serviceSubtaskAgain.setId(null);
            serviceSubtaskAgain.setUpid(serviceSubtask.getId().intValue());
            serviceSubtaskAgain.setVisitCount(ObjectUtils.isNotEmpty(serviceSubtask.getVisitCount())
                    ? serviceSubtask.getVisitCount() + 1 : 1);
            serviceSubtaskAgain.setSendstate(2L);
            serviceSubtaskAgain.setIsVisitAgain(1);
            serviceSubtaskAgain.setCreateTime(now);
            serviceSubtaskAgain.setUpdateTime(now);
//            // 二次随访时间:上次随访完成时间(未完成则当前时间)+ 下一任务发送天数;过去时间兜底为当前时间
//            Date baseTime = ObjectUtils.isNotEmpty(serviceSubtask.getFinishtime()) ? serviceSubtask.getFinishtime() : now;
//            int intervalDays = ObjectUtils.isNotEmpty(nextServiceTask.getSendDay())
//                    ? nextServiceTask.getSendDay().intValue() : 0;
//            Date nextVisitTime = DateUtils.addDays(baseTime, intervalDays);
//            if (nextVisitTime.before(now)) {
//                nextVisitTime = now;
//            }
//            serviceSubtaskAgain.setVisitTime(nextVisitTime);
//            serviceSubtaskAgain.setLongSendTime(nextVisitTime);
            serviceSubtaskAgain.setVisitType(serviceSubtask.getVisitType());
            serviceSubtaskAgain.setVisitDeptCode(sysDept.getDeptCode());
            serviceSubtaskAgain.setVisitDeptName(sysDept.getDeptName());
            int i = serviceSubtaskMapper.insertServiceSubtask(serviceSubtaskAgain);
            if (i != 1) {
                return "再次随访任务创建失败,再请次重试";
            }
            // 初始化发送方式明细,补偿(compensateTask)和超时判断依赖该表
            patMedInhospService.addServiceSubtaskPreachform(serviceSubtaskAgain, null);
            // 条件更新原任务:0 行说明已被其他请求生成,抛异常回滚本次插入的重复数据
            int updated = serviceSubtaskMapper.updateIsVisitAgainById(serviceSubtask.getId(), 2);
            if (updated != 1) {
                throw new BaseException("无需再次随访,该任务已经再次随访过了");
            }
            return "再次随访任务创建成功";
        } finally {
            unlockAgainTask(serviceSubtask.getId(), lockToken);
        }
    }
    /**
     * 首次随访任务超时后,按下一任务自动生成再次随访任务(随访中心)。
     * <p>
     * 定时任务入口 {@code RyTask#dealSubtaskDeadline} 使用。与手动入口一致的部分:
     * 表单/发送方式取下一任务、链路 upid + visit_count、发送方式明细初始化;
     * 差异:随访中心科室未配置时回退原任务科室,原任务置为发送失败(sendstate=5)。
     * <p>
     * 幂等:同一原任务只允许生成一条再次随访(Redis 锁 + upid 校验 + 原任务条件更新),
     * 多节点/多线程同时扫描时,后到的事务会在条件更新处拿到 0 行并回滚本次插入。
     *
     * @param serviceSubtask 已超时的首次随访子任务
     * @return 结果描述
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String addSubTaskAgainByDeadline(ServiceSubtask serviceSubtask) {
        if (ObjectUtils.isEmpty(serviceSubtask) || ObjectUtils.isEmpty(serviceSubtask.getId())) {
            return "再次随访任务创建失败,原任务不存在";
        }
        String lockToken = tryLockAgainTask(serviceSubtask.getId());
        if (lockToken == null) {
            return "再次随访任务正在创建中,请勿重复提交";
        }
        try {
            // 幂等校验:同一原任务只允许生成一条再次随访
            ServiceSubtaskEntity againQuery = new ServiceSubtaskEntity();
            againQuery.setUpid(serviceSubtask.getId().intValue());
            if (CollectionUtils.isNotEmpty(serviceSubtaskMapper.selectServiceSubtaskList(againQuery))) {
                return "无需再次随访,该任务已经再次随访过了";
            }
            // 原任务已被标记为不再随访(手动入口写过 is_visit_again=2)
            if (ObjectUtils.isNotEmpty(serviceSubtask.getIsVisitAgain()) && serviceSubtask.getIsVisitAgain() == 2) {
                return "无需再次随访,该任务已经再次随访过了";
            }
            ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
            if (ObjectUtils.isEmpty(serviceTask)) {
                return "再次随访任务创建失败,找不到对应任务 taskid = " + serviceSubtask.getTaskid();
            }
            if (ObjectUtils.isEmpty(serviceTask.getNexttaskid())) {
                return "再次随访任务创建失败,任务缺少 nexttaskid, taskid = " + serviceSubtask.getTaskid();
            }
            ServiceTask nextServiceTask = serviceTaskService.selectServiceTaskByTaskid(serviceTask.getNexttaskid());
            if (ObjectUtils.isEmpty(nextServiceTask)) {
                return "再次随访任务创建失败,找不到对应任务 nexttaskid = " + serviceTask.getNexttaskid();
            }
            PatMedInhosp patMedInhosp = selectOutHospRecord(serviceSubtask);
            if (ObjectUtils.isEmpty(patMedInhosp)) {
                return "再次随访任务创建失败,任务缺少对应的出院记录, taskid = " + serviceSubtask.getTaskid();
            }
            PatArchive patArchive = patArchiveService.selectPatArchiveByPatid(serviceSubtask.getPatid());
            if (ObjectUtils.isEmpty(patArchive)) {
                return "再次随访任务创建失败,未找到患者档案, subTaskId = " + serviceSubtask.getId();
            }
            // 随访中心科室:未配置(或科室不存在)时回退原任务科室,保持超时入口原有行为
            SysDept visitDept = selectCenterDept(serviceSubtask.getOrgid());
            String config = configService.selectConfigByKey("visit.early.day");
            ServiceSubtask serviceSubtaskAgain = patMedInhospService.boxedServiceSubtask(nextServiceTask,
                    patMedInhosp, patArchive, config, 1);
            if (ObjectUtils.isEmpty(serviceSubtaskAgain) || ObjectUtils.isEmpty(serviceSubtaskAgain.getTaskid())) {
                return "再次随访任务创建失败,患者出院记录不完整或下一任务配置异常, subTaskId = " + serviceSubtask.getId();
            }
            Date now = new Date();
            serviceSubtaskAgain.setId(null);
            serviceSubtaskAgain.setUpid(serviceSubtask.getId().intValue());
            serviceSubtaskAgain.setVisitCount(ObjectUtils.isNotEmpty(serviceSubtask.getVisitCount())
                    ? serviceSubtask.getVisitCount() + 1 : 2);
            serviceSubtaskAgain.setSendstate(2L);
            serviceSubtaskAgain.setIsVisitAgain(1);
            serviceSubtaskAgain.setCreateTime(now);
            serviceSubtaskAgain.setUpdateTime(now);
            serviceSubtaskAgain.setVisitType(serviceSubtask.getVisitType());
            if (ObjectUtils.isNotEmpty(visitDept)) {
                serviceSubtaskAgain.setVisitDeptCode(visitDept.getDeptCode());
                serviceSubtaskAgain.setVisitDeptName(visitDept.getDeptName());
            } else {
                serviceSubtaskAgain.setVisitDeptCode(serviceSubtask.getVisitDeptCode());
                serviceSubtaskAgain.setVisitDeptName(serviceSubtask.getVisitDeptName());
            }
            if (serviceSubtaskMapper.insertServiceSubtask(serviceSubtaskAgain) != 1) {
                return "再次随访任务创建失败,再请次重试";
            }
            // 初始化发送方式明细,补偿(compensateTask)和超时判断依赖该表
            patMedInhospService.addServiceSubtaskPreachform(serviceSubtaskAgain, null);
            // 条件更新原任务:0 行说明已被其他请求生成,抛异常回滚本次插入的重复数据
            if (serviceSubtaskMapper.updateIsVisitAgainById(serviceSubtask.getId(), 2) != 1) {
                throw new BaseException("无需再次随访,该任务已经再次随访过了");
            }
            // 原任务超时,置为发送失败
            ServiceSubtask originUpdate = new ServiceSubtask();
            originUpdate.setId(serviceSubtask.getId());
            originUpdate.setSendstate(5L);
            originUpdate.setUpdateTime(now);
            serviceSubtaskMapper.updateServiceSubtask(originUpdate);
            return "再次随访任务创建成功";
        } finally {
            unlockAgainTask(serviceSubtask.getId(), lockToken);
        }
    }
    /**
     * 按 orgid + inhospid 取出院记录(超时再次随访用)。
     */
    private PatMedInhosp selectOutHospRecord(ServiceSubtask serviceSubtask) {
        PatMedInhosp patMedInhospVo = new PatMedInhosp();
        patMedInhospVo.setOrgid(serviceSubtask.getOrgid());
        patMedInhospVo.setInhospid(serviceSubtask.getInhospid());
        patMedInhospVo.setCry(1);
        List<PatMedInhosp> patMedInhospList = patMedInhospService.selectPatMedInhospList(patMedInhospVo);
        if (CollectionUtils.isEmpty(patMedInhospList)) {
            return null;
        }
        return patMedInhospList.get(0);
    }
    /**
     * 取随访中心科室,未配置或科室不存在时返回 null(由调用方回退原任务科室)。
     */
    private SysDept selectCenterDept(String orgid) {
        String centerDept = configService.selectConfigByKey("sys.smartor.center.dept", orgid);
        if (StringUtils.isEmpty(centerDept)) {
            return null;
        }
        SysDept sysDeptVo = new SysDept();
        sysDeptVo.setDeptType("1");
        sysDeptVo.setOrgid(orgid);
        sysDeptVo.setDeptCode(centerDept);
        List<SysDept> sysDepts = sysDeptService.selectDeptList(sysDeptVo);
        return CollectionUtils.isEmpty(sysDepts) ? null : sysDepts.get(0);
    }
    /**
     * 获取二次随访生成锁。Redis 异常时降级为数据库幂等校验,不阻断业务。
     */
    private String tryLockAgainTask(Long subId) {
        String token = UUID.randomUUID().toString();
        try {
            Boolean locked = redisTemplate.opsForValue().setIfAbsent(
                    AGAIN_LOCK_KEY_PREFIX + subId, token, AGAIN_LOCK_SECONDS, TimeUnit.SECONDS);
            return Boolean.TRUE.equals(locked) ? token : null;
        } catch (Exception e) {
            log.warn("再次随访获取Redis锁失败,降级使用数据库幂等校验, subId={}", subId, e);
            return token;
        }
    }
    /**
     * 释放二次随访生成锁,仅释放自己持有的锁。
     */
    private void unlockAgainTask(Long subId, String token) {
        if (token == null) {
            return;
        }
        try {
            String lockKey = AGAIN_LOCK_KEY_PREFIX + subId;
            Object current = redisTemplate.opsForValue().get(lockKey);
            if (token.equals(current)) {
                redisTemplate.delete(lockKey);
            }
        } catch (Exception e) {
            log.warn("再次随访释放Redis锁失败, subId={}", subId, e);
        }
    }
    /**
@@ -2940,7 +3248,7 @@
     * @return
     */
    @Override
    @Cacheable(value = "sfStatistics", key = "T(org.springframework.util.DigestUtils).md5DigestAsHex(#serviceSubtaskCountReq.toString().getBytes())", unless = "#result == null or #result.isEmpty()")
//    @Cacheable(value = "sfStatistics", key = "T(org.springframework.util.DigestUtils).md5DigestAsHex(#serviceSubtaskCountReq.toString().getBytes())", unless = "#result == null or #result.isEmpty()")
    public List<ServiceSubtaskStatistic> getSfStatistics(ServiceSubtaskCountReq serviceSubtaskCountReq) {
        log.info("getSfStatistics的入参为:{}", serviceSubtaskCountReq);
        String drcode = "drcode";
@@ -3062,6 +3370,8 @@
        ssRate.setOrgid(serviceSubtaskCountReq.getOrgid());
        ssRate.setStarttime(serviceSubtaskCountReq.getStartTime());
        ssRate.setEndtime(serviceSubtaskCountReq.getEndTime());
        ssRate.setStartOutHospTime(serviceSubtaskCountReq.getHospitalStartTime());
        ssRate.setEndOutHospTime(serviceSubtaskCountReq.getHospitalEndTime());
        ssRate.setVisitCount(serviceSubtaskCountReq.getVisitCount());
        ssRate.setServiceTypeList(serviceSubtaskCountReq.getServiceType());
        if (serviceSubtaskCountReq.getEndTime() != null && new Date().before(serviceSubtaskCountReq.getEndTime())) {
@@ -3153,6 +3463,8 @@
            if (serviceSubtaskCountReq.getEndTime() != null && new Date().before(serviceSubtaskCountReq.getEndTime())) {
                ss.setEndtime(new Date());
            }
            ss.setStartOutHospTime(serviceSubtaskCountReq.getHospitalStartTime());
            ss.setEndOutHospTime(serviceSubtaskCountReq.getHospitalEndTime());
            Double jsRate = serviceSubtaskMapper.selectTimelyRate(ss);
            statistic.setRate(jsRate);
            if (ObjectUtils.isNotEmpty(statistic)) {
@@ -3305,6 +3617,16 @@
                //应随访人次
                if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) {
                    serviceSubtaskStatistic.setFollowUpNeeded(serviceSubtaskStatistic.getFollowUpNeeded() + 1L);
                }
                //及时随访人次:已完成且未超时
                if (isTimelyFollowUp(serviceSubtask)) {
                    serviceSubtaskStatistic.setTimelyFollowUp(serviceSubtaskStatistic.getTimelyFollowUp() + 1L);
                }
                //人工已拨通总量
                if (ObjectUtils.isNotEmpty(serviceSubtask.getIsManuallyCalled())
                        && serviceSubtask.getIsManuallyCalled().equals(1L)) {
                    serviceSubtaskStatistic.setManuallyCalled(serviceSubtaskStatistic.getManuallyCalled() + 1L);
                }
                //首次出院随访
@@ -3568,6 +3890,19 @@
    }
    /**
     * 是否属于「及时随访」:随访已完成且完成时状态为正常完成(未超时)。
     * <p>
     * finish_state:1-正常完成 2-延期完成 3-超时完成,只有 1 计入及时随访。
     *
     * @param serviceSubtask 随访子任务
     * @return true-计入及时随访人次
     */
    private boolean isTimelyFollowUp(ServiceSubtask serviceSubtask) {
        return serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate().equals(6L)
                && serviceSubtask.getFinishState() != null && serviceSubtask.getFinishState().equals(1L);
    }
    /**
     * 首次随访统计
     *
     * @param serviceSubtaskList
@@ -3598,6 +3933,10 @@
            //应随访人次
            if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) {
                serviceSubtaskStatistic.setFollowUpNeeded(serviceSubtaskStatistic.getFollowUpNeeded() + 1L);
            }
            //及时随访人次:已完成且未超时
            if (isTimelyFollowUp(serviceSubtask)) {
                serviceSubtaskStatistic.setTimelyFollowUp(serviceSubtaskStatistic.getTimelyFollowUp() + 1L);
            }
            //首次出院随访
@@ -3767,6 +4106,10 @@
            //应随访人次
            if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) {
                serviceSubtaskStatistic.setFollowUpNeeded(serviceSubtaskStatistic.getFollowUpNeeded() + 1L);
            }
            //及时随访人次:已完成且未超时
            if (isTimelyFollowUp(serviceSubtask)) {
                serviceSubtaskStatistic.setTimelyFollowUp(serviceSubtaskStatistic.getTimelyFollowUp() + 1L);
            }
            //再次出院随访
@@ -4298,6 +4641,7 @@
     * pendingFollowUpInfo - visitCount = 1 && sendstate = 2
     * followUpSuccessInfo - visitCount = 1 && sendstate = 6
     * followUpFailInfo - visitCount = 1 && (sendstate = 5 || sendstate = 7)
     * timelyFollowUpInfo - sendstate = 6 && finish_state = 1(沿用请求的 visitCount 口径)
     * manualInfo - visitCount = 1 && currentPreachForm = "1"
     * voiceInfo - visitCount = 1 && currentPreachForm = "3"
     * smsInfo - visitCount = 1 && currentPreachForm = "4"
@@ -4326,6 +4670,7 @@
     * taskSituation4Info - taskSituation = 4
     * taskSituation5Info - taskSituation = 5
     * taskSituation6Info - taskSituation = 7
     * manuallyCalledInfo - isManuallyCalled = 1L
     * filterCountList - taskSituation = 6 && sendstate = 4
     *
     */
@@ -4355,14 +4700,22 @@
    }
    private void applyHyperLinkInfoType(ServiceSubtaskCountReq serviceSubtaskCountReq, String hyperLinkInfoType) {
        //及时随访明细需要沿用请求的首次/再次口径
        Integer originVisitCount = serviceSubtaskCountReq.getVisitCount();
        serviceSubtaskCountReq.setVisitCount(null);
        serviceSubtaskCountReq.setSendstates(null);
        serviceSubtaskCountReq.setCurrentPreachform(null);
        serviceSubtaskCountReq.setExcep(null);
        serviceSubtaskCountReq.setTaskSituation(null);
        serviceSubtaskCountReq.setFinishState(null);
        switch (hyperLinkInfoType) {
            case "dischargeCountInfo":
                break;
            case "timelyFollowUpInfo":
                serviceSubtaskCountReq.setVisitCount(originVisitCount);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L)));
                serviceSubtaskCountReq.setFinishState(1L);
                break;
            case "followUpNeededAllInfo":
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 5L, 6L, 7L)));
@@ -4398,7 +4751,7 @@
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setCurrentPreachform("4");
                break;
            case "wechatInfo":
            case "weChatInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setCurrentPreachform("5");
                break;
@@ -4417,7 +4770,7 @@
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L)));
                serviceSubtaskCountReq.setCurrentPreachform("4");
                break;
            case "wechatSuccessInfo":
            case "weChatSuccessInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L)));
                serviceSubtaskCountReq.setCurrentPreachform("5");
@@ -4454,7 +4807,7 @@
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setCurrentPreachform("4");
                break;
            case "wechatAgainInfo":
            case "weChatAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setCurrentPreachform("5");
                break;
@@ -4473,7 +4826,7 @@
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L)));
                serviceSubtaskCountReq.setCurrentPreachform("4");
                break;
            case "wechatAgainSuccessInfo":
            case "weChatAgainSuccessInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L)));
                serviceSubtaskCountReq.setCurrentPreachform("5");
@@ -4503,6 +4856,9 @@
            case "filterCountList":
                serviceSubtaskCountReq.setTaskSituation(6);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(4L)));
                break;
            case "manuallyCalledInfo":
                serviceSubtaskCountReq.setIsManuallyCalled(1L);
                break;
            default:
                break;
@@ -4829,6 +5185,7 @@
        total.setPendingFollowUp(0L);
        total.setFollowUpSuccess(0L);
        total.setFollowUpFail(0L);
        total.setTimelyFollowUp(0L);
        total.setManual(0L);
        total.setSms(0L);
        total.setWeChat(0L);
@@ -4860,6 +5217,7 @@
            total.setPendingFollowUp(total.getPendingFollowUp() + (stat.getPendingFollowUp() != null ? stat.getPendingFollowUp() : 0L));
            total.setFollowUpSuccess(total.getFollowUpSuccess() + (stat.getFollowUpSuccess() != null ? stat.getFollowUpSuccess() : 0L));
            total.setFollowUpFail(total.getFollowUpFail() + (stat.getFollowUpFail() != null ? stat.getFollowUpFail() : 0L));
            total.setTimelyFollowUp(total.getTimelyFollowUp() + (stat.getTimelyFollowUp() != null ? stat.getTimelyFollowUp() : 0L));
            total.setManual(total.getManual() + (stat.getManual() != null ? stat.getManual() : 0L));
            total.setSms(total.getSms() + (stat.getSms() != null ? stat.getSms() : 0L));
            total.setWeChat(total.getWeChat() + (stat.getWeChat() != null ? stat.getWeChat() : 0L));
@@ -5141,12 +5499,6 @@
        String subId = rsaPublicKeyExample.encryptedData(serviceSubtask.getId().toString(), pub_key);
        String taskId = rsaPublicKeyExample.encryptedData(serviceSubtask.getTaskid().toString(), pub_key);
        //丽水中医院发送的是微信公众号
        if (StringUtils.isNotEmpty(serviceSubtask.getOrgid()) && serviceSubtask.getOrgid().equals("47231022633110211A2101")) {
            resultMap = wechatSubTask(subid);
            return resultMap;
        }
        //发送数据的封装
        ServiceOutPath serviceOutPath = new ServiceOutPath();
        serviceOutPath.setParam1(taskId);
@@ -5188,6 +5540,25 @@
                serviceSubtask.setCurrentPreachform("4");
            }
        }
        return resultMap;
    }
    @Override
    public Map<String, Object> weChatSubTask(Long subid) {
        log.info("smsSubTask的入参为:{}", subid);
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("code", "500");
        resultMap.put("msg", "发送失败");
        //获取患者的基本信息
        ServiceSubtask serviceSubtask = selectServiceSubtaskById(subid);
        RSAPublicKeyExample rsaPublicKeyExample = new RSAPublicKeyExample();
        String patid = rsaPublicKeyExample.encryptedData(serviceSubtask.getPatid().toString(), pub_key);
        String subId = rsaPublicKeyExample.encryptedData(serviceSubtask.getId().toString(), pub_key);
        String taskId = rsaPublicKeyExample.encryptedData(serviceSubtask.getTaskid().toString(), pub_key);
        //丽水中医院发送的是微信公众号
        resultMap = wechatSubTask(subid);
        return resultMap;
    }
@@ -6137,6 +6508,8 @@
        String voiceText = phoneCallReqYQVO.getAsrtext();
        String questionText = StringUtils.isNotEmpty(ivrTaskTemplateScriptVO.getIvrtext()) ? ivrTaskTemplateScriptVO.getIvrtext() : ivrTaskTemplateScriptVO.getScriptContent();
        Integer matched = -1;
        log.info("AI匹配:url = {},questionText = {},voiceText = {},options = {},uuid ={}",
                url,questionText,voiceText,options,uuid);
        matched = matchOptionIndex(url, questionText, voiceText, options, uuid);
        //说明匹配正确了
        if (matched != null && matched >= 0) {