陈昶聿
4 天以前 eb845e8be695ed1280a7cbf854a4d1dedf9db30d
smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java
@@ -5,12 +5,14 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.service.IConfigService;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.DtoConversionUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.smartor.common.ScheduleSubtaskBuilder;
import com.smartor.domain.*;
import com.smartor.domain.DTO.ManualVisitDTO;
import com.smartor.domain.DTO.PatMedInhospDTO;
import com.smartor.domain.entity.ServiceSubtaskEntity;
import com.smartor.mapper.*;
@@ -20,6 +22,8 @@
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;
import java.io.IOException;
@@ -99,6 +103,8 @@
     */
    @Override
    public List<PatMedInhosp> selectPatMedInhospList(PatMedInhosp patMedInhosp) {
        //入院床位排序只在「在院」查询(cry=0)下生效,其它查询一律忽略,保证原有排序不变
        patMedInhosp.setBedSort(normalizeBedSort(patMedInhosp.getCry(), patMedInhosp.getBedSort()));
        if (patMedInhosp.getCry() != null && patMedInhosp.getCry() == 0) {
            patMedInhosp.setDeptcodeList(patMedInhosp.getLeaveldeptcodes());
            patMedInhosp.setHospitaldistrictcodeList(patMedInhosp.getLeavehospitaldistrictcodes());
@@ -119,6 +125,24 @@
            patMedInhosp1.setTagname(tagname);
        }
        return patMedInhospList;
    }
    /**
     * 归一化入院床位排序参数
     * <p>
     * asc / 1 -> asc(床位正序),desc / 2 -> desc(床位倒序),其余(含不传)-> null(不排序)。
     * 只对「在院」患者查询(cry=0)生效,出院/转科等查询忽略该参数。
     *
     * @param cry     出入院标识:0入院 1出院 2转科 3预入院
     * @param bedSort 前端传入的床位排序值
     * @return 归一化后的排序值(asc / desc / null)
     */
    private String normalizeBedSort(Integer cry, String bedSort) {
        if (cry == null || cry != 0 || StringUtils.isEmpty(bedSort)) return null;
        String value = bedSort.trim().toLowerCase();
        if ("asc".equals(value) || "1".equals(value)) return "asc";
        if ("desc".equals(value) || "2".equals(value)) return "desc";
        return null;
    }
    @Override
