陈昶聿
4 天以前 eb845e8be695ed1280a7cbf854a4d1dedf9db30d
【丽水】/smartor/serviceSubtask/getSfStatisticsHyperlink 和 /smartor/serviceSubtask/getSfStatistics 统计的时候,对于 service_subtask.current_preachForm 为空的数据 当成 service_subtask.preachform 字符串的第一个执行方式、首次待随访全部(visitCount = 1 && sendstate in (1、2、3、7)) 区别于原先的待随访,这个是给市一使用的
再次待随访全部(visitCount > 1 && sendstate in (1、2、3、7)) 区别于原先的待随访,这个是给市一使用的
首次待随访待发送(visitCount = 1 && sendstate in (1、2))
再次待随访待发送(visitCount > 1 && sendstate in (1、2))
首次待随访已发送(visitCount = 1 && sendstate = in (3、7))
再次待随访已发送(visitCount > 1 && sendstate = in (3、7))
首次已发送(visitCount = 1 && sendstate = 3)
再次已发送(visitCount > 1 && sendstate = 3)
已修改9个文件
284 ■■■■■ 文件已修改
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatMedInhospController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceSubtaskStatistic.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/mapper/PatArchiveMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/PatArchiveMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatMedInhospController.java
@@ -29,6 +29,7 @@
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
 * 患者住院记录Controller
@@ -274,7 +275,11 @@
    @ApiOperation("addManualVisit")
    @PostMapping("/addManualVisit")
    public AjaxResult addManualVisit(@RequestBody ManualVisitDTO manualVisitDTO) {
        return success(patMedInhospService.addManualVisit(manualVisitDTO));
        Map<String, Object> result = patMedInhospService.addManualVisit(manualVisitDTO);
        if (Boolean.TRUE.equals(result.get("hasDuplicate"))) {
            return AjaxResult.warn((String) result.get("tipMessage"), result);
        }
        return success(result);
    }
    /**
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java
@@ -713,6 +713,16 @@
     * taskSituation5Info - taskSituation = 5
     * taskSituation6Info - taskSituation = 7
     * manuallyCalledInfo - isManuallyCalled = 1L
     * manualCallTimeoutInfo - visitCount = 1 && sendstate = 7 && isManuallyCalled = 0
     * manualCallTimeoutAgainInfo - visitCount > 1 && sendstate = 7 && isManuallyCalled = 0
     * pendingFollowUpAllInfo - visitCount = 1 && sendstate in (1,2,3,7)
     * pendingFollowUpAllAgainInfo - visitCount > 1 && sendstate in (1,2,3,7)
     * pendingFollowUpWaitSendInfo - visitCount = 1 && sendstate in (1,2)
     * pendingFollowUpWaitSendAgainInfo - visitCount > 1 && sendstate in (1,2)
     * pendingFollowUpSentInfo - visitCount = 1 && sendstate in (3,7)
     * pendingFollowUpSentAgainInfo - visitCount > 1 && sendstate in (3,7)
     * sentInfo - visitCount = 1 && sendstate = 3
     * sentAgainInfo - visitCount > 1 && sendstate = 3
     * firstDischargeCountInfo - visitCount = 1
     * againFollowUpPushCountInfo - visitCount = 2 && visitDeptCode = sys.smartor.center.dept
     * filterCountList - taskSituation = 6 && sendstate = 4
smartor/src/main/java/com/smartor/domain/ServiceSubtaskStatistic.java
@@ -274,6 +274,46 @@
    @Excel(name = " 二次待随访 ")
    private Long pendingFollowUpAgain = 0L;
    @ApiModelProperty(value = "首次随访人工电话超时(sendstate=7 且 isManuallyCalled=0)")
    @Excel(name = " 首次随访人工电话超时 ")
    private Long manualCallTimeout = 0L;
    @ApiModelProperty(value = "再次随访人工电话超时(sendstate=7 且 isManuallyCalled=0)")
    @Excel(name = " 再次随访人工电话超时 ")
    private Long manualCallTimeoutAgain = 0L;
    @ApiModelProperty(value = "首次待随访全部(sendstate in 1,2,3,7)")
    @Excel(name = " 首次待随访全部 ")
    private Long pendingFollowUpAll = 0L;
    @ApiModelProperty(value = "再次待随访全部(sendstate in 1,2,3,7)")
    @Excel(name = " 再次待随访全部 ")
    private Long pendingFollowUpAllAgain = 0L;
    @ApiModelProperty(value = "首次待随访待发送(sendstate in 1,2)")
    @Excel(name = " 首次待随访待发送 ")
    private Long pendingFollowUpWaitSend = 0L;
    @ApiModelProperty(value = "再次待随访待发送(sendstate in 1,2)")
    @Excel(name = " 再次待随访待发送 ")
    private Long pendingFollowUpWaitSendAgain = 0L;
    @ApiModelProperty(value = "首次待随访已发送(sendstate in 3,7)")
    @Excel(name = " 首次待随访已发送 ")
    private Long pendingFollowUpSent = 0L;
    @ApiModelProperty(value = "再次待随访已发送(sendstate in 3,7)")
    @Excel(name = " 再次待随访已发送 ")
    private Long pendingFollowUpSentAgain = 0L;
    @ApiModelProperty(value = "首次已发送(sendstate=3)")
    @Excel(name = " 首次已发送 ")
    private Long sent = 0L;
    @ApiModelProperty(value = "再次已发送(sendstate=3)")
    @Excel(name = " 再次已发送 ")
    private Long sentAgain = 0L;
    @ApiModelProperty(value = "二次随访成功")
    @Excel(name = " 二次随访成功 ")
    private Long followUpSuccessAgain = 0L;
smartor/src/main/java/com/smartor/mapper/PatArchiveMapper.java
@@ -24,6 +24,14 @@
     */
    public PatArchive selectPatArchiveByPatid(Long patid);
    /**
     * 锁定患者档案行,串行化同一患者的人工随访创建
     *
     * @param patid 患者档案主键
     * @return 患者档案主键,档案不存在或已删除时返回 null
     */
    Long lockPatArchiveByPatid(@Param("patid") Long patid);
    List<PatArchive> selectPatArchiveByPatnos(@Param("patnos") List<String> patnos);
    List<PatArchive> selectPatArchiveByPatidHiss(@Param("patidHiss") List<String> patidHiss);
smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java
@@ -168,6 +168,18 @@
                                                         @Param("sendstates") List<Long> sendstates);
    /**
     * 统计患者指定任务下未完成的随访任务数量(用于人工随访重复校验)
     *
     * @param patid      患者ID
     * @param taskid     任务ID
     * @param sendstates 未完成状态集合
     * @return 未完成任务数量
     */
    int countUnfinishedServiceSubtaskByPatidAndTaskid(@Param("patid") Long patid,
                                                      @Param("taskid") Long taskid,
                                                      @Param("sendstates") List<Long> sendstates);
    /**
     * 删除单一任务(随访)
     *
     * @return 结果
smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java
@@ -22,6 +22,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
@@ -2009,10 +2010,11 @@
    /**
     * 人工随访:删除患者出院记录对应的未完成随访任务,并按指定任务重新生成。
     * 未完成状态口径:sendstate in (1,2,3,7)。
     * 删除旧任务口径:sendstate in (1,2,3,7);
     * 重复任务校验口径:sendstate in (1,2,3,5,7),已有相同 taskid 未完成任务的患者跳过。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
    public Map<String, Object> addManualVisit(ManualVisitDTO manualVisitDTO) {
        if (manualVisitDTO == null) {
            throw new BaseException("入参不能为空");
@@ -2029,20 +2031,27 @@
            throw new BaseException("人工随访任务不存在,taskid:" + manualVisitDTO.getTaskid());
        }
        LinkedHashSet<Long> patids = manualVisitDTO.getPatids().stream()
        // 升序处理,配合患者档案行锁,避免批量请求交叉加锁产生死锁
        List<Long> patids = manualVisitDTO.getPatids().stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toCollection(LinkedHashSet::new));
                .distinct()
                .sorted()
                .collect(Collectors.toList());
        if (patids.isEmpty()) {
            throw new BaseException("患者patid集合不能为空");
        }
        String config = iSysConfigService.selectConfigByKey("visit.early.day");
        List<Long> unfinishedSendstates = Arrays.asList(1L, 2L, 3L, 7L);
        // 与前端待随访口径保持一致,发送失败(5)也视为已有未完成任务
        List<Long> activeSendstates = Arrays.asList(1L, 2L, 3L, 5L, 7L);
        int deletedCount = 0;
        int generatedCount = 0;
        List<Long> noDischargeRecordPatids = new ArrayList<>();
        List<Long> noArchivePatids = new ArrayList<>();
        List<Long> generateFailedPatids = new ArrayList<>();
        List<Long> duplicatePatids = new ArrayList<>();
        List<String> duplicatePatientNames = new ArrayList<>();
        for (Long patid : patids) {
            // 查询患者全部出院记录,SQL 按出院时间倒序,第一条作为本次生成任务的出院记录
@@ -2067,6 +2076,26 @@
            PatArchive patArchive = patArchiveMapper.selectPatArchiveByPatid(patid);
            if (Objects.isNull(patArchive)) {
                noArchivePatids.add(patid);
                continue;
            }
            // 锁定患者档案行,串行化同一患者的并发创建请求
            Long lockedPatid = patArchiveMapper.lockPatArchiveByPatid(patid);
            if (Objects.isNull(lockedPatid)) {
                noArchivePatids.add(patid);
                continue;
            }
            // 同一患者在同一 taskid 下已有未完成随访任务时,跳过该患者,避免重复创建
            int existingSubtaskCount = serviceSubtaskMapper.countUnfinishedServiceSubtaskByPatidAndTaskid(
                    patid, serviceTask.getTaskid(), activeSendstates);
            if (existingSubtaskCount > 0) {
                duplicatePatids.add(patid);
                String patientName = StringUtils.isNotEmpty(patArchive.getName())
                        ? patArchive.getName() : String.valueOf(patid);
                duplicatePatientNames.add(patientName + "(" + patid + ")");
                log.info("addManualVisit 患者已有相同服务,跳过生成,patid:{},taskid:{},已有任务数:{}",
                        patid, serviceTask.getTaskid(), existingSubtaskCount);
                continue;
            }
@@ -2111,14 +2140,29 @@
                    patid, serviceTask.getTaskid(), serviceSubtask.getId());
        }
        String tipMessage = null;
        if (CollectionUtils.isNotEmpty(duplicatePatids)) {
            String taskName = StringUtils.isNotEmpty(serviceTask.getTaskName())
                    ? serviceTask.getTaskName() : String.valueOf(serviceTask.getTaskid());
            tipMessage = "患者" + String.join("、", duplicatePatientNames)
                    + "已有相同服务【" + taskName + "】,已跳过";
            if (generatedCount > 0) {
                tipMessage += ";其余患者已处理";
            }
        }
        Map<String, Object> result = new HashMap<>();
        result.put("deletedCount", deletedCount);
        result.put("generatedCount", generatedCount);
        result.put("noDischargeRecordPatids", noDischargeRecordPatids);
        result.put("noArchivePatids", noArchivePatids);
        result.put("generateFailedPatids", generateFailedPatids);
        log.info("addManualVisit 处理完成,patid数:{},taskid:{},删除未完成任务数:{},生成任务数:{}",
                patids.size(), serviceTask.getTaskid(), deletedCount, generatedCount);
        result.put("duplicatePatids", duplicatePatids);
        result.put("duplicatePatientNames", duplicatePatientNames);
        result.put("hasDuplicate", CollectionUtils.isNotEmpty(duplicatePatids));
        result.put("tipMessage", tipMessage);
        log.info("addManualVisit 处理完成,patid数:{},taskid:{},删除未完成任务数:{},生成任务数:{},重复跳过数:{}",
                patids.size(), serviceTask.getTaskid(), deletedCount, generatedCount, duplicatePatids.size());
        return result;
    }
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -3801,6 +3801,7 @@
                //首次出院随访
                if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() == 1) {
                    applyFirstFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
                    //首次应随访
                    if (serviceSubtask.getSendstate() != null && !serviceSubtask.getSendstate().equals(4L)) {
                        serviceSubtaskStatistic.setNeedFollowUp(serviceSubtaskStatistic.getNeedFollowUp() + 1L);
@@ -3922,6 +3923,7 @@
                    }
                }//二次出院随访
                else if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() > 1) {
                    applyAgainFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
                    //再次应随访
                    if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) {
                        serviceSubtaskStatistic.setNeedFollowUpAgain(serviceSubtaskStatistic.getNeedFollowUpAgain() + 1L);
@@ -4073,6 +4075,60 @@
    }
    /**
     * 市一统计口径:首次待随访、已发送及人工电话超时。
     */
    private void applyFirstFollowUpExtraStatistics(ServiceSubtaskStatistic statistic, ServiceSubtask serviceSubtask) {
        if (serviceSubtask.getVisitCount() == null || serviceSubtask.getVisitCount() != 1 || serviceSubtask.getSendstate() == null) {
            return;
        }
        Long sendstate = serviceSubtask.getSendstate();
        if (sendstate.equals(3L)) {
            statistic.setSent(statistic.getSent() + 1L);
        }
        if (sendstate.equals(7L) && isManuallyNotCalled(serviceSubtask)) {
            statistic.setManualCallTimeout(statistic.getManualCallTimeout() + 1L);
        }
        if (sendstate.equals(1L) || sendstate.equals(2L) || sendstate.equals(3L) || sendstate.equals(7L)) {
            statistic.setPendingFollowUpAll(statistic.getPendingFollowUpAll() + 1L);
        }
        if (sendstate.equals(1L) || sendstate.equals(2L)) {
            statistic.setPendingFollowUpWaitSend(statistic.getPendingFollowUpWaitSend() + 1L);
        }
        if (sendstate.equals(3L) || sendstate.equals(7L)) {
            statistic.setPendingFollowUpSent(statistic.getPendingFollowUpSent() + 1L);
        }
    }
    /**
     * 市一统计口径:再次待随访、已发送及人工电话超时。
     */
    private void applyAgainFollowUpExtraStatistics(ServiceSubtaskStatistic statistic, ServiceSubtask serviceSubtask) {
        if (serviceSubtask.getVisitCount() == null || serviceSubtask.getVisitCount() <= 1 || serviceSubtask.getSendstate() == null) {
            return;
        }
        Long sendstate = serviceSubtask.getSendstate();
        if (sendstate.equals(3L)) {
            statistic.setSentAgain(statistic.getSentAgain() + 1L);
        }
        if (sendstate.equals(7L) && isManuallyNotCalled(serviceSubtask)) {
            statistic.setManualCallTimeoutAgain(statistic.getManualCallTimeoutAgain() + 1L);
        }
        if (sendstate.equals(1L) || sendstate.equals(2L) || sendstate.equals(3L) || sendstate.equals(7L)) {
            statistic.setPendingFollowUpAllAgain(statistic.getPendingFollowUpAllAgain() + 1L);
        }
        if (sendstate.equals(1L) || sendstate.equals(2L)) {
            statistic.setPendingFollowUpWaitSendAgain(statistic.getPendingFollowUpWaitSendAgain() + 1L);
        }
        if (sendstate.equals(3L) || sendstate.equals(7L)) {
            statistic.setPendingFollowUpSentAgain(statistic.getPendingFollowUpSentAgain() + 1L);
        }
    }
    private boolean isManuallyNotCalled(ServiceSubtask serviceSubtask) {
        return serviceSubtask.getIsManuallyCalled() != null && serviceSubtask.getIsManuallyCalled().equals(0L);
    }
    /**
     * 首次随访统计
     *
     * @param serviceSubtaskList
@@ -4093,6 +4149,7 @@
        serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size());
        for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
            applyFirstFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
            //无需随访人次
            if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 4) {
                serviceSubtaskStatistic.setNonFollowUp(serviceSubtaskStatistic.getNonFollowUp() + 1L);
@@ -4266,6 +4323,7 @@
        //出院人次
        serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size());
        for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
            applyAgainFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
            //无需随访人次
            if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 4) {
                serviceSubtaskStatistic.setNonFollowUp(serviceSubtaskStatistic.getNonFollowUp() + 1L);
@@ -4844,6 +4902,16 @@
     * taskSituation5Info - taskSituation = 5
     * taskSituation6Info - taskSituation = 7
     * manuallyCalledInfo - isManuallyCalled = 1L
     * manualCallTimeoutInfo - visitCount = 1 && sendstate = 7 && isManuallyCalled = 0
     * manualCallTimeoutAgainInfo - visitCount > 1 && sendstate = 7 && isManuallyCalled = 0
     * pendingFollowUpAllInfo - visitCount = 1 && sendstate in (1,2,3,7)
     * pendingFollowUpAllAgainInfo - visitCount > 1 && sendstate in (1,2,3,7)
     * pendingFollowUpWaitSendInfo - visitCount = 1 && sendstate in (1,2)
     * pendingFollowUpWaitSendAgainInfo - visitCount > 1 && sendstate in (1,2)
     * pendingFollowUpSentInfo - visitCount = 1 && sendstate in (3,7)
     * pendingFollowUpSentAgainInfo - visitCount > 1 && sendstate in (3,7)
     * sentInfo - visitCount = 1 && sendstate = 3
     * sentAgainInfo - visitCount > 1 && sendstate = 3
     * filterCountList - taskSituation = 6 && sendstate = 4
     *
     */
