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})。
|
*
|
* <h3>⚠️ 2026-10 口径变更:一个患者(一次出院)只生成 <b>一条</b> 随访子任务</h3>
|
* 搬迁时本类是"逐行等价"的(原实现按维度全生成:每个 {@code *ForPatient} 把命中的
|
* <b>所有</b>长期任务各写一条)。业务确认改为:
|
* <ol>
|
* <li>维度按 {@code service_task_rule} 优先级排序尝试:首要 → 次要 → 通用兜底(科室、病区);</li>
|
* <li>第一个真正落库成功的维度即停止 → 只留一条;</li>
|
* <li>无规则时按 {@code service_taskdept} 的科室/病区"通用模块"生成;</li>
|
* <li>标志位统一回写,保证 {@code deptcheck_flag} 不再停在 0(防重复扫描);
|
* 并在写库前按 {@code patid+inhospid+taskid} 判重(见 {@code VisitTaskCreateApi#existsSubtask})。</li>
|
* </ol>
|
* 因此 {@code verify_inhospvisit_orchestration_semantics.py} 里 O18~O23 那 6 条
|
* "搬迁等价"断言已被改为"口径变更"断言(脚本里单独分组并注明依据)。
|
*
|
* <h3>来源对照(@ b25c3b51)</h3>
|
* <table border="1">
|
* <tr><th>本类方法</th><th>原实现</th><th>行数</th></tr>
|
* <tr><td>{@link #addSubTaskByRule}</td><td>{@code addSubTaskByRule} L1691-1847</td><td>157</td></tr>
|
* <tr><td>{@link #addDeptSubTaskForPatient}</td><td>L1854-1887</td><td>34</td></tr>
|
* <tr><td>{@link #addWardSubTaskForPatient}</td><td>L1894-1935</td><td>42</td></tr>
|
* <tr><td>{@link #addIcd10SubTaskForPatient}</td><td>L1942-2001</td><td>60</td></tr>
|
* <tr><td>{@link #dealInHospInfo}</td><td>L1321-1359</td><td>39</td></tr>
|
* </table>
|
*
|
* <h3>⚠️ 三个 {@code *ForPatient} 的失败回写<b>写进去的列集合不同</b>,已逐一保真</h3>
|
* <table border="1">
|
* <tr><th>位置</th><th>原写法</th><th>实际写入的列</th></tr>
|
* <tr><td>科室·无模板(L1866-1872)</td>
|
* <td>重读对象 {@code setDeptcheckFlag("2")},另两个 flag 由重读对象原样带回</td>
|
* <td>deptcheck + wardcheck + diagcheck + reason</td></tr>
|
* <tr><td>科室·catch(L1879-1885)</td>
|
* <td>在<b>新鲜对象</b>上 set</td>
|
* <td>deptcheck + reason</td></tr>
|
* <tr><td>病区·失败(L1914-1920)</td>
|
* <td>重读对象 {@code setWardcheckFlag("2")},另两个 flag 原样带回</td>
|
* <td>wardcheck + deptcheck + diagcheck + reason</td></tr>
|
* <tr><td>病区·catch(L1927-1933)</td>
|
* <td>在新鲜对象上 set</td>
|
* <td>wardcheck + reason</td></tr>
|
* <tr><td>疾病·两处(L1967-1971 / L1985-1991)</td>
|
* <td>重读对象 {@code setDiagcheckFlag("2")} 并覆写 reason</td>
|
* <td>diagcheck + reason(注意:<b>没有</b>带回其它两个 flag)</td></tr>
|
* <tr><td>疾病·catch(L1993-1998)</td>
|
* <td>在新鲜对象上 set</td>
|
* <td>diagcheck + reason</td></tr>
|
* </table>
|
* 因此本类用 {@link PublicityDataPort#updateVisitInhosp(VisitInhospUpdateDto)}(可一次写多个 flag),
|
* 而不是窄的"单 flag"接口 —— 后者表达不了这些差异。
|
*
|
* <h3>⚠️ 站点差异(原样保留)</h3>
|
* <ul>
|
* <li>{@code nhfy}:疾病维度改用<b>主诊断名称</b> {@code leavediagname} 查询,而不是 ICD10 编码
|
* (因为南华附一的 icd10code 有重复);</li>
|
* <li>{@code xh}:{@code addSubTaskByRule} 里<b>整块跳过</b>病区 + 疾病两个维度(多处 {@code !xh} 判断)。</li>
|
* </ul>
|
*/
|
@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<PublicityDataPort> dataPortProvider;
|
|
@Autowired
|
private ObjectProvider<VisitTaskCreateApi> taskCreateApiProvider;
|
|
// ==========================================================================
|
// 1. 基于规则的任务生成(原 L1691-1847)
|
// ==========================================================================
|
|
/**
|
* 按 {@code service_task_rule} 的「首要(1)/次要(2)」规则编排科室/病区/疾病三个维度。
|
*
|
* <h3>⚠️ 2026-10 口径变更:一个患者(一次出院)只生成 <b>一条</b> 随访子任务</h3>
|
* 原实现(以及搬迁后的等价版)是按维度"全生成":先跑所有「首要」维度的
|
* {@code *ForPatient},而每个 {@code *ForPatient} 又把命中的<b>所有</b>长期任务各写一条。
|
* 结果是同一患者一次出院能生成几十条(线上实测 27 条),而且因为扫描键
|
* {@code deptcheck_flag} 在"只有病区/疾病维度"的规则下不会被置 1,
|
* 患者每轮扫描都被重新处理 → 重复子任务持续累积。
|
* <p>
|
* 现口径(业务确认):
|
* <ol>
|
* <li>把三个维度按规则优先级排成一个<b>尝试顺序</b>:
|
* 首要(1) → 次要(2) → 通用兜底(科室、病区);同优先级内固定 科室 > 病区 > 疾病。
|
* {@code service_task_rule} 没有该患者科室/病区对应的规则时 → 顺序为 科室 → 病区
|
* (即"按 {@code service_taskdept} 里的通用模块生成")。</li>
|
* <li>按顺序逐个维度尝试,<b>第一个真正落库成功(或已存在)的维度即停止</b> —— 只留一条。</li>
|
* <li>最后统一回写标志位:命中维度 = {@code 1};其余维度 = {@code 2}(跳过),
|
* 且<b>无论命中哪个维度,{@code deptcheck_flag} 一定会被写成非 0</b>
|
* —— 这就是"不再被下一轮扫描重复生成"的保证。</li>
|
* </ol>
|
* 另有一道兜底在 {@code InhospSubtaskWriter#writeInSubTask}:落库前按
|
* {@code patid + inhospid + taskid} 判重,见 {@link VisitTaskCreateApi#existsSubtask}。
|
*
|
* <h3>站点差异(保留)</h3>
|
* {@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<Integer> 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 口径)。
|
* <p>
|
* 规则为 {@code null} → {@code [科室, 病区]}:按 {@code service_taskdept} 里该患者的
|
* 科室/病区"通用模块"生成;这是业务明确要求的兜底口径(此时<b>不进入疾病维度</b>)。
|
* <p>
|
* 有规则 → {@code [首要维度…, 次要维度…, 科室, 病区]}:
|
* 首要优先、次要其后,最后用通用兜底保证"能生成一条就生成一条"。
|
*/
|
private List<Integer> resolveDimensionOrder(VisitTaskRule rule) {
|
boolean xhSite = SITE_XH.equals(active);
|
List<Integer> 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<Integer> 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;
|
}
|
}
|
|
/**
|
* 一轮生成结束后的标志位回写。
|
* <p>
|
* ⚠️ 关键点:扫描键是 {@code pat_med_inhosp.deptcheck_flag = '0'},
|
* 所以<b>不论命中哪个维度,deptcheck_flag 都必须被写成 1 或 2</b>,
|
* 否则下一轮扫描会把这个患者再处理一遍(2026-10 重复生成的直接原因)。
|
* <p>
|
* 没生成时({@code hit == null})刻意<b>只补扫描键</b>:各维度方法已经把更具体的原因
|
* ("该患者所在科室未配置离院长期任务;" / "生成子任务失败 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)
|
// ==========================================================================
|
|
/**
|
* 为单个患者生成科室维度的子任务。
|
* <p>
|
* ⚠️ 2026-10 口径变更:命中多个长期任务时<b>只生成第一条真正落库的任务</b>
|
* (原实现是 {@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<VisitTaskdeptRow> 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)
|
// ==========================================================================
|
|
/**
|
* 为单个患者生成病区维度的子任务。
|
* <p>
|
* ⚠️ 同上:命中多个长期任务时只生成第一条真正落库的(原实现是全生成)。
|
*
|
* @return 生成的子任务数量(0 或 1)
|
*/
|
public int addWardSubTaskForPatient(VisitInhospRow inhosp, VisitArchiveRow archive, String config) {
|
try {
|
String errorreason = null;
|
List<VisitTaskdeptRow> 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)
|
// ==========================================================================
|
|
/**
|
* 为单个患者生成疾病维度的子任务。
|
* <p>
|
* ⚠️ 同上:命中多个长期任务时只生成第一条真正落库的(原实现是全生成)。
|
*
|
* @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<VisitTaskdiagRow> 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)
|
// ==========================================================================
|
|
/**
|
* 再入院处理:新入院记录出现时,把该患者<b>旧的待执行随访</b>停掉。
|
* <p>
|
* 逐行对应原 {@code dealInHospInfo(List<PatMedInhosp> patMedInhospList)}。
|
* <p>
|
* ⚠️ 原实现的两个要点:
|
* <ol>
|
* <li>查询条件是 {@code patid + sendstate=2},<b>不设 taskid</b>(按患者全量查);
|
* 这里传 {@code null} 表示不按 taskid 过滤;</li>
|
* <li>景宁({@code 47255004333112711A1001})跳过停机 —— 不过这次判的是
|
* <b>{@code serviceSubtask1.getOrgid()}</b>(子任务的 orgid),不是入参患者的 orgid。</li>
|
* </ol>
|
*
|
* @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<VisitPendingSubtaskDto> 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} 的随访侧视图)。
|
* <p>
|
* 规则查询本身仍由 {@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();
|
}
|
}
|