陈昶聿
7 天以前 599beddc80e7e50b2099bacdbf7df2d89df00ef2
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -14,6 +14,7 @@
import com.ruoyi.common.enums.ServiceFromEnum;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.common.utils.sms.SmsUtils;
import com.ruoyi.message.SendResult;
import com.ruoyi.message.sms.SmsSendRequest;
import com.ruoyi.message.sms.SmsSenderFactory;
@@ -32,6 +33,7 @@
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.message.sms.provider.sltd.ws.MessageSend;
import com.smartor.common.QwenLLMUtil;
import com.smartor.common.SpecificArchiveConstants;
import com.smartor.domain.*;
import com.ruoyi.task.domain.VO.ServiceSubtaskDetailTraceVO;
import com.smartor.mapper.*;
@@ -39,6 +41,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -66,8 +69,12 @@
import com.ruoyi.task.domain.ServiceSubtask;
import com.ruoyi.task.domain.ServiceSubtaskRecord;
import com.ruoyi.task.domain.ServiceTask;
import com.ruoyi.task.domain.ServiceTaskSchedule;
import com.ruoyi.task.domain.ServiceTaskScheduleDetail;
import com.ruoyi.task.mapper.ServiceSubtaskMapper;
import com.ruoyi.task.mapper.ServiceTaskMapper;
import com.ruoyi.task.mapper.ServiceTaskScheduleMapper;
import com.ruoyi.task.mapper.ServiceTaskScheduleDetailMapper;
import com.ruoyi.task.domain.ServiceSubtaskDetailTrace;
import com.ruoyi.task.domain.VO.ServiceSubtaskDetailTraceVO;
import com.ruoyi.task.service.IServiceSubtaskDetailTraceService;
@@ -164,6 +171,12 @@
    @Autowired
    private ServiceSubtaskMapper serviceSubtaskMapper;
    @Autowired
    private ServiceTaskScheduleMapper serviceTaskScheduleMapper;
    @Autowired
    private ServiceTaskScheduleDetailMapper serviceTaskScheduleDetailMapper;
    @Autowired
    private RedisCache redisCache;
@@ -1149,6 +1162,13 @@
            serviceSubtaskList.addAll(compensateServiceSubtaskList);
        }
        for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
            //currentPreachform为空,说明该子任务还没有执行过任何一种发送方式(sendstate=1,属于首次补偿),
            //这里直接交给sfHandlle处理,它会按sort=1的第一种发送方式发送,与getSendPreachform中的处理保持一致
            if (StringUtils.isEmpty(serviceSubtask.getCurrentPreachform())) {
                log.warn("【compensateTask】subid:{},currentPreachform为空,按第一种发送方式补偿处理", serviceSubtask.getId());
                sfHandlle(serviceSubtask);
                continue;
            }
            //根据当前的执行方式,获取下一种执行方式
            ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform();
            serviceSubtaskPreachform.setTaskid(serviceSubtask.getTaskid());