@@ -500,7 +524,9 @@
                                pmoi.setDeptCodes(deptCodeList);
                                pmoi.setWardCodes(wardCodeList);
                                pmoi.setOpLevelCodes(opLevelCodeList);
                                List<PatMedOperationItem> patMedOperationItems = patMedOperationItemMapper.selectOperationItemList(pmoi);
                                //每次查询只查最大的手术等级记录
                                List<PatMedOperationItem> patMedOperationItems = patMedOperationItemMapper.selectOperationItemListMaxLevel(pmoi);
                                for (PatMedOperationItem patMedOperationItem : patMedOperationItems) {
                                    PatArchive patArchive = patArchiveMapper.selectPatArchiveByPatid(patMedOperationItem.getPatid());
                                    if (Objects.isNull(patArchive)) {
@@ -686,6 +712,29 @@
        //记录insertServiceSubtask语句返回状态
        Integer i = 0;
        try {
            // 兜底:任务模板修改会重置 pat_med_inhosp 的处理标记,但已完成的首访不会删除。
            // 同一 inhospid + taskid 已存在未删除的首次随访(upid 为空)时,不再插入,避免出现两条首次随访。
            if (check
                    && ("2".equals(serviceTask.getServiceType()) || "13".equals(serviceTask.getServiceType()))
                    && (type == 1 || type == 2 || type == 3)) {
                ServiceSubtaskEntity firstQuery = new ServiceSubtaskEntity();
                firstQuery.setInhospid(patMedInhosp1.getInhospid());
                firstQuery.setTaskid(taskid);
                List<ServiceSubtask> firstList = serviceSubtaskMapper.selectServiceSubtaskList(firstQuery);
                boolean firstExists = CollectionUtils.isNotEmpty(firstList)
                        && firstList.stream().anyMatch(item -> item.getUpid() == null);
                if (firstExists) {
                    log.info("患者{}已存在首次随访任务,跳过重复生成, taskid={}, type={}",
                            patMedInhosp1.getInhospid(), taskid, type);
                    PatMedInhosp flagUpdate = new PatMedInhosp();
                    flagUpdate.setInhospid(patMedInhosp1.getInhospid());
                    if (type == 1) flagUpdate.setDeptcheckFlag("1");
                    if (type == 2) flagUpdate.setWardcheckFlag("1");
                    if (type == 3) flagUpdate.setDiagcheckFlag("1");
                    patMedInhospMapper.updatePatMedInhosp(flagUpdate);
                    return;
                }
            }
            //先判断一下,是否需要校验
            if (check && (Objects.isNull(serviceSubtask.getTaskSituation()) || serviceSubtask.getTaskSituation() != 6)) {
                //在新增之前,先通过患者ID,sendstate=2查询一下,在所有长期任务中,是不是还有该患者待执行的任务,有的话,比较之前的endtime是否小于当前的endtaime,如果之前的小于现在的,则直接将之前的停掉(原因再入院)
@@ -990,7 +1039,8 @@
        }
    }
    private Integer addServiceSubtaskPreachform(ServiceSubtask serviceSubtask, String sendstate) {
    @Override
    public Integer addServiceSubtaskPreachform(ServiceSubtask serviceSubtask, String sendstate) {
        //将preachformDesc的json对象,放到list集合中
        ServiceTask serviceTask = serviceTaskMapper.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
        if (Objects.isNull(serviceTask)) {
@@ -1044,6 +1094,8 @@
//        }
        ServiceSubtask serviceSubtask = DtoConversionUtils.sourceToTarget(serviceTask, ServiceSubtask.class);
        serviceSubtask.setTaskid(serviceTask.getTaskid());
        serviceSubtask.setOrgid(serviceTask.getOrgid());
        serviceSubtask.setCampusid(serviceTask.getCampusid());
        if (StringUtils.isNotEmpty(serviceTask.getLibtemplateid()))
            serviceSubtask.setLibtemplateid(Long.valueOf(serviceTask.getLibtemplateid()));
        serviceSubtask.setNurseId(patMedInhosp1.getNurseId());
@@ -1155,6 +1207,8 @@
    private ServiceSubtask boxedServiceSubtaskForOp(ServiceTask serviceTask, PatMedInhosp patMedInhosp1, PatMedOperationItem patMedOperationItem, PatArchive patArchive, String config) {
        ServiceSubtask serviceSubtask = DtoConversionUtils.sourceToTarget(serviceTask, ServiceSubtask.class);
        serviceSubtask.setTaskid(serviceTask.getTaskid());
        serviceSubtask.setOrgid(serviceTask.getOrgid());
        serviceSubtask.setCampusid(serviceTask.getCampusid());
        if (StringUtils.isNotEmpty(serviceTask.getLibtemplateid()))
            serviceSubtask.setLibtemplateid(Long.valueOf(serviceTask.getLibtemplateid()));
        serviceSubtask.setNurseId(patMedInhosp1.getNurseId());
@@ -1296,9 +1350,9 @@
        }
        pmoi.setOpercheckFlag("0");
        pmoi.setMainFlag("1");
        pmoi.setOpid("5414");
        List<PatMedOperationItem> patMedOperationItems = patMedOperationItemMapper.selectOperationItemList(pmoi);
        //每次查询只查最大的手术等级记录
        List<PatMedOperationItem> patMedOperationItems = patMedOperationItemMapper.selectOperationItemListMaxLevel(pmoi);
        log.info("dealOutHospInfo需要处理的出院手术患者信息:{}", CollectionUtils.isNotEmpty(patMedOperationItems) ? patMedOperationItems.size() : null);
        for (PatMedOperationItem opItem : patMedOperationItems) {
@@ -1928,5 +1982,188 @@
        return generatedCount;
    }
    @Override
    public void dealIcd10Subtask(PatMedInhosp pmks, String config) {
        pmks.setDiagcheckFlag("0");
        pmks.setInhospstate("1");
        pmks.setFuflag("1");
        List<PatMedInhosp> patMedInhosps = patMedInhospMapper.selectPatMedInhospList(pmks);
        log.info("dealIcd10Subtask 待处理出院疾病患者数:{}", CollectionUtils.isEmpty(patMedInhosps) ? 0 : patMedInhosps.size());
        if (CollectionUtils.isNotEmpty(patMedInhosps)) {
            int matchedTaskCount = 0;
            for (PatMedInhosp patMedInhosp : patMedInhosps) {
                // 获取患者基本信息,档案不存在时直接标记失败,避免后续空指针
                PatArchive patArchive = patArchiveMapper.selectPatArchiveByPatid(patMedInhosp.getPatid());
                if (Objects.isNull(patArchive)) {
                    PatMedInhosp updateObj = new PatMedInhosp();
                    updateObj.setInhospid(patMedInhosp.getInhospid());
                    updateObj.setDiagcheckFlag("2");
                    updateObj.setLongTaskReason("患者基本信息为空");
                    patMedInhospMapper.updatePatMedInhosp(updateObj);
                    continue;
                }
                matchedTaskCount += addIcd10SubTaskForPatient(patMedInhosp, patArchive, config);
            }
            log.info("dealIcd10Subtask 处理完成,待处理:{},匹配疾病长期任务次数:{}", patMedInhosps.size(), matchedTaskCount);
        }
    }
    /**
     * 人工随访:删除患者出院记录对应的未完成随访任务,并按指定任务重新生成。
     * 删除旧任务口径:sendstate in (1,2,3,7);
     * 重复任务校验口径:sendstate in (1,2,3,5,7),已有相同 taskid 未完成任务的患者跳过。
     */
    @Override
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
    public Map<String, Object> addManualVisit(ManualVisitDTO manualVisitDTO) {
        if (manualVisitDTO == null) {
            throw new BaseException("入参不能为空");
        }
        if (CollectionUtils.isEmpty(manualVisitDTO.getPatids())) {
            throw new BaseException("患者patid集合不能为空");
        }
        if (manualVisitDTO.getTaskid() == null) {
            throw new BaseException("人工随访任务taskid不能为空");
        }
        ServiceTask serviceTask = serviceTaskMapper.selectServiceTaskByTaskid(manualVisitDTO.getTaskid());
        if (Objects.isNull(serviceTask)) {
            throw new BaseException("人工随访任务不存在,taskid:" + manualVisitDTO.getTaskid());
        }
        // 升序处理,配合患者档案行锁,避免批量请求交叉加锁产生死锁
        List<Long> patids = manualVisitDTO.getPatids().stream()
                .filter(Objects::nonNull)
                .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 按出院时间倒序,第一条作为本次生成任务的出院记录
            PatMedInhosp query = new PatMedInhosp();
            query.setPatid(patid);
            query.setCry(1);
            if (StringUtils.isNotEmpty(serviceTask.getOrgid())) {
                query.setOrgid(serviceTask.getOrgid());
            }
            List<PatMedInhosp> inhospList = patMedInhospMapper.selectPatMedInhospList(query);
            if (CollectionUtils.isEmpty(inhospList)) {
                noDischargeRecordPatids.add(patid);
                continue;
            }
            PatMedInhosp latestInhosp = inhospList.get(0);
            if (Objects.isNull(latestInhosp.getEndtime())) {
                noDischargeRecordPatids.add(patid);
                continue;
            }
            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;
            }
            // 先构建新任务,构建失败时不删除原任务,避免患者丢失待随访任务
            ServiceSubtask serviceSubtask = boxedServiceSubtask(serviceTask, latestInhosp, patArchive, config, 1);
            if (Objects.isNull(serviceSubtask) || Objects.isNull(serviceSubtask.getTaskid())) {
                generateFailedPatids.add(patid);
                continue;
            }
            List<Long> inhospids = inhospList.stream()
                    .map(PatMedInhosp::getInhospid)
                    .filter(Objects::nonNull)
                    .distinct()
                    .collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(inhospids)) {
                deletedCount += serviceSubtaskMapper.deleteUnfinishedServiceSubtaskByInhospids(
                        patid, inhospids, unfinishedSendstates);
            }
            serviceSubtask.setId(null);
            serviceSubtask.setUpid(null);
            serviceSubtask.setVisitDeptCode(StringUtils.isNotEmpty(latestInhosp.getLeaveldeptcode())
                    ? latestInhosp.getLeaveldeptcode() : latestInhosp.getLeavehospitaldistrictcode());
            serviceSubtask.setVisitDeptName(StringUtils.isNotEmpty(latestInhosp.getLeaveldeptname())
                    ? latestInhosp.getLeaveldeptname() : latestInhosp.getLeavehospitaldistrictname());
            // 与常规生成逻辑保持一致:手机号为空时生成任务但不执行
            if (StringUtils.isEmpty(serviceSubtask.getPhone())) {
                serviceSubtask.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark())
                        ? serviceSubtask.getRemark() + ",手机号为空" : "手机号为空");
                serviceSubtask.setSendstate(4L);
                serviceSubtask.setResult("error");
            }
            int inserted = serviceSubtaskMapper.insertServiceSubtask(serviceSubtask);
            if (inserted != 1) {
                throw new BaseException("生成随访任务失败,patid:" + patid + ",taskid:" + serviceTask.getTaskid());
            }
            addServiceSubtaskPreachform(serviceSubtask, null);
            generatedCount++;
            log.info("addManualVisit 生成随访任务成功,patid:{},taskid:{},subtaskId:{}",
                    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);
        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;
    }
}