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})。 * *
| 本类方法 | 原实现 | 行数 |
|---|---|---|
| {@link #addSubTaskByRule} | {@code addSubTaskByRule} L1691-1847 | 157 |
| {@link #addDeptSubTaskForPatient} | L1854-1887 | 34 |
| {@link #addWardSubTaskForPatient} | L1894-1935 | 42 |
| {@link #addIcd10SubTaskForPatient} | L1942-2001 | 60 |
| {@link #dealInHospInfo} | L1321-1359 | 39 |
| 位置 | 原写法 | 实际写入的列 |
|---|---|---|
| 科室·无模板(L1866-1872) | *重读对象 {@code setDeptcheckFlag("2")},另两个 flag 由重读对象原样带回 | *deptcheck + wardcheck + diagcheck + reason |
| 科室·catch(L1879-1885) | *在新鲜对象上 set | *deptcheck + reason |
| 病区·失败(L1914-1920) | *重读对象 {@code setWardcheckFlag("2")},另两个 flag 原样带回 | *wardcheck + deptcheck + diagcheck + reason |
| 病区·catch(L1927-1933) | *在新鲜对象上 set | *wardcheck + reason |
| 疾病·两处(L1967-1971 / L1985-1991) | *重读对象 {@code setDiagcheckFlag("2")} 并覆写 reason | *diagcheck + reason(注意:没有带回其它两个 flag) |
| 疾病·catch(L1993-1998) | *在新鲜对象上 set | *diagcheck + reason |
* 现口径(业务确认): *
* 规则为 {@code null} → {@code [科室, 病区]}:按 {@code service_taskdept} 里该患者的 * 科室/病区"通用模块"生成;这是业务明确要求的兜底口径(此时不进入疾病维度)。 *
* 有规则 → {@code [首要维度…, 次要维度…, 科室, 病区]}:
* 首要优先、次要其后,最后用通用兜底保证"能生成一条就生成一条"。
*/
private List
* ⚠️ 关键点:扫描键是 {@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
* ⚠️ 同上:命中多个长期任务时只生成第一条真正落库的(原实现是全生成)。
*
* @return 生成的子任务数量(0 或 1)
*/
public int addWardSubTaskForPatient(VisitInhospRow inhosp, VisitArchiveRow archive, String config) {
try {
String errorreason = null;
List
* ⚠️ 同上:命中多个长期任务时只生成第一条真正落库的(原实现是全生成)。
*
* @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
* 逐行对应原 {@code dealInHospInfo(List
* ⚠️ 原实现的两个要点:
*
* 规则查询本身仍由 {@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();
}
}
*
*
* @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