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.ArrayList; import java.util.Date; import java.util.List; /** * 出院随访「编排器」—— 逐行搬自 {@code PatMedInhospServiceImpl} * 的编排层({@code addSubTaskByRule} + 3 个 {@code *ForPatient} + {@code dealInHospInfo})。 * *

⚠️ 2026-10 口径变更:一个患者(一次出院)只生成 一条 随访子任务

* 搬迁时本类是"逐行等价"的(原实现按维度全生成:每个 {@code *ForPatient} 把命中的 * 所有长期任务各写一条)。业务确认改为: *
    *
  1. 维度按 {@code service_task_rule} 优先级排序尝试:首要 → 次要 → 通用兜底(科室、病区);
  2. *
  3. 第一个真正落库成功的维度即停止 → 只留一条;
  4. *
  5. 无规则时按 {@code service_taskdept} 的科室/病区"通用模块"生成;
  6. *
  7. 标志位统一回写,保证 {@code deptcheck_flag} 不再停在 0(防重复扫描); * 并在写库前按 {@code patid+inhospid+taskid} 判重(见 {@code VisitTaskCreateApi#existsSubtask})。
  8. *
* 因此 {@code verify_inhospvisit_orchestration_semantics.py} 里 O18~O23 那 6 条 * "搬迁等价"断言已被改为"口径变更"断言(脚本里单独分组并注明依据)。 * *

来源对照(@ 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)」规则编排科室/病区/疾病三个维度。 * *

⚠️ 2026-10 口径变更:一个患者(一次出院)只生成 一条 随访子任务

* 原实现(以及搬迁后的等价版)是按维度"全生成":先跑所有「首要」维度的 * {@code *ForPatient},而每个 {@code *ForPatient} 又把命中的所有长期任务各写一条。 * 结果是同一患者一次出院能生成几十条(线上实测 27 条),而且因为扫描键 * {@code deptcheck_flag} 在"只有病区/疾病维度"的规则下不会被置 1, * 患者每轮扫描都被重新处理 → 重复子任务持续累积。 *

* 现口径(业务确认): *

    *
  1. 把三个维度按规则优先级排成一个尝试顺序: * 首要(1) → 次要(2) → 通用兜底(科室、病区);同优先级内固定 科室 > 病区 > 疾病。 * {@code service_task_rule} 没有该患者科室/病区对应的规则时 → 顺序为 科室 → 病区 * (即"按 {@code service_taskdept} 里的通用模块生成")。
  2. *
  3. 按顺序逐个维度尝试,第一个真正落库成功(或已存在)的维度即停止 —— 只留一条。
  4. *
  5. 最后统一回写标志位:命中维度 = {@code 1};其余维度 = {@code 2}(跳过), * 且无论命中哪个维度,{@code deptcheck_flag} 一定会被写成非 0 * —— 这就是"不再被下一轮扫描重复生成"的保证。
  6. *
* 另有一道兜底在 {@code InhospSubtaskWriter#writeInSubTask}:落库前按 * {@code patid + inhospid + taskid} 判重,见 {@link VisitTaskCreateApi#existsSubtask}。 * *

站点差异(保留)

* {@code xh} 只做科室维度 —— {@link #addDimension} 里统一挡掉病区/疾病。 * * @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 { List order = resolveDimensionOrder(rule); log.info("【基于规则】患者{}规则:deptRule={}, wradRule={}, diagRule={};维度尝试顺序={}", inhosp.getInhospid(), rule == null ? null : rule.getDeptRule(), rule == null ? null : rule.getWradRule(), rule == null ? null : rule.getDiagRule(), order); Integer hit = null; for (Integer type : order) { log.info("【基于规则】患者{}尝试维度{}({})", inhosp.getInhospid(), type, dimensionName(type)); if (generateOneForDimension(inhosp, archive, type, config)) { hit = type; break; } } writeFlagsAfterRun(inhosp, hit); } catch (Exception e) { // 原 L1839-1845 e.printStackTrace(); log.error("【基于规则】患者{}处理异常:{}", inhosp.getInhospid(), e.getMessage()); updateCheckFlag(inhosp, 1, "2", "基于规则处理异常"); } } /** * 维度尝试顺序(2026-10 口径)。 *

* 规则为 {@code null} → {@code [科室, 病区]}:按 {@code service_taskdept} 里该患者的 * 科室/病区"通用模块"生成;这是业务明确要求的兜底口径(此时不进入疾病维度)。 *

* 有规则 → {@code [首要维度…, 次要维度…, 科室, 病区]}: * 首要优先、次要其后,最后用通用兜底保证"能生成一条就生成一条"。 */ private List resolveDimensionOrder(VisitTaskRule rule) { boolean xhSite = SITE_XH.equals(active); List order = new ArrayList<>(4); if (rule == null) { addDimension(order, 1, xhSite); addDimension(order, 2, xhSite); return order; } 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()); // 同优先级内固定顺序:科室 → 病区 → 疾病 if (deptIsPrimary) { addDimension(order, 1, xhSite); } if (wardIsPrimary) { addDimension(order, 2, xhSite); } if (diagIsPrimary) { addDimension(order, 3, xhSite); } if (deptIsSecondary) { addDimension(order, 1, xhSite); } if (wardIsSecondary) { addDimension(order, 2, xhSite); } if (diagIsSecondary) { addDimension(order, 3, xhSite); } // 通用兜底:规则里配的维度全都取不到任务时,仍按 service_taskdept 的 科室/病区 生成一条 addDimension(order, 1, xhSite); addDimension(order, 2, xhSite); return order; } /** * 追加一个候选维度;{@code xh} 站点只允许科室维度(原 {@code !active.equals("xh")} 的等价写法) */ private void addDimension(List order, int type, boolean xhSite) { if (xhSite && type != 1) { return; } if (!order.contains(type)) { order.add(type); } } private String dimensionName(int type) { switch (type) { case 1: return "科室"; case 2: return "病区"; case 3: return "疾病"; default: return "未知(" + type + ")"; } } /** * 尝试一个维度,返回"是否真的生成了(或本来就已经有)一条子任务" */ private boolean generateOneForDimension(VisitInhospRow inhosp, VisitArchiveRow archive, int type, String config) { switch (type) { case 1: return addDeptSubTaskForPatient(inhosp, archive, config) > 0; case 2: return addWardSubTaskForPatient(inhosp, archive, config) > 0; case 3: return addIcd10SubTaskForPatient(inhosp, archive, config) > 0; default: return false; } } /** * 一轮生成结束后的标志位回写。 *