@@ -1159,6 +1179,25 @@
            Optional<Long> currentSort = serviceSubtaskPreachforms.stream().filter(item -> serviceSubtask.getCurrentPreachform().equals(item.getPreachform())).map(ServiceSubtaskPreachform::getSort).findFirst();
            Optional<Long> id = serviceSubtaskPreachforms.stream().filter(item -> serviceSubtask.getCurrentPreachform().equals(item.getPreachform())).map(ServiceSubtaskPreachform::getId).findFirst();
            if (currentSort.isPresent()) {
                // 方案B:只处理当前子任务——公众号已领取(service_subtask.sendstate=1 + current_preachform=5)
                // 且未填写时,按默认选项完成,避免进入下一种发送方式或最后一步超时。
                // 先用内存字段快速过滤,其他医院/任务/发送方式不会进入下面的默认完成逻辑。
                if (IServiceSubtaskService.WECHAT_DEFAULT_COMPLETE_ORGID.equals(serviceSubtask.getOrgid())
                        && "2".equals(serviceSubtask.getType())
                        && "5".equals(serviceSubtask.getCurrentPreachform())
                        && Long.valueOf(1L).equals(serviceSubtask.getSendstate())
                        && !Long.valueOf(1L).equals(serviceSubtask.getSubmit())) {
                    try {
                        if (iServiceSubtaskService.completeWechatClaimedByDefault(serviceSubtask)) {
                            log.info("【compensateTask】subid:{},微信公众号已领取未填写,按默认选项完成", serviceSubtask.getId());
                            continue;
                        }
                    } catch (Exception e) {
                        // 单个子任务失败不影响其他任务,回滚后继续原补偿流程
                        log.error("【compensateTask】微信公众号默认完成失败,subid:{},继续原补偿流程", serviceSubtask.getId(), e);
                    }
                }
                //1先检查一下,是不是有执行状态是完成的(怕之前已经有完的了,没有将servuce_subtask的状态改成功,这里再检查一下)
                boolean finishState = serviceSubtaskPreachforms.stream().allMatch(item -> item.getSendstate().equals("9"));
                if (finishState) {
@@ -1217,6 +1256,24 @@
        LocalDate today = LocalDate.now();
        // 如果日期早于或等于今天,返回 true
        return !localDate.isAfter(today);
    }
    /**
     * 按机构查询配置值(同一 key 在不同机构下可配置不同值)
     *
     * @param configKey 配置key
     * @param orgid     机构ID
     * @return 配置值,未配置返回 null
     */
    private String getConfigValue(String configKey, String orgid) {
        SysConfig query = new SysConfig();
        query.setConfigKey(configKey);
        query.setOrgid(orgid);
        List<SysConfig> sysConfigs = configService.selectConfigList(query);
        if (CollectionUtils.isNotEmpty(sysConfigs)) {
            return sysConfigs.get(0).getConfigValue();
        }
        return null;
    }
    /**
@@ -1352,7 +1409,7 @@
                    serviceOutPath.setOrgid(serviceSubtask.getOrgid());
                    iServiceOutPathService.insertServiceOutPath(serviceOutPath);
                    String url = "";
                    String format = String.format("%03X", serviceOutPath.getId());
                    String format = ShortCode.encode(serviceOutPath.getId());
                    //先判断一下发的是不是宣教
                    log.info("serviceSubtask.getType的值为:{}", serviceSubtask.getType());
                    if (!"3".equals(serviceSubtask.getType())) {
@@ -1374,7 +1431,7 @@
                            sysConfig = sysConfigMapper.checkConfigKeyUnique("wj.sms.template");
                        }
                        log.info("sysConfig的值为:{}", sysConfig);
                        if (!Objects.isNull(sysConfig)) {
                        if (!Objects.isNull(sysConfig) && StringUtils.isNotEmpty(sysConfig.getConfigValue())) {
                            String smsContemt = sysConfig.getConfigValue();
//                            sendMagParam.setContent("您好,邀请您填写出院调查表,请点击" + sendMagParam.getUrl() + "填写。感谢您配合!");
                            sendMagParam.setContent(smsContemt.replace("${wjurl}", sendMagParam.getUrl()));
@@ -1394,7 +1451,7 @@
                            sendMagParam.setUrl(url);
                            SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique("xj.sms.template");
                            if (!Objects.isNull(sysConfig)) {
                            if (!Objects.isNull(sysConfig) && StringUtils.isNotEmpty(sysConfig.getConfigValue())) {
                                String smsContemt = sysConfig.getConfigValue();
//                              sendMagParam.setContent("您好,邀请您填写出院宣教调查表,请点击" + sendMagParam.getUrl() + "查看。感谢您配合!");
                                sendMagParam.setContent(smsContemt.replace("${xjurl}", sendMagParam.getUrl()));
@@ -1410,6 +1467,13 @@
                    }
                    //内容为空说明短信模板未配置或宣教/通知内容为空,直接按发送失败处理,避免后续发送时出现空指针
                    if (StringUtils.isEmpty(sendMagParam.getContent())) {
                        log.error("【sfHandlle】长期任务短信内容为空,无法发送,subid:{},taskid:{},type:{},机构ID:{}", serviceSubtask.getId(), serviceSubtask.getTaskid(), serviceSubtask.getType(), serviceSubtask.getOrgid());
                        setSuccessPreachForm(serviceSubtask, sendPreachform, "长期任务短信发送失败:短信内容为空(短信模板未配置或内容为空), 时间: " + now, "5");
                        return;
                    }
                    serviceOutPath.setRadix(format);
                    serviceOutPath.setUpdateTime(new Date());
                    serviceOutPath.setUrl(url);
@@ -1417,9 +1481,24 @@
                    //短信发送:各站点对接方式差异较大(报文/签名/token/返回判定都不同),
                    //已收口到 ruoyi-message 模块,这里只负责组装入参并路由
                    log.info("active的值为:{}", active);
                    SendResult smsSendResult = sendSmsByActive(serviceSubtask, sendMagParam);
                    log.info("smsSendResult的值为:{}", smsSendResult);
                    String isSuccess = smsSendResult.asLegacyFlag();
                    String isSuccess = "";
                    if ("ls".equals(active) && "47226079133110211G1001".equals(serviceSubtask.getOrgid())) {
                        //丽水妇幼走中国移动云MAS,不依赖 ls.msg.info 与 token;
                        //ruoyi-message 的 ls 通道尚未覆盖云MAS,故此处保留 SmsUtils 直连
                        SmsUtils.YunMasConfig yunMasConfig = SmsUtils.YunMasConfig.of(getConfigValue("ls.msg.yunmas", serviceSubtask.getOrgid()));
                        SmsUtils.SmsResult yunMasSmsResult = SmsUtils.sendYunMasSms(yunMasConfig, sendMagParam.getPhone(), sendMagParam.getContent());
                        log.info("【sfHandlle】云MAS短信发送结果:ok={},rspcod={},msgGroup={},message={}",
                                yunMasSmsResult.isOk(), yunMasSmsResult.getRspcod(), yunMasSmsResult.getMsgGroup(), yunMasSmsResult.getMessage());
                        isSuccess = yunMasSmsResult.isOk() ? "true" : "false";
                        if (!yunMasSmsResult.isOk()) {
                            log.error("【sfHandlle】云MAS短信发送失败,subid:{},手机号:{},原因:{}",
                                    serviceSubtask.getId(), sendMagParam.getPhone(), yunMasSmsResult.getMessage());
                        }
                    } else {
                        SendResult smsSendResult = sendSmsByActive(serviceSubtask, sendMagParam);
                        log.info("smsSendResult的值为:{}", smsSendResult);
                        isSuccess = smsSendResult.asLegacyFlag();
                    }
                    //任务发送记录
                    ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord();
                    serviceSubtaskRecord.setTaskid(serviceSubtask.getTaskid().toString());
@@ -1473,7 +1552,7 @@
                    serviceOutPath.setCreateTime(new Date());
                    serviceOutPath.setOrgid(serviceSubtask.getOrgid());
                    iServiceOutPathService.insertServiceOutPath(serviceOutPath);
                    String format = String.format("%03X", serviceOutPath.getId());
                    String format = ShortCode.encode(serviceOutPath.getId());
                    serviceOutPath.setRadix(format);
                    serviceOutPath.setUpdateTime(new Date());
                    serviceOutPath.setUrl(url);
@@ -1487,6 +1566,7 @@
                    //任务发送记录
                    ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord();
                    serviceSubtaskRecord.setTaskid(serviceSubtask.getTaskid().toString());
                    serviceSubtaskRecord.setSubtaskId(serviceSubtask.getId());
                    serviceSubtaskRecord.setUuid(UUID.randomUUID().toString());
                    serviceSubtaskRecord.setTasktype(serviceSubtask.getType());
                    serviceSubtaskRecord.setPreachform("4");
@@ -1730,6 +1810,25 @@
        }
    }
    /**
     * 采集丽水市妇幼保健院门诊接诊完成记录,并按配置生成妇科/儿科档案。
     * 定时任务默认采集昨天 00:00:00 ~ 今天 00:00:00。
     */
    public void collectMchArchive() {
        try {
            LocalDateTime todayZero = LocalDateTime.now().with(LocalTime.MIN);
            LocalDateTime yesterdayZero = todayZero.minusDays(1);
            Date startTime = Date.from(yesterdayZero.atZone(ZoneId.systemDefault()).toInstant());
            Date endTime = Date.from(todayZero.atZone(ZoneId.systemDefault()).toInstant());
            log.info("【collectMchArchive】开始采集丽水妇幼门诊接诊完成记录");
            ichService.collectMchOuthospAndGenerateArchive(
                    SpecificArchiveConstants.LISHUI_MCH_ORG_ID, startTime, endTime);
            log.info("【collectMchArchive】丽水妇幼门诊接诊完成记录采集结束");
        } catch (Exception e) {
            log.error("【collectMchArchive】丽水妇幼门诊接诊完成记录采集异常", e);
        }
    }
    public void collectHISYesterday() {
        try {
            // 获取昨天0点到今天0点的时间范围
@@ -1854,14 +1953,15 @@
     * @return
     */
    private String getObject(ServiceSubtask serviceSubtask, String scriptContent) {
        //内容为空时直接返回,避免下面的replace出现空指针
        if (StringUtils.isEmpty(scriptContent)) {
            return scriptContent;
        }
        Map<String, Map<String, String>> param = getParam(serviceSubtask.getTaskid());
        for (Map<String, String> map : param.values()) {
            for (String key : map.keySet()) {
                scriptContent = scriptContent.replace(key, StringUtils.isNotEmpty(map.get(key)) ? map.get(key) : "");
            }
        }
        if (StringUtils.isEmpty(scriptContent)) {
            return scriptContent;
        }
        scriptContent = scriptContent.replace("${name}", StringUtils.isNotEmpty(serviceSubtask.getSendname()) ? serviceSubtask.getSendname() : "");
        scriptContent = scriptContent.replace("${dzz}", StringUtils.isNotEmpty(serviceSubtask.getAddr()) ? serviceSubtask.getAddr() : "");
@@ -1962,31 +2062,89 @@
        if (Objects.isNull(serviceSubtask) || CollectionUtils.isEmpty(serviceSubtaskPreachforms)) {
            return false;
        }
        // 基准时刻:出院时间 + 发送日(sendDay 缺省按 1),并兜底避免 NPE/脏数据
        ServiceTask serviceTask = serviceTaskMapper.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
        int sendDay = serviceTask != null && serviceTask.getSendDay() != null ? serviceTask.getSendDay().intValue() : 1;
        Date deadline = DateUtils.addDays(serviceSubtask.getEndtime(), sendDay);
        int sumCompensateTime = CollectionUtils.isEmpty(serviceSubtaskPreachforms) ? 0 : serviceSubtaskPreachforms.stream().mapToInt(x -> x.getCompensateTime() == null ? 0 : Integer.parseInt(x.getCompensateTime())).sum();
        Date deadline;
        if (serviceTask != null && "5".equals(serviceTask.getAppltype())) {
            // 任务组:基准 = schedule_benchmark 解析出的时间 + day_offset,不叠加 send_day
            deadline = resolveTaskGroupDeadlineBase(serviceSubtask);
            if (deadline == null) {
                log.warn("【isSubtaskDeadline】subid:{} 任务组基准时间为空,无法计算超时,按未超时处理", serviceSubtask.getId());
                return false;
            }
        } else {
            // 非任务组:基准 = 出院时间 + 发送日(sendDay 缺省按 1)
            if (serviceSubtask.getEndtime() == null) {
                log.warn("【isSubtaskDeadline】subid:{} endtime为空,无法计算超时,按未超时处理", serviceSubtask.getId());
                return false;
            }
            int sendDay = serviceTask != null && serviceTask.getSendDay() != null ? serviceTask.getSendDay().intValue() : 1;
            deadline = DateUtils.addDays(serviceSubtask.getEndtime(), sendDay);
        }
        int sumCompensateTime = serviceSubtaskPreachforms.stream()
                .mapToInt(x -> NumberUtils.toInt(x.getCompensateTime(), 0))
                .sum();
        sumCompensateTime = sumCompensateTime + 1;
        // 只补偿最后一步自身的补偿时间(天),脏值一律按 0 兜底
        // 补偿时间脏值一律按 0 兜底,最后再加 1 天宽限
        deadline = DateUtils.addDays(deadline, sumCompensateTime);
        // 出院(发送)时间 + 本步补偿时间 小于当前时间 -> 超时
        return deadline.getTime() < new Date().getTime();
    }
    /**
     * 任务组基准时间:按 schedule_benchmark 解析后加 day_offset。
     * 0/其它-自定义时间 1-入院 2-出院 3-就诊 4-入组
     */
    private Date resolveTaskGroupDeadlineBase(ServiceSubtask serviceSubtask) {
        if (serviceSubtask.getScheduleId() == null || serviceSubtask.getScheduleDetailId() == null) {
            return null;
        }
        ServiceTaskSchedule schedule = serviceTaskScheduleMapper.selectServiceTaskScheduleById(serviceSubtask.getScheduleId());
        ServiceTaskScheduleDetail detail = serviceTaskScheduleDetailMapper.selectServiceTaskScheduleDetailById(serviceSubtask.getScheduleDetailId());
        if (schedule == null || detail == null || detail.getDayOffset() == null) {
            return null;
        }
        Date base;
        Integer benchmark = schedule.getScheduleBenchmark() == null ? 0 : schedule.getScheduleBenchmark();
        switch (benchmark) {
            case 1:
                base = serviceSubtask.getStarttime();
                break;
            case 2:
                base = serviceSubtask.getEndtime();
                break;
            case 3:
                base = serviceSubtask.getAdmindate();
                break;
            case 4:
                base = serviceSubtask.getCreateTime();
                break;
            default:
                base = schedule.getScheduleBenchmarkCustomTime();
                break;
        }
        return base == null ? null : DateUtils.addDays(base, detail.getDayOffset());
    }
    /**
     * 处理超时随访任务(随访超时 → 生成"再次随访")
     * <p>
     * 扫描「出院满 deadlineDay 天仍未完成」的首次随访任务(sendstate=7 + visit_count=1),
     * 按当前任务的 nexttaskid 生成再次随访任务,科室固定为随访中心科室。
     * <p>
     * 注意:截止时间必须在循环外计算一次。历史实现把 {@code deadlineDay = -deadlineDay}
     * 写在循环体内,每轮取反一次,导致偶数轮用「今天 + deadlineDay」做截止时间,
     * 把出院不满 28 天的超时任务也扫了进来。
     * <p>
     * ⚠️ 2025 修复(开关失效):本方法原不判任何模块开关 ——
     * 关掉 {@code inhospvisit.enabled} 照样建"再次随访"子任务。
     * <p>
     * <b>为什么这里是"入口判开关"而不是像 {@link #dealOutHospInfo()} 那样切到门面</b>:
     * 模块侧只提供了 {@code InhospVisitApi#buildAgainVisitDraft}(组装草稿),
     * <b>没有</b>完整的"扫描超时 → 落库 → 回写原任务状态"入口,
     * 而本方法自己组装并 insert(见下方 {@code insertServiceSubtask})。
     * 造一个模块入口属于新增能力、超出"只解决开关"的范围,所以取最小改动:入口守卫。
     * 超时扫描与回写原任务状态的入口由 service 侧承接(见下方 {@code addSubTaskAgainByDeadline})。
     * <p>
     * ⚠️ 全仓只有 {@code ServiceSubtaskController#dealSubtaskDeadline} 一个调用者。
     *
     * @param orgid       机构ID
     * @param deadlineDay 出院满多少天算超时(正数,如 28)
     */
    public void dealSubtaskDeadline(String orgid, Integer deadlineDay) {
        // ⚠️ 开关守卫:先判再干活,与 dealOutHospInfo / rollTaskGroupCycle 同口径。
@@ -2002,9 +2160,17 @@
        st.setNexttaskflag("1");
        st.setOrgid(orgid);
        List<ServiceTask> serviceTasks = serviceTaskMapper.selectServiceTaskList(st);
        // 截止时间只算一次:出院时间 <= 今天 - deadlineDay
        int days = Math.abs(ObjectUtils.isEmpty(deadlineDay) ? 28 : deadlineDay);
        Date deadline = DateUtils.addDays(new Date(), -days);
        log.info("【dealSubtaskDeadline】开始扫描, orgid={}, day={}, deadline={}, 任务数={}",
                orgid, days, deadline, serviceTasks.size());
        for (ServiceTask serviceTask : serviceTasks) {
            deadlineDay = -deadlineDay;
            Date deadline = DateUtils.addDays(new Date(), deadlineDay);
            if (ObjectUtils.isEmpty(serviceTask.getNexttaskid())) {
                continue;
            }
            //通过任务ID拿到患者信息,并且随访时间得是今天之前的
            ServiceSubtaskEntity serviceSubtaskVO = new ServiceSubtaskEntity();
            serviceSubtaskVO.setOrgid(orgid);
@@ -2013,44 +2179,19 @@
            serviceSubtaskVO.setVisitCount(1);
            serviceSubtaskVO.setTaskid(serviceTask.getTaskid());
            serviceSubtaskVO.setEndOutHospTime(deadline);
            List<ServiceSubtask> serviceSubtaskList = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskVO);
            List<ServiceSubtask> serviceSubtaskList
                    = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskVO);
            log.info("【dealSubtaskDeadline】获取到{}个超时任务, taskid={}", serviceSubtaskList.size(), serviceTask.getTaskid());
            for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
                PatMedInhosp patMedInhospVo = new PatMedInhosp();
                patMedInhospVo.setOrgid(serviceSubtask.getOrgid());
                patMedInhospVo.setInhospid(serviceSubtask.getInhospid());
                patMedInhospVo.setCry(1);
                List<PatMedInhosp> patMedInhospList = iPatMedInhospService.selectPatMedInhospList(patMedInhospVo);
                PatMedInhosp patMedInhosp = new PatMedInhosp();
                if (!patMedInhospList.isEmpty()) {
                    patMedInhosp = patMedInhospList.get(0);
                } else {
                    continue;
                }
                if (ObjectUtils.isEmpty(serviceTask.getNexttaskid())) {
                    continue;
                }
                ServiceTask nextServiceTask = serviceTaskMapper.selectServiceTaskByTaskid(serviceTask.getNexttaskid());
                PatArchive patArchive = patArchiveService.selectPatArchiveByPatid(serviceSubtask.getPatid());
                String config = configService.selectConfigByKey("visit.early.day");
                Integer type = 1;
                //新增再次随访任务
                ServiceSubtask serviceSubtaskAgain = iPatMedInhospService.boxedServiceSubtask(nextServiceTask, patMedInhosp, patArchive, config, type);
                serviceSubtaskAgain.setId(null);
                serviceSubtaskAgain.setUpid(serviceSubtask.getId().intValue());
                serviceSubtaskAgain.setVisitCount(serviceSubtask.getVisitCount() + 1);
                serviceSubtaskAgain.setSendstate(2L);
                serviceSubtaskAgain.setCreateTime(new Date());
                serviceSubtaskAgain.setUpdateTime(new Date());
                serviceSubtaskAgain.setVisitType(serviceSubtask.getVisitType());
                serviceSubtaskAgain.setVisitDeptCode(serviceSubtask.getVisitDeptCode());
                serviceSubtaskAgain.setVisitDeptName(serviceSubtask.getVisitDeptName());
                int i = serviceSubtaskMapper.insertServiceSubtask(serviceSubtaskAgain);
                if (i == 1) {
                    //再次随访任务创建成功后,原任务改为失败状态
                    serviceSubtask.setSendstate(5L);
                    serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
                try {
                    // 生成逻辑统一收口到 service:锁 + 幂等 + 事务 + 发送方式明细初始化
                    String result = iServiceSubtaskService.addSubTaskAgainByDeadline(serviceSubtask);
                    log.info("【dealSubtaskDeadline】生成再次随访, subId={}, taskid={}, 结果={}",
                            serviceSubtask.getId(), serviceTask.getTaskid(), result);
                } catch (Exception e) {
                    // 单条失败不影响其他患者
                    log.error("【dealSubtaskDeadline】生成再次随访失败, subId=" + serviceSubtask.getId()
                            + ", taskid=" + serviceTask.getTaskid(), e);
                }
            }
        }