@@ -5051,6 +5119,48 @@
                break;
            case "manuallyCalledInfo":
                serviceSubtaskCountReq.setIsManuallyCalled(1L);
                break;
            case "manualCallTimeoutInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(7L)));
                serviceSubtaskCountReq.setIsManuallyCalled(0L);
                break;
            case "manualCallTimeoutAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(7L)));
                serviceSubtaskCountReq.setIsManuallyCalled(0L);
                break;
            case "pendingFollowUpAllInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 7L)));
                break;
            case "pendingFollowUpAllAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 7L)));
                break;
            case "pendingFollowUpWaitSendInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L)));
                break;
            case "pendingFollowUpWaitSendAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L)));
                break;
            case "pendingFollowUpSentInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(3L, 7L)));
                break;
            case "pendingFollowUpSentAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(3L, 7L)));
                break;
            case "sentInfo":
                serviceSubtaskCountReq.setVisitCount(1);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(3L)));
                break;
            case "sentAgainInfo":
                serviceSubtaskCountReq.setVisitCount(2);
                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(3L)));
                break;
            default:
                break;
@@ -5413,6 +5523,16 @@
        total.setAbnormal(0L);
        total.setNeedFollowUpAgain(0L);
        total.setPendingFollowUpAgain(0L);
        total.setManualCallTimeout(0L);
        total.setManualCallTimeoutAgain(0L);
        total.setPendingFollowUpAll(0L);
        total.setPendingFollowUpAllAgain(0L);
        total.setPendingFollowUpWaitSend(0L);
        total.setPendingFollowUpWaitSendAgain(0L);
        total.setPendingFollowUpSent(0L);
        total.setPendingFollowUpSentAgain(0L);
        total.setSent(0L);
        total.setSentAgain(0L);
        total.setFollowUpSuccessAgain(0L);
        total.setFollowUpFailAgain(0L);
        total.setManualAgain(0L);
