package com.ruoyi.inhospvisit.service;
|
|
import com.ruoyi.common.core.service.OtherVisitApi;
|
import com.ruoyi.common.core.service.PublicityApi;
|
import com.ruoyi.common.core.service.PublicityDataPort;
|
import com.ruoyi.common.core.service.dto.InHospPublicityRowDto;
|
import com.ruoyi.common.core.service.dto.VisitTaskRuleDto;
|
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.mapper.VisitArchiveMapper;
|
import com.ruoyi.inhospvisit.mapper.VisitInhospMapper;
|
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.List;
|
|
/**
|
* 出入院随访的<b>两个入口</b> —— 逐行搬自 {@code PatMedInhospServiceImpl} 的
|
* {@code dealOutHospInfo}(L324-463,140 行)与 {@code dealOutHospInfoBySchedule}(L471-614,144 行)。
|
*
|
* <h3>⚠️ 这一步是"切换调用点",必须在同一步完成三件事</h3>
|
* <ol>
|
* <li><b>宣教改走 {@link PublicityApi}</b>:原 L331-409(入院宣教)与 L411-444(药品宣教)
|
* 本次<b>不再内联</b>,改为回调宣教模块。若不同时删掉旧的内联块,
|
* <b>宣教会发两遍</b>(第 5 步就留下的已知中间状态);</li>
|
* <li><b>手术随访改走 {@link OtherVisitApi}</b>:原 L449-451 与 L559-608 那两处;</li>
|
* <li><b>编排层改走本模块的 {@link InhospTaskOrchestrator}</b>:原 {@code addSubTaskByRule} /
|
* {@code dealInHospInfo}。</li>
|
* </ol>
|
*
|
* <h3>与原实现的两处结构差异(都已登记)</h3>
|
* <ol>
|
* <li><b>患者列表的取数方式变了</b>:原实现直接查 {@code pat_med_inhosp};
|
* 搬迁后走本模块自带的 {@link VisitInhospMapper}(SQL 与 smartor 那份逐条等价,
|
* 由 {@code verify_inhospvisit_sql_expansion.py} 把关)。
|
* 注意原实现第一段查的是 {@code deptcheckFlag="0" + inhospstate="1" + fuflag="1"},
|
* 这段既用于宣教也用于 `addSubTaskByRule`。</li>
|
* <li><b>宣教的患者列表由宣教模块自己查</b>:原实现复用同一个 {@code patMedInhosps} 列表;
|
* 搬迁后 {@link PublicityApi#dealInHospPublicity} 内部自己查一份(条件相同)。
|
* 这是<b>故意的</b>:宣教模块不该依赖随访模块的内存列表。
|
* 代价是多一次轻量查询,已在 {@code PublicityServiceImpl} 的注释里说明。</li>
|
* </ol>
|
*/
|
@Slf4j
|
@Service
|
public class InhospVisitTaskService {
|
|
@Value("${spring.profiles.active}")
|
private String active;
|
|
@Autowired
|
private VisitInhospMapper inhospMapper;
|
|
@Autowired
|
private VisitArchiveMapper archiveMapper;
|
|
@Autowired
|
private InhospTaskOrchestrator orchestrator;
|
|
@Autowired
|
private ObjectProvider<PublicityApi> publicityApiProvider;
|
|
@Autowired
|
private ObjectProvider<OtherVisitApi> otherVisitApiProvider;
|
|
@Autowired
|
private ObjectProvider<PublicityDataPort> dataPortProvider;
|
|
// ==========================================================================
|
// 入口 1:出院随访主扫描(原 L324-463)
|
// ==========================================================================
|
|
/**
|
* 出院随访主扫描。顺序<b>逐条对应</b>原实现:
|
* <ol>
|
* <li>取"科室维度未处理"的在院患者(原 L325-329);</li>
|
* <li>入院宣教 → 回调 {@link PublicityApi#dealInHospPublicity}(原 L331-409);</li>
|
* <li>药品宣教(仅 sltd)→ 回调 {@link PublicityApi#dealMedicalPublicity}(原 L411-444);</li>
|
* <li>基于规则的出院随访 → {@link #addSubTaskByRule}(原 L448);</li>
|
* <li>手术随访(非 xh)→ 回调 {@link OtherVisitApi#addOperationSubTask}(原 L449-451);</li>
|
* <li>再入院处理 → {@link #dealInHospInfo}(原 L454-461)。</li>
|
* </ol>
|
*
|
* @param config {@code sys_config: visit.early.day}
|
* @return 原实现固定返回 1
|
*/
|
public int dealOutHospInfo(String config) {
|
// 原 L325-329:科室维度未处理的在院患者
|
VisitInhospRow pmks = new VisitInhospRow();
|
pmks.setDeptcheckFlag("0");
|
pmks.setInhospstate("1");
|
pmks.setFuflag("1");
|
List<VisitInhospRow> patMedInhosps = inhospMapper.selectForScan(pmks);
|
|
// 原 L331-409 + L411-444:两段宣教改为回调宣教模块
|
// ⚠️ 这里不再内联实现 —— 否则会与 ruoyi-publicity 里的实现"发两遍"
|
dealInHospPublicityAndMedical(config);
|
|
// 原 L445-451:基于规则的出院随访 + 手术随访
|
addSubTaskByRule(config, patMedInhosps);
|
if (!"xh".equals(active)) {
|
// 原 L450:addOperationSubTask(config)
|
callOperationVisit(config);
|
}
|
|
// 原 L454-461:再入院处理
|
VisitInhospRow inHospQuery = new VisitInhospRow();
|
inHospQuery.setDeptcheckFlag("0");
|
inHospQuery.setInhospstate("0");
|
List<VisitInhospRow> inHospList = inhospMapper.selectForScan(inHospQuery);
|
if (CollectionUtils.isNotEmpty(inHospList)) {
|
dealInHospInfo(inHospList);
|
}
|
return 1;
|
}
|
|
/**
|
* 两段宣教回调(原 L331-409 入院宣教 + L411-444 药品宣教)。
|
* <p>
|
* 顺序与原实现一致:<b>先入院宣教,再药品宣教</b>。
|
* 取不到宣教模块时按"没有宣教要做"处理并打 WARN(fail-open)。
|
*/
|
private void dealInHospPublicityAndMedical(String config) {
|
PublicityApi api = publicityApi();
|
if (api == null) {
|
log.warn("【出入院随访】PublicityApi 不可用(宣教模块未装配),跳过入院宣教与药品宣教。");
|
return;
|
}
|
try {
|
// 原 L331-409
|
api.dealInHospPublicity(config);
|
} catch (Exception e) {
|
// 宣教失败不能拖垮随访主流程
|
log.warn("【出入院随访】入院宣教回调失败,已忽略:{}", e.toString());
|
}
|
try {
|
// 原 L411-444
|
api.dealMedicalPublicity(config);
|
} catch (Exception e) {
|
log.warn("【出入院随访】药品宣教回调失败,已忽略:{}", e.toString());
|
}
|
}
|
|
/**
|
* 手术随访回调(原 L449-451 与 L559-608 的 {@code addOperationSubTask})。
|
*/
|
private void callOperationVisit(String config) {
|
OtherVisitApi api = otherVisitApi();
|
if (api == null) {
|
log.warn("【出入院随访】OtherVisitApi 不可用(手术随访模块未装配),跳过手术随访。");
|
return;
|
}
|
try {
|
api.addOperationSubTask(config);
|
} catch (Exception e) {
|
log.warn("【出入院随访】手术随访回调失败,已忽略:{}", e.toString());
|
}
|
}
|
|
/**
|
* 逐患者做规则编排(原 L448 的 {@code addSubTaskByRule(config, patMedInhosps)})。
|
* <p>
|
* 原 {@code addSubTaskByRule}(L1691) 是个"遍历患者"的外层方法,
|
* 本次把"遍历"放在这里,把"单患者规则判定"放进
|
* {@link InhospTaskOrchestrator#addSubTaskByRule}。
|
* 这样切分之后,两边的方法名相同但职责不同,已在各自注释里写明。
|
*/
|
private void addSubTaskByRule(String config, List<VisitInhospRow> patMedInhosps) {
|
if (CollectionUtils.isEmpty(patMedInhosps)) {
|
return;
|
}
|
for (VisitInhospRow row : patMedInhosps) {
|
// 原 L1692-1700:获取患者基本信息,为空则写 deptcheck=2 + reason
|
VisitArchiveRow archive = archiveMapper.selectByPatid(row.getPatid());
|
if (archive == null) {
|
com.ruoyi.common.core.service.dto.VisitInhospUpdateDto u =
|
new com.ruoyi.common.core.service.dto.VisitInhospUpdateDto();
|
u.setInhospid(row.getInhospid());
|
u.setDeptcheckFlag("2");
|
u.setLongTaskReason("患者基本信息为空");
|
updateInhosp(u);
|
continue;
|
}
|
|
try {
|
// 原 L1703-1707:按离院科室/病区查规则
|
VisitTaskRuleDto rule = findTaskRule(row);
|
// 原 L1709 起:交给编排器按规则执行
|
orchestrator.addSubTaskByRule(row, archive, toOrchestratorRule(rule), config);
|
} catch (Exception e) {
|
// 原 L1839-1845(在编排器里也有,但外层遍历这一层原实现没有 try/catch;
|
// 这里加一层是为了"单患者失败不影响其它患者",属健壮性改进,已登记)
|
log.error("【基于规则】患者{}外层处理异常:{}", row.getInhospid(), e.getMessage());
|
com.ruoyi.common.core.service.dto.VisitInhospUpdateDto u =
|
new com.ruoyi.common.core.service.dto.VisitInhospUpdateDto();
|
u.setInhospid(row.getInhospid());
|
u.setDeptcheckFlag("2");
|
u.setLongTaskReason("基于规则处理异常");
|
updateInhosp(u);
|
}
|
}
|
}
|
|
/**
|
* 再入院处理(原 L454-461 的 {@code dealInHospInfo(patMedInhospList)})。
|
*/
|
private void dealInHospInfo(List<VisitInhospRow> inHospList) {
|
for (VisitInhospRow row : inHospList) {
|
orchestrator.dealInHospInfo(row);
|
}
|
}
|
|
// ==========================================================================
|
// 入口 2:任务组首轮纳入(原 L471-614)
|
// ==========================================================================
|
|
/**
|
* 任务组(appltype=5)首轮纳入 —— <b>本次不搬,仍留在 smartor</b>。
|
*
|
* <h3>为什么不搬(一个必须说明的范围决定)</h3>
|
* 原 {@code dealOutHospInfoBySchedule}(L471-614) 由两段构成:
|
* <ul>
|
* <li><b>非手术分支</b>(L518-558):按维度过滤扫患者 → {@code boxedServiceSubtask} →
|
* {@code scheduleSubtaskBuilder.buildSubtasksForPatient}(一次生成 N 条)→ 置 flag;</li>
|
* <li><b>手术分支</b>(L559-608):{@code selectOperationItemListMaxLevel} +
|
* {@code boxedServiceSubtaskForOp} → 同一 builder。</li>
|
* </ul>
|
* 两段都依赖 {@code ScheduleSubtaskBuilder} —— 它同时服务"HIS 自动纳入"与
|
* "前端手动勾选患者"两条路径(见其类注释),**不属于本次范围**。
|
* 而手术分支按版图属第二批的 {@code ruoyi-othervisit}。
|
* <p>
|
* 所以本次把<b>整个方法留在 smartor</b>(行为一行不变),
|
* 等第二批做 {@code ruoyi-othervisit} 时连同任务组链路一起收走。
|
* <p>
|
* 这样做的好处:本轮不需要为 {@code ScheduleSubtaskBuilder} 再造一个端口,
|
* 也不会因为"搬一半留一半"而在两处生成任务组的子任务。
|
*
|
* @param config {@code sys_config: visit.early.day}
|
* @return 原实现固定返回 1
|
*/
|
public int dealOutHospInfoBySchedule(String config) {
|
log.warn("【出入院随访】dealOutHospInfoBySchedule 本次未搬迁(含手术分支与任务组 builder,"
|
+ "属第二批 ruoyi-othervisit 范围),仍由 smartor 的实现承接。config={}", config);
|
return 1;
|
}
|
|
// ==========================================================================
|
// 辅助
|
// ==========================================================================
|
|
/** 原 L1703-1707:按离院科室/病区查规则(取不到端口时返回 null = 未配置规则 → 全部生成) */
|
private VisitTaskRuleDto findTaskRule(VisitInhospRow row) {
|
PublicityDataPort port = dataPort();
|
if (port == null) {
|
log.warn("【出入院随访】PublicityDataPort 不可用,规则按「未配置」处理(全部生成)。inhospid={}",
|
row.getInhospid());
|
return null;
|
}
|
try {
|
return port.findTaskRule(row.getLeaveldeptcode(), row.getLeavehospitaldistrictcode());
|
} catch (Exception e) {
|
log.warn("【出入院随访】查任务规则失败,按「未配置」处理:{}", e.toString());
|
return null;
|
}
|
}
|
|
/** 把端口的规则 DTO 适配成编排器内嵌的规则视图(字段一一对应,含拼错的 {@code wradRule}) */
|
private InhospTaskOrchestrator.VisitTaskRule toOrchestratorRule(VisitTaskRuleDto dto) {
|
if (dto == null) {
|
return null;
|
}
|
InhospTaskOrchestrator.VisitTaskRule r = new InhospTaskOrchestrator.VisitTaskRule();
|
r.setDeptRule(dto.getDeptRule());
|
r.setWradRule(dto.getWradRule());
|
r.setDiagRule(dto.getDiagRule());
|
return r;
|
}
|
|
private void updateInhosp(com.ruoyi.common.core.service.dto.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 PublicityApi publicityApi() {
|
if (publicityApiProvider == null) {
|
return null;
|
}
|
try {
|
return publicityApiProvider.stream().findFirst().orElse(null);
|
} catch (Exception e) {
|
log.warn("【出入院随访】获取 PublicityApi 失败:{}", e.toString());
|
return null;
|
}
|
}
|
|
private OtherVisitApi otherVisitApi() {
|
if (otherVisitApiProvider == null) {
|
return null;
|
}
|
try {
|
return otherVisitApiProvider.stream().findFirst().orElse(null);
|
} catch (Exception e) {
|
log.warn("【出入院随访】获取 OtherVisitApi 失败:{}", e.toString());
|
return null;
|
}
|
}
|
}
|