package com.ruoyi.inhospvisit.service; import com.ruoyi.common.core.service.PublicityDataPort; import com.ruoyi.common.core.service.VisitTaskCreateApi; import com.ruoyi.common.core.service.dto.VisitInhospUpdateDto; import com.ruoyi.common.core.service.dto.VisitPendingSubtaskDto; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.core.service.dto.VisitArchiveRow; import com.ruoyi.inhospvisit.domain.row.VisitInhospRow; import com.ruoyi.inhospvisit.domain.row.VisitTaskdeptRow; import com.ruoyi.inhospvisit.domain.row.VisitTaskdiagRow; import com.ruoyi.inhospvisit.mapper.VisitArchiveMapper; import com.ruoyi.inhospvisit.mapper.VisitInhospMapper; import com.ruoyi.inhospvisit.mapper.VisitTaskdeptMapper; import com.ruoyi.inhospvisit.mapper.VisitTaskdiagMapper; import com.ruoyi.inhospvisit.support.InhospFlagWriter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.Date; import java.util.List; /** * 出院随访「编排器」—— 逐行搬自 {@code PatMedInhospServiceImpl} * 的编排层({@code addSubTaskByRule} + 3 个 {@code *ForPatient} + {@code dealInHospInfo})。 * *

来源对照(@ b25c3b51)

* * * * * * * *
本类方法原实现行数
{@link #addSubTaskByRule}{@code addSubTaskByRule} L1691-1847157
{@link #addDeptSubTaskForPatient}L1854-188734
{@link #addWardSubTaskForPatient}L1894-193542
{@link #addIcd10SubTaskForPatient}L1942-200160
{@link #dealInHospInfo}L1321-135939
* *

⚠️ 三个 {@code *ForPatient} 的失败回写写进去的列集合不同,已逐一保真

* * * * * * * * * * * * * * * * * * * * *
位置原写法实际写入的列
科室·无模板(L1866-1872)重读对象 {@code setDeptcheckFlag("2")},另两个 flag 由重读对象原样带回deptcheck + wardcheck + diagcheck + reason
科室·catch(L1879-1885)在新鲜对象上 setdeptcheck + reason
病区·失败(L1914-1920)重读对象 {@code setWardcheckFlag("2")},另两个 flag 原样带回wardcheck + deptcheck + diagcheck + reason
病区·catch(L1927-1933)在新鲜对象上 setwardcheck + reason
疾病·两处(L1967-1971 / L1985-1991)重读对象 {@code setDiagcheckFlag("2")} 并覆写 reasondiagcheck + reason(注意:没有带回其它两个 flag)
疾病·catch(L1993-1998)在新鲜对象上 setdiagcheck + reason
* 因此本类用 {@link PublicityDataPort#updateVisitInhosp(VisitInhospUpdateDto)}(可一次写多个 flag), * 而不是窄的"单 flag"接口 —— 后者表达不了这些差异。 * *

⚠️ 站点差异(原样保留)

* */ @Slf4j @Service public class InhospTaskOrchestrator { /** 出院随访的服务类型 */ private static final String SERVICE_TYPE_DISCHARGE = "2"; /** 维度:科室 */ private static final String DEPT_TYPE_DEPT = "1"; /** 维度:病区 */ private static final String DEPT_TYPE_WARD = "2"; /** 站点:新华(跳过病区+疾病维度) */ private static final String SITE_XH = "xh"; /** 站点:南华附一(疾病维度用诊断名) */ private static final String SITE_NHFY = "nhfy"; @Value("${spring.profiles.active}") private String active; @Autowired private VisitInhospMapper inhospMapper; @Autowired private VisitArchiveMapper archiveMapper; @Autowired private VisitTaskdeptMapper taskdeptMapper; @Autowired private VisitTaskdiagMapper taskdiagMapper; @Autowired private InhospSubtaskWriter subtaskWriter; @Autowired private InhospFlagWriter flagWriter; @Autowired private ObjectProvider dataPortProvider; @Autowired private ObjectProvider taskCreateApiProvider; // ========================================================================== // 1. 基于规则的任务生成(原 L1691-1847) // ========================================================================== /** * 按 {@code service_task_rule} 的「首要(1)/次要(2)」规则编排科室/病区/疾病三个维度。 *

* 逐行对应原 {@code addSubTaskByRule(String config, List patMedInhosps)}。 *

* ⚠️ 规则查询 {@code serviceTaskRuleMapper.selectServiceTaskRuleByDeptOrWard} 仍在 smartor, * 所以本方法接收已查好的规则(由调用方 {@code InhospVisitTaskService} 通过 * {@code PublicityDataPort} 取——该端口已含"按科室/病区查规则"的能力,见下)。 * 这样做是为了避免为"规则表"再加一个 Mapper 或端口。 * * @param inhosp 待处理的出院患者 * @param archive 患者档案 * @param rule 已查好的规则;{@code null} = 未配置规则(全部生成) * @param config {@code sys_config: visit.early.day} */ public void addSubTaskByRule(VisitInhospRow inhosp, VisitArchiveRow archive, VisitTaskRule rule, String config) { try { if (rule == null) { // 原 L1709-1716:规则为空,全部生成 log.info("【基于规则】患者{}未配置规则,执行全部生成逻辑", inhosp.getInhospid()); addDeptSubTaskForPatient(inhosp, archive, config); if (!SITE_XH.equals(active)) { addWardSubTaskForPatient(inhosp, archive, config); addIcd10SubTaskForPatient(inhosp, archive, config); } return; } // 原 L1717-1726:有规则,按规则执行 log.info("【基于规则】患者{}查询到规则:deptRule={}, wradRule={}, diagRule={}", inhosp.getInhospid(), rule.getDeptRule(), rule.getWradRule(), rule.getDiagRule()); boolean deptIsPrimary = "1".equals(rule.getDeptRule()); boolean wardIsPrimary = "1".equals(rule.getWradRule()); boolean diagIsPrimary = "1".equals(rule.getDiagRule()); boolean deptIsSecondary = "2".equals(rule.getDeptRule()); boolean wardIsSecondary = "2".equals(rule.getWradRule()); boolean diagIsSecondary = "2".equals(rule.getDiagRule()); boolean hasPrimaryRule = deptIsPrimary || wardIsPrimary || diagIsPrimary; if (!hasPrimaryRule) { // 原 L1829-1837:没有首要规则,全部生成 log.info("【基于规则】无首要规则,执行全部生成逻辑"); addDeptSubTaskForPatient(inhosp, archive, config); if (!SITE_XH.equals(active)) { addWardSubTaskForPatient(inhosp, archive, config); addIcd10SubTaskForPatient(inhosp, archive, config); } return; } // 原 L1730-1754:先执行所有规则=1 的方法 int deptGenCount = 0; int wardGenCount = 0; int diagGenCount = 0; if (deptIsPrimary) { log.info("【基于规则】执行科室首要规则"); deptGenCount = addDeptSubTaskForPatient(inhosp, archive, config); } if (!SITE_XH.equals(active)) { if (wardIsPrimary) { log.info("【基于规则】执行病区首要规则"); wardGenCount = addWardSubTaskForPatient(inhosp, archive, config); } if (diagIsPrimary) { log.info("【基于规则】执行疾病首要规则"); diagGenCount = addIcd10SubTaskForPatient(inhosp, archive, config); } } int primaryGeneratedCount = deptGenCount + wardGenCount + diagGenCount; log.info("【基于规则】首要规则执行结果: deptGen={}, wardGen={}, diagGen={}, total={}", deptGenCount, wardGenCount, diagGenCount, primaryGeneratedCount); String remark = inhosp.getRemark(); if (primaryGeneratedCount > 0) { // 原 L1758-1796:至少一个首要规则成功 log.info("【基于规则】首要规则有成功(count={}),将次要规则对应flag设为2", primaryGeneratedCount); if (deptIsSecondary) { updateCheckFlag(inhosp, 1, "2", StringUtils.isNotEmpty(remark) ? remark + ";首要规则已生成,次要规则跳过" : "首要规则已生成,次要规则跳过"); } if (!SITE_XH.equals(active)) { if (wardIsSecondary) { updateCheckFlag(inhosp, 2, "2", StringUtils.isNotEmpty(remark) ? remark + ";首要规则已生成,次要规则跳过" : "首要规则已生成,次要规则跳过"); } if (diagIsSecondary) { updateCheckFlag(inhosp, 3, "2", StringUtils.isNotEmpty(remark) ? remark + ";首要规则已生成,次要规则跳过" : "首要规则已生成,次要规则跳过"); } } if (deptIsPrimary && deptGenCount > 0) { updateCheckFlag(inhosp, 1, "1", StringUtils.isNotEmpty(remark) ? remark + ";科室首要规则生成成功" : "科室首要规则生成成功"); } if (!SITE_XH.equals(active)) { if (wardIsPrimary && wardGenCount > 0) { updateCheckFlag(inhosp, 2, "1", StringUtils.isNotEmpty(remark) ? remark + ";病区首要规则生成成功" : "病区首要规则生成成功"); } if (diagIsPrimary && diagGenCount > 0) { updateCheckFlag(inhosp, 3, "1", StringUtils.isNotEmpty(remark) ? remark + ";疾病首要规则生成成功" : "疾病首要规则生成成功"); } } if (deptIsPrimary && deptGenCount == 0) { updateCheckFlag(inhosp, 1, "2", StringUtils.isNotEmpty(remark) ? remark + ";科室首要规则生成失败" : "科室首要规则生成失败"); } if (!SITE_XH.equals(active)) { if (wardIsPrimary && wardGenCount == 0) { updateCheckFlag(inhosp, 2, "2", StringUtils.isNotEmpty(remark) ? remark + ";病区首要规则生成失败" : "病区首要规则生成失败"); } if (diagIsPrimary && diagGenCount == 0) { updateCheckFlag(inhosp, 3, "2", StringUtils.isNotEmpty(remark) ? remark + ";疾病首要规则生成失败" : "疾病首要规则生成失败"); } } } else { // 原 L1797-1828:所有首要规则全部失败 → 把 =1 的置 2,然后执行次要规则 log.info("【基于规则】首要规则全部失败,将=1的flag设为2,开始执行次要规则"); if (deptIsPrimary) { updateCheckFlag(inhosp, 1, "2", StringUtils.isNotEmpty(remark) ? remark + ";科室首要规则全部失败" : "科室首要规则全部失败"); } if (!SITE_XH.equals(active)) { if (wardIsPrimary) { updateCheckFlag(inhosp, 2, "2", StringUtils.isNotEmpty(remark) ? remark + ";病区首要规则全部失败" : "病区首要规则全部失败"); } if (diagIsPrimary) { updateCheckFlag(inhosp, 3, "2", StringUtils.isNotEmpty(remark) ? remark + ";疾病首要规则全部失败" : "疾病首要规则全部失败"); } } if (deptIsSecondary) { log.info("【基于规则】执行科室次要规则"); addDeptSubTaskForPatient(inhosp, archive, config); } if (!SITE_XH.equals(active)) { if (wardIsSecondary) { log.info("【基于规则】执行病区次要规则"); addWardSubTaskForPatient(inhosp, archive, config); } if (diagIsSecondary) { log.info("【基于规则】执行疾病次要规则"); addIcd10SubTaskForPatient(inhosp, archive, config); } } } } catch (Exception e) { // 原 L1839-1845 e.printStackTrace(); log.error("【基于规则】患者{}处理异常:{}", inhosp.getInhospid(), e.getMessage()); updateCheckFlag(inhosp, 1, "2", "基于规则处理异常"); } } /** * 统一回写标志位 —— 委托 {@link InhospFlagWriter}(原 {@code updateCheckFlag})。 */ private void updateCheckFlag(VisitInhospRow inhosp, Integer type, String flagValue, String remark) { flagWriter.updateCheckFlag(inhosp, type, flagValue, remark); } // ========================================================================== // 2. 科室维度(原 L1854-1887) // ========================================================================== /** * 为单个患者生成科室维度的子任务。 * * @return 生成的子任务数量 */ public int addDeptSubTaskForPatient(VisitInhospRow inhosp, VisitArchiveRow archive, String config) { int generatedCount = 0; try { // 原 L1857-1864:按患者所在科室查该科室的长期任务(dept_type=1 + service_type=2) VisitTaskdeptRow query = new VisitTaskdeptRow(); query.setLongtask(1L); query.setDeptCode(inhosp.getLeaveldeptcode()); query.setDeptType(DEPT_TYPE_DEPT); query.setServiceType(SERVICE_TYPE_DISCHARGE); query.setOrgid(inhosp.getOrgid()); List taskdepts = taskdeptMapper.selectList(query); if (CollectionUtils.isEmpty(taskdepts)) { // 原 L1866-1872:⚠️ 在【重读对象】上改 deptcheck,并把另两个 flag 原样带回 VisitInhospRow fresh = inhospMapper.selectByInhospid(inhosp.getInhospid()); VisitInhospUpdateDto u = new VisitInhospUpdateDto(); u.setInhospid(inhosp.getInhospid()); u.setDeptcheckFlag("2"); u.setWardcheckFlag(fresh == null ? null : fresh.getWardcheckFlag()); u.setDiagcheckFlag(fresh == null ? null : fresh.getDiagcheckFlag()); // 原实现不判 null(fresh.getLongTaskReason() + "..."),原样保留 u.setLongTaskReason((fresh == null ? null : fresh.getLongTaskReason()) + "该患者所在科室未配置离院长期任务;"); updateInhosp(u); } else { for (VisitTaskdeptRow t : taskdepts) { subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 1, config); generatedCount++; } } } catch (Exception e) { // 原 L1879-1885:⚠️ 在【新鲜对象】上 set —— 只写 deptcheck + reason e.printStackTrace(); log.error("【科室任务】患者{}处理异常:{}", inhosp.getInhospid(), e.getMessage()); VisitInhospUpdateDto u = new VisitInhospUpdateDto(); u.setInhospid(inhosp.getInhospid()); u.setDeptcheckFlag("2"); u.setLongTaskReason("科室任务处理异常"); updateInhosp(u); } return generatedCount; } // ========================================================================== // 3. 病区维度(原 L1894-1935) // ========================================================================== /** * 为单个患者生成病区维度的子任务。 * * @return 生成的子任务数量 */ public int addWardSubTaskForPatient(VisitInhospRow inhosp, VisitArchiveRow archive, String config) { int generatedCount = 0; try { String errorreason = null; List taskdeptList = null; // 原 L1899-1912 if (!StringUtils.isEmpty(inhosp.getLeavehospitaldistrictcode())) { VisitTaskdeptRow query = new VisitTaskdeptRow(); query.setLongtask(1L); query.setDeptCode(inhosp.getLeavehospitaldistrictcode()); query.setDeptType(DEPT_TYPE_WARD); query.setServiceType(SERVICE_TYPE_DISCHARGE); query.setOrgid(inhosp.getOrgid()); taskdeptList = taskdeptMapper.selectList(query); if (CollectionUtils.isEmpty(taskdeptList)) { errorreason = "该患者所在病区未配置长期任务;"; } } else { errorreason = "离院病区为空;"; } if (StringUtils.isNotEmpty(errorreason)) { // 原 L1914-1920:⚠️ 重读对象 setWardcheck,另两个 flag 原样带回 VisitInhospRow fresh = inhospMapper.selectByInhospid(inhosp.getInhospid()); VisitInhospUpdateDto u = new VisitInhospUpdateDto(); u.setInhospid(inhosp.getInhospid()); u.setWardcheckFlag("2"); u.setDeptcheckFlag(fresh == null ? null : fresh.getDeptcheckFlag()); u.setDiagcheckFlag(fresh == null ? null : fresh.getDiagcheckFlag()); // 原有判空(与科室维度那处不同),原样保留 u.setLongTaskReason(StringUtils.isNotEmpty(fresh == null ? null : fresh.getLongTaskReason()) ? fresh.getLongTaskReason() + errorreason : errorreason); updateInhosp(u); } else { for (VisitTaskdeptRow t : taskdeptList) { subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 2, config); generatedCount++; } } } catch (Exception e) { // 原 L1927-1933:新鲜对象 —— 只写 wardcheck + reason e.printStackTrace(); log.error("【病区任务】患者{}处理异常:{}", inhosp.getInhospid(), e.getMessage()); VisitInhospUpdateDto u = new VisitInhospUpdateDto(); u.setInhospid(inhosp.getInhospid()); u.setWardcheckFlag("2"); u.setLongTaskReason("病区任务处理异常"); updateInhosp(u); } return generatedCount; } // ========================================================================== // 4. 疾病维度(原 L1942-2001) // ========================================================================== /** * 为单个患者生成疾病维度的子任务。 * * @return 生成的子任务数量 */ public int addIcd10SubTaskForPatient(VisitInhospRow inhosp, VisitArchiveRow archive, String config) { int generatedCount = 0; try { // 原 L1945-1947 if (StringUtils.isEmpty(inhosp.getLeaveicd10code())) { return 0; } // 原 L1949-1963:站点差异 —— nhfy 用诊断名,其它用 ICD10 编码 VisitTaskdiagRow query = new VisitTaskdiagRow(); query.setLongtask(1L); if (SITE_NHFY.equals(active)) { query.setIcd10name(inhosp.getLeavediagname()); if (StringUtils.isEmpty(inhosp.getLeavediagname())) { // 原 L1954-1959:⚠️ 新鲜对象 —— 只写 diagcheck + reason(覆盖式) VisitInhospUpdateDto u = new VisitInhospUpdateDto(); u.setInhospid(inhosp.getInhospid()); u.setDiagcheckFlag("2"); u.setLongTaskReason("该患者主诊断名称为空;"); updateInhosp(u); return 0; } } else { query.setIcd10code(inhosp.getLeaveicd10code()); } query.setOrgid(inhosp.getOrgid()); List taskdiags = taskdiagMapper.selectListByIcdName(query); if (CollectionUtils.isEmpty(taskdiags)) { // 原 L1967-1971:⚠️ 重读对象,但【只】setDiagcheckFlag + 覆写 reason // (与科室/病区那两处不同:这里不带回其它 flag) VisitInhospUpdateDto u = new VisitInhospUpdateDto(); u.setInhospid(inhosp.getInhospid()); u.setDiagcheckFlag("2"); u.setLongTaskReason("该患者所患疾病未配置长期任务;"); updateInhosp(u); } else { int flag = 0; for (VisitTaskdiagRow t : taskdiags) { // 原 L1975-1983:必须科室或病区能匹配上才建 if (StringUtils.isNotEmpty(t.getDeptCode()) && t.getDeptCode().equals(inhosp.getLeaveldeptcode())) { subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 3, config); generatedCount++; flag = 1; } else if (StringUtils.isNotEmpty(t.getWardCode()) && t.getWardCode().equals(inhosp.getLeavehospitaldistrictcode())) { subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 3, config); generatedCount++; flag = 1; } } if (flag == 0) { // 原 L1985-1991 log.error("该科室或病区对于患者所患疾病未配置长期任务,患者:{}", inhosp.getInhospid()); VisitInhospUpdateDto u = new VisitInhospUpdateDto(); u.setInhospid(inhosp.getInhospid()); u.setDiagcheckFlag("2"); u.setLongTaskReason("该科室或病区对于患者所患疾病未配置长期任务;"); updateInhosp(u); } } } catch (Exception e) { // 原 L1993-1998:新鲜对象 —— 只写 diagcheck + reason e.printStackTrace(); log.error("【疾病任务】患者{}处理异常:{}", inhosp.getInhospid(), e.getMessage()); VisitInhospUpdateDto u = new VisitInhospUpdateDto(); u.setInhospid(inhosp.getInhospid()); u.setDiagcheckFlag("2"); u.setLongTaskReason("疾病任务处理异常"); updateInhosp(u); } return generatedCount; } // ========================================================================== // 5. 再入院处理(原 L1321-1359) // ========================================================================== /** * 再入院处理:新入院记录出现时,把该患者旧的待执行随访停掉。 *

* 逐行对应原 {@code dealInHospInfo(List patMedInhospList)}。 *

* ⚠️ 原实现的两个要点: *

    *
  1. 查询条件是 {@code patid + sendstate=2},不设 taskid(按患者全量查); * 这里传 {@code null} 表示不按 taskid 过滤;
  2. *
  3. 景宁({@code 47255004333112711A1001})跳过停机 —— 不过这次判的是 * {@code serviceSubtask1.getOrgid()}(子任务的 orgid),不是入参患者的 orgid。
  4. *
* * @param newInHosp 新入院的出入院记录(用它和旧子任务的 endtime 做比较) */ public void dealInHospInfo(VisitInhospRow newInHosp) { try { VisitTaskCreateApi api = taskApi(); if (api == null) { log.warn("【出入院随访】VisitTaskCreateApi 不可用,跳过再入院处理。inhospid={}", newInHosp.getInhospid()); return; } // 原 L1324-1328:patid + sendstate=2(不设 taskid) List pendingList = api.findPendingSubtasks(newInHosp.getPatid(), null); if (CollectionUtils.isEmpty(pendingList)) { return; } for (VisitPendingSubtaskDto pending : pendingList) { // 原 L1332-1335:不是长期任务,不处理 if (pending.getLongSendTime() == null) { continue; } // 原 L1336-1340:⚠️ 判的是【子任务的 orgid】 boolean firstVisit = ORG_JINGNING.equals(pending.getOrgid()); if (firstVisit) { continue; } // 原 L1342-1350:旧子任务的出院时间在新入院时间之前 → 停掉 if (pending.getEndtime() != null && newInHosp.getStarttime() != null && pending.getEndtime().before(newInHosp.getStarttime())) { pending.setSendstate(4L); pending.setIsVisitAgain(2); pending.setRemark("患者再入院"); pending.setResult("error"); api.updatePendingSubtask(pending); } } } catch (Exception e) { // 原 L1355-1357 log.error("入院数据信息出异常了:{},{}", newInHosp, e.getMessage()); } } /** 景宁机构ID(原 L1338;与 InhospSubtaskWriter 里那份是同一常量,已逐字核对) */ private static final String ORG_JINGNING = "47255004333112711A1001"; // ========================================================================== // 辅助 // ========================================================================== private void updateInhosp(VisitInhospUpdateDto u) { PublicityDataPort port = dataPort(); if (port == null) { log.warn("【出入院随访】PublicityDataPort 不可用,无法写回标志位。inhospid={}", u.getInhospid()); return; } port.updateVisitInhosp(u); } private PublicityDataPort dataPort() { if (dataPortProvider == null) { return null; } try { return dataPortProvider.stream().findFirst().orElse(null); } catch (Exception e) { log.warn("【出入院随访】获取 PublicityDataPort 失败:{}", e.toString()); return null; } } private VisitTaskCreateApi taskApi() { if (taskCreateApiProvider == null) { return null; } try { return taskCreateApiProvider.stream().findFirst().orElse(null); } catch (Exception e) { log.warn("【出入院随访】获取 VisitTaskCreateApi 失败:{}", e.toString()); return null; } } /** * {@code service_task_rule} 的规则快照(原 {@code ServiceTaskRule} 的随访侧视图)。 *

* 规则查询本身仍由 {@code PublicityDataPort} 提供(它已经在服务宣教链路), * 本类只消费结果,避免为规则表再加一个 Mapper。 */ public static class VisitTaskRule { /** 科室规则:"1"=首要 "2"=次要,其它=不参与 */ private String deptRule; /** 病区规则(原字段名就是拼错的 {@code wradRule},保持同名以免读错) */ private String wradRule; /** 疾病规则 */ private String diagRule; public String getDeptRule() { return deptRule; } public void setDeptRule(String deptRule) { this.deptRule = deptRule; } public String getWradRule() { return wradRule; } public void setWradRule(String wradRule) { this.wradRule = wradRule; } public String getDiagRule() { return diagRule; } public void setDiagRule(String diagRule) { this.diagRule = diagRule; } } /** 供测试/日志:本类是否拿到了必需的 Mapper */ boolean hasMappers() { return inhospMapper != null && archiveMapper != null && taskdeptMapper != null && taskdiagMapper != null; } /** 预留:供日志使用的时间戳(避免 IDE 报未使用 import Date) */ @SuppressWarnings("unused") private Date now() { return new Date(); } }