@@ -5447,6 +5567,16 @@
            total.setAbnormal(total.getAbnormal() + (stat.getAbnormal() != null ? stat.getAbnormal() : 0L));
            total.setNeedFollowUpAgain(total.getNeedFollowUpAgain() + (stat.getNeedFollowUpAgain() != null ? stat.getNeedFollowUpAgain() : 0L));
            total.setPendingFollowUpAgain(total.getPendingFollowUpAgain() + (stat.getPendingFollowUpAgain() != null ? stat.getPendingFollowUpAgain() : 0L));
            total.setManualCallTimeout(total.getManualCallTimeout() + (stat.getManualCallTimeout() != null ? stat.getManualCallTimeout() : 0L));
            total.setManualCallTimeoutAgain(total.getManualCallTimeoutAgain() + (stat.getManualCallTimeoutAgain() != null ? stat.getManualCallTimeoutAgain() : 0L));
            total.setPendingFollowUpAll(total.getPendingFollowUpAll() + (stat.getPendingFollowUpAll() != null ? stat.getPendingFollowUpAll() : 0L));
            total.setPendingFollowUpAllAgain(total.getPendingFollowUpAllAgain() + (stat.getPendingFollowUpAllAgain() != null ? stat.getPendingFollowUpAllAgain() : 0L));
            total.setPendingFollowUpWaitSend(total.getPendingFollowUpWaitSend() + (stat.getPendingFollowUpWaitSend() != null ? stat.getPendingFollowUpWaitSend() : 0L));
            total.setPendingFollowUpWaitSendAgain(total.getPendingFollowUpWaitSendAgain() + (stat.getPendingFollowUpWaitSendAgain() != null ? stat.getPendingFollowUpWaitSendAgain() : 0L));
            total.setPendingFollowUpSent(total.getPendingFollowUpSent() + (stat.getPendingFollowUpSent() != null ? stat.getPendingFollowUpSent() : 0L));
            total.setPendingFollowUpSentAgain(total.getPendingFollowUpSentAgain() + (stat.getPendingFollowUpSentAgain() != null ? stat.getPendingFollowUpSentAgain() : 0L));
            total.setSent(total.getSent() + (stat.getSent() != null ? stat.getSent() : 0L));
            total.setSentAgain(total.getSentAgain() + (stat.getSentAgain() != null ? stat.getSentAgain() : 0L));
            total.setFollowUpSuccessAgain(total.getFollowUpSuccessAgain() + (stat.getFollowUpSuccessAgain() != null ? stat.getFollowUpSuccessAgain() : 0L));
            total.setFollowUpFailAgain(total.getFollowUpFailAgain() + (stat.getFollowUpFailAgain() != null ? stat.getFollowUpFailAgain() : 0L));
            total.setManualAgain(total.getManualAgain() + (stat.getManualAgain() != null ? stat.getManualAgain() : 0L));