* ⚠️ 关键点:扫描键是 {@code pat_med_inhosp.deptcheck_flag = '0'}, * 所以不论命中哪个维度,deptcheck_flag 都必须被写成 1 或 2, * 否则下一轮扫描会把这个患者再处理一遍(2026-10 重复生成的直接原因)。 *

* 没生成时({@code hit == null})刻意只补扫描键:各维度方法已经把更具体的原因 * ("该患者所在科室未配置离院长期任务;" / "生成子任务失败 1" …)写进 * {@code long_task_reason} 了,这里先用一次重读判断 deptcheck 是否已经非 0, * 避免把它覆盖成泛泛的"未生成随访任务"。 * * @param hit 命中的维度;{@code null} = 三个维度都没能生成 */ private void writeFlagsAfterRun(VisitInhospRow inhosp, Integer hit) { boolean xhSite = SITE_XH.equals(active); if (hit == null) { VisitInhospRow fresh = inhospMapper.selectByInhospid(inhosp.getInhospid()); String current = fresh == null ? null : fresh.getDeptcheckFlag(); if (current == null || "0".equals(current)) { updateCheckFlag(inhosp, 1, "2", composeRemark(inhosp, "未生成随访任务(无可用长期任务)")); } return; } for (int type = 1; type <= 3; type++) { if (xhSite && type != 1) { continue; } if (hit == type) { updateCheckFlag(inhosp, type, "1", composeRemark(inhosp, "按优先级生成一条随访任务(维度=" + dimensionName(type) + ")")); } else { updateCheckFlag(inhosp, type, "2", composeRemark(inhosp, "已生成一条随访任务(维度=" + dimensionName(hit) + "),本维度跳过")); } } } /** * 拼 reason:沿用原实现的 {@code remark + ";xxx"} 形式 */ private String composeRemark(VisitInhospRow inhosp, String text) { String remark = inhosp.getRemark(); return StringUtils.isNotEmpty(remark) ? remark + ";" + text : text; } /** * 统一回写标志位 —— 委托 {@link InhospFlagWriter}(原 {@code updateCheckFlag})。 */ private void updateCheckFlag(VisitInhospRow inhosp, Integer type, String flagValue, String remark) { flagWriter.updateCheckFlag(inhosp, type, flagValue, remark); } // ========================================================================== // 2. 科室维度(原 L1854-1887) // ========================================================================== /** * 为单个患者生成科室维度的子任务。 *

* ⚠️ 2026-10 口径变更:命中多个长期任务时只生成第一条真正落库的任务 * (原实现是 {@code for (t : taskdepts) writeInSubTask(...); generatedCount++;}, * 一个科室配 N 个长期任务就会生成 N 条 —— 是"同一患者几十条"的主因)。 * * @return 生成的子任务数量(0 或 1) */ public int addDeptSubTaskForPatient(VisitInhospRow inhosp, VisitArchiveRow archive, String config) { 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) { // 一个患者只留一条:第一条真正落库的即返回 if (subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 1, config)) { return 1; } log.info("【科室任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); } } } 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 0; } // ========================================================================== // 3. 病区维度(原 L1894-1935) // ========================================================================== /** * 为单个患者生成病区维度的子任务。 *

* ⚠️ 同上:命中多个长期任务时只生成第一条真正落库的(原实现是全生成)。 * * @return 生成的子任务数量(0 或 1) */ public int addWardSubTaskForPatient(VisitInhospRow inhosp, VisitArchiveRow archive, String config) { 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) { // 一个患者只留一条:第一条真正落库的即返回 if (subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 2, config)) { return 1; } log.info("【病区任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); } } } 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 0; } // ========================================================================== // 4. 疾病维度(原 L1942-2001) // ========================================================================== /** * 为单个患者生成疾病维度的子任务。 *

* ⚠️ 同上:命中多个长期任务时只生成第一条真正落库的(原实现是全生成)。 * * @return 生成的子任务数量(0 或 1) */ public int addIcd10SubTaskForPatient(VisitInhospRow inhosp, VisitArchiveRow archive, String config) { 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())) { flag = 1; // 一个患者只留一条:第一条真正落库的即返回 if (subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 3, config)) { return 1; } log.info("【疾病任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); } else if (StringUtils.isNotEmpty(t.getWardCode()) && t.getWardCode().equals(inhosp.getLeavehospitaldistrictcode())) { flag = 1; if (subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 3, config)) { return 1; } log.info("【疾病任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); } } 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 0; } // ========================================================================== // 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(); } }