smartor/src/main/resources/mapper/smartor/PatArchiveMapper.xml
@@ -286,6 +286,14 @@
        where id = #{id} and del_flag=0
    </select>
    <!-- 串行化同一患者的人工随访创建,避免并发重复校验失效 -->
    <select id="lockPatArchiveByPatid" resultType="java.lang.Long">
        select id
        from pat_archive
        where id = #{patid} and del_flag = 0
        for update
    </select>
    <select id="selectPatArchiveByPatnos" resultMap="PatArchiveResult">
        SELECT id, patientno
        FROM pat_archive
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
@@ -1671,6 +1671,19 @@
          </foreach>
    </update>
    <!-- 人工随访重复校验:同一患者同一任务下是否已有未完成随访任务 -->
    <select id="countUnfinishedServiceSubtaskByPatidAndTaskid" resultType="int">
        select count(1)
        from service_subtask
        where del_flag = 0
          and patid = #{patid}
          and taskid = #{taskid}
          and sendstate in
          <foreach collection="sendstates" item="sendstate" open="(" separator="," close=")">
              #{sendstate}
          </foreach>
    </select>
    <select id="getSfStatistics" parameterType="com.smartor.domain.ServiceSubtaskCountReq"
            resultMap="ServiceSubtaskResult">
        select id,