package com.smartor.service.impl.bridge;
|
|
import com.ruoyi.common.core.service.PublicityDataPort;
|
import com.ruoyi.common.core.service.dto.InHospPublicityRowDto;
|
import com.ruoyi.common.core.service.dto.PublicityTaskMatchDto;
|
import com.ruoyi.common.core.service.dto.VisitTaskRuleDto;
|
import com.ruoyi.inhospvisit.domain.PatMedInhosp;
|
import com.ruoyi.task.domain.ServiceTaskRule;
|
import com.ruoyi.patarchive.mapper.PatArchiveMapper;
|
import com.ruoyi.inhospvisit.api.InhospVisitFacade;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.collections4.CollectionUtils;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Component;
|
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Objects;
|
import java.util.Set;
|
|
import com.ruoyi.common.core.domain.entity.PatArchive;
|
import com.ruoyi.task.domain.ServiceTaskdept;
|
import com.ruoyi.task.domain.ServiceTaskmedical;
|
import com.ruoyi.task.mapper.ServiceTaskdeptMapper;
|
import com.ruoyi.task.mapper.ServiceTaskmedicalMapper;
|
|
/**
|
* 【过渡实现】{@link PublicityDataPort} 在 smartor 侧的桥接(宣教模块的数据来源)。
|
*
|
* <h3>为什么需要它</h3>
|
* {@code ruoyi-publicity} 要读 {@code service_taskdept} / {@code service_taskmedical} /
|
* {@code pat_med_inhosp} / {@code pat_archive},但这些表和 Mapper 全在 smartor;
|
* 而 smartor 又要依赖 publicity(它负责触发宣教)→ 直接依赖会循环。
|
* 所以接口下沉到 ruoyi-common,实现在这里。
|
*
|
* <h3>⚠️ 本类必须保持"纯搬运"</h3>
|
* 只做<b>字段映射</b>与<b>转发</b>,不得加入去重、排序、默认值补齐等新逻辑 ——
|
* 那些语义在 {@code ruoyi-publicity} 的 {@code PublicityService} 里(逐行照抄原实现)。
|
*
|
* <h3>逐方法来源(迁移前 {@code PatMedInhospServiceImpl#dealOutHospInfo},@ b25c3b51)</h3>
|
* <table border="1">
|
* <tr><th>本类方法</th><th>原实现位置</th></tr>
|
* <tr><td>{@link #findInHospForPublicity}</td><td>L325-329(科室)、L376-379(病区)</td></tr>
|
* <tr><td>{@link #findDeptPublicityTaskIds}</td><td>L347-353(deptType=1)、L390-396(deptType=2)</td></tr>
|
* <tr><td>{@link #findMedicalPublicityTaskIds}</td><td>L424-434</td></tr>
|
* <tr><td>{@link #existsPatArchive}</td><td>L338-344、L382-388</td></tr>
|
* <tr><td>{@link #updatePublicityFlag}</td><td>L340-342 / L357-359 / L367-370(科室)、L384-386 / L400-402(病区)</td></tr>
|
* </table>
|
*
|
* <h3>将来动作</h3>
|
* 若将来宣教与随访都收进同一个"随访平台模块",或 smartor 不再依赖 publicity,
|
* 本类可整体删除并把实现挪进新模块 —— 接口不变,调用方零改动。
|
*
|
* @see PublicityDataPort
|
*/
|
@Slf4j
|
@Component
|
public class PublicityDataPortBridge implements PublicityDataPort {
|
|
/**
|
* 允许的 check_flag 列名(白名单,拼进 SQL 的是常量,不做字符串拼接,避免注入)
|
*/
|
private static final Set<String> ALLOWED_CHECK_FLAG_COLUMNS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("in_deptcheck_flag", "in_wardcheck_flag")));
|
|
@Autowired
|
private InhospVisitFacade inhospVisitFacade;
|
|
@Autowired
|
private PatArchiveMapper patArchiveMapper;
|
|
@Autowired
|
private ServiceTaskdeptMapper serviceTaskdeptMapper;
|
|
@Autowired
|
private ServiceTaskmedicalMapper serviceTaskmedicalMapper;
|
|
/**
|
* 任务执行规则的只读查询(原 {@code addSubTaskByRule} L1707 用)。
|
* <p>
|
* 虽然本端口名义上服务宣教,但 {@link PublicityDataPort#findTaskRule} 服务的是
|
* 出院随访的规则编排 —— 见接口上的说明。
|
*/
|
@Autowired
|
private com.ruoyi.task.mapper.ServiceTaskRuleMapper serviceTaskRuleMapper;
|
|
/**
|
* 扫在院患者。
|
* <p>
|
* 原实现两段查询的入参:
|
* <pre>
|
* // 科室维度(L325-329)
|
* PatMedInhosp pmks = new PatMedInhosp();
|
* pmks.setDeptcheckFlag("0"); pmks.setInhospstate("1"); pmks.setFuflag("1");
|
*
|
* // 病区维度(L376-379)
|
* pmks.setInDeptcheckFlag(null); pmks.setInWardcheckFlag("0"); pmks.setDeptcheckFlag(null);
|
* </pre>
|
* ⚠️ 注意病区维度那段是<b>复用同一个 pmks 对象</b>并改字段,所以
|
* {@code inhospstate="1"} 与 {@code fuflag="1"} 是一直留着的。
|
* 这里用白名单参数区分两个维度,并保持"两段独立查询"。
|
*/
|
@Override
|
public List<InHospPublicityRowDto> findInHospForPublicity(String checkFlagColumn) {
|
if (!ALLOWED_CHECK_FLAG_COLUMNS.contains(checkFlagColumn)) {
|
log.warn("【宣教·过渡桥】findInHospForPublicity 收到非法列名「{}」,只允许 {},返回空列表", checkFlagColumn, ALLOWED_CHECK_FLAG_COLUMNS);
|
return Collections.emptyList();
|
}
|
PatMedInhosp query = new PatMedInhosp();
|
if ("in_wardcheck_flag".equals(checkFlagColumn)) {
|
// 病区维度:只按 in_wardcheck_flag 过滤,另两个维度置 null(与原实现一致)
|
query.setInDeptcheckFlag(null);
|
query.setInWardcheckFlag("0");
|
query.setDeptcheckFlag(null);
|
} else {
|
// 科室维度
|
query.setDeptcheckFlag("0");
|
}
|
query.setInhospstate("1");
|
query.setFuflag("1");
|
|
List<PatMedInhosp> rows = inhospVisitFacade.selectPatMedInhospList(query);
|
if (CollectionUtils.isEmpty(rows)) {
|
return Collections.emptyList();
|
}
|
List<InHospPublicityRowDto> out = new ArrayList<>(rows.size());
|
for (PatMedInhosp r : rows) {
|
out.add(toRow(r));
|
}
|
return out;
|
}
|
|
@Override
|
public List<PublicityTaskMatchDto> findDeptPublicityTaskIds(String deptCode, String deptType, String orgid, Long inhospid) {
|
ServiceTaskdept query = new ServiceTaskdept();
|
query.setLongtask(1L);
|
query.setDeptCode(deptCode);
|
query.setDeptType(deptType);
|
query.setServiceType("17");
|
query.setOrgid(orgid);
|
List<ServiceTaskdept> tasks = serviceTaskdeptMapper.selectServiceTaskdeptList(query);
|
if (CollectionUtils.isEmpty(tasks)) {
|
return Collections.emptyList();
|
}
|
List<PublicityTaskMatchDto> out = new ArrayList<>(tasks.size());
|
for (ServiceTaskdept t : tasks) {
|
PublicityTaskMatchDto dto = new PublicityTaskMatchDto();
|
dto.setTaskId(t.getTaskId());
|
dto.setInhospid(inhospid);
|
out.add(dto);
|
}
|
return out;
|
}
|
|
/**
|
* 按药品编码 + 名称 + 公司查长期宣教任务(原 L424-434)。
|
* <p>
|
* ⚠️ <b>三个字段都照原样透传,绝不做 null/空串归一</b>:
|
* {@code ServiceTaskmedicalMapper.xml} 的条件是
|
* {@code <if test="medicalName != null and medicalName != ''">} 与
|
* {@code <if test="medicalCompany != null and medicalCompany != ''">} ——
|
* <b>空串与 null 都会跳过该条件</b>,但语义来源不同(越界分支传空串、正常分支传 null),
|
* 这里保留原样才能保证"哪些条件生效"与迁移前完全一致。
|
*/
|
@Override
|
public List<PublicityTaskMatchDto> findMedicalPublicityTaskIds(String medicalCode, String medicalName, String medicalCompany, Long inhospid) {
|
ServiceTaskmedical query = new ServiceTaskmedical();
|
query.setMedicalCode(medicalCode);
|
query.setMedicalName(medicalName);
|
query.setMedicalCompany(medicalCompany);
|
List<ServiceTaskmedical> tasks = serviceTaskmedicalMapper.selectServiceTaskmedicalList(query);
|
if (CollectionUtils.isEmpty(tasks)) {
|
return Collections.emptyList();
|
}
|
List<PublicityTaskMatchDto> out = new ArrayList<>(tasks.size());
|
for (ServiceTaskmedical t : tasks) {
|
PublicityTaskMatchDto dto = new PublicityTaskMatchDto();
|
dto.setTaskId(t.getTaskId());
|
dto.setInhospid(inhospid);
|
out.add(dto);
|
}
|
return out;
|
}
|
|
/**
|
* 患者档案是否存在。
|
* <p>
|
* ⚠️ 原实现取的是<b>完整对象</b>然后判 null(L338-344 / L382-388),
|
* 这里只判存在性 —— 宣教层确实只用到 null 判断
|
* (真正的患者信息填充发生在 inhospvisit 组装子任务时)。
|
* 这是<b>有意的等价简化</b>:把"取完整对象"降级为"取 existence",
|
* 不影响任何分支走向,但省掉一次大字段查询。
|
* <p>
|
* 若将来发现 {@code selectPatArchiveByPatid} 内部还有别的副作用(例如缓存写入),
|
* 需要在 {@code verify_inhospvisit_semantics.py} 里补一条断言。
|
*/
|
@Override
|
public boolean existsPatArchive(Long patid) {
|
if (patid == null) {
|
return false;
|
}
|
PatArchive archive;
|
try {
|
archive = patArchiveMapper.selectPatArchiveByPatid(patid);
|
} catch (Exception e) {
|
log.warn("【宣教·过渡桥】查患者档案失败,按「不存在」处理。patid={},原因={}", patid, e.toString());
|
return false;
|
}
|
return Objects.nonNull(archive);
|
}
|
|
/**
|
* 回写宣教标志位 + 按需写 long_task_reason。
|
* <p>
|
* ⚠️ 语义要点(照抄原实现,别"顺手修"):
|
* <ol>
|
* <li>{@code appendMode=true} 时,原实现在写之前会<b>先按 inhospid 重查一次记录</b>
|
* 拿 {@code long_task_reason} 再拼接,这里也照做(避免用调用方手里可能过期的值);</li>
|
* <li>拼接方式是 {@code 旧值 + 新文案},<b>不判断旧值是否为 null</b>
|
* —— 旧值为 null 时会真的拼出 {@code "null该患者所在病区..."},这是原行为,保留;</li>
|
* <li>{@code appendMode=false} 时直接覆盖(对应「患者基本信息为空」「人员...处理出异常了」两个分支);</li>
|
* <li>{@code reason=null} 时只写 flag、不动 long_task_reason。</li>
|
* </ol>
|
*/
|
@Override
|
public boolean updatePublicityFlag(Long inhospid, String flagColumn, String flagValue, String reason, boolean appendMode) {
|
if (!ALLOWED_CHECK_FLAG_COLUMNS.contains(flagColumn)) {
|
log.warn("【宣教·过渡桥】updatePublicityFlag 收到非法列名「{}」,只允许 {},放弃更新。inhospid={}", flagColumn, ALLOWED_CHECK_FLAG_COLUMNS, inhospid);
|
return false;
|
}
|
if (inhospid == null) {
|
log.warn("【宣教·过渡桥】updatePublicityFlag 缺少 inhospid,放弃更新。column={}", flagColumn);
|
return false;
|
}
|
|
PatMedInhosp update = new PatMedInhosp();
|
update.setInhospid(inhospid);
|
if ("in_deptcheck_flag".equals(flagColumn)) {
|
update.setInDeptcheckFlag(flagValue);
|
} else {
|
update.setInWardcheckFlag(flagValue);
|
}
|
|
if (reason != null) {
|
if (appendMode) {
|
// 原实现:先重查一次拿 long_task_reason,再拼接后写回(不判 null)
|
PatMedInhosp existing = inhospVisitFacade.selectPatMedInhospByInhospid(inhospid);
|
String oldReason = existing == null ? null : existing.getLongTaskReason();
|
update.setLongTaskReason(oldReason + reason);
|
} else {
|
update.setLongTaskReason(reason);
|
}
|
}
|
|
return inhospVisitFacade.updatePatMedInhosp(update) > 0;
|
}
|
|
/**
|
* 按字段更新(可一次写多个 flag)—— 供出院随访编排层的 3 个 {@code *ForPatient} 使用。
|
* <p>
|
* 语义与原实现完全一致:原代码是"在对象上 set 几个字段,然后
|
* {@code inhospVisitFacade.updatePatMedInhosp(obj)}",
|
* 而 {@code updatePatMedInhosp} 的 XML 对每个字段各自 {@code <if test="xxx != null">},
|
* 所以<b>只有非 null 字段生效</b>。这里逐字段搬运,null 一律不设。
|
*/
|
@Override
|
public boolean updateVisitInhosp(com.ruoyi.common.core.service.dto.VisitInhospUpdateDto u) {
|
if (u == null || u.getInhospid() == null) {
|
log.warn("【随访·过渡桥】updateVisitInhosp 缺少 inhospid,放弃更新。u={}", u);
|
return false;
|
}
|
PatMedInhosp update = new PatMedInhosp();
|
update.setInhospid(u.getInhospid());
|
update.setDeptcheckFlag(u.getDeptcheckFlag());
|
update.setWardcheckFlag(u.getWardcheckFlag());
|
update.setDiagcheckFlag(u.getDiagcheckFlag());
|
update.setInDeptcheckFlag(u.getInDeptcheckFlag());
|
update.setInWardcheckFlag(u.getInWardcheckFlag());
|
update.setScheduleCheckFlag(u.getScheduleCheckFlag());
|
update.setMedcheckFlag(u.getMedcheckFlag());
|
update.setLongTaskReason(u.getLongTaskReason());
|
update.setRemark(u.getRemark());
|
return inhospVisitFacade.updatePatMedInhosp(update) > 0;
|
}
|
|
// ==========================================================================
|
// 字段映射
|
// ==========================================================================
|
|
/**
|
* 按离院科室/病区查任务执行规则(原 {@code addSubTaskByRule} L1704-1707)。
|
* <p>
|
* ⚠️ 该查询最多返回一条 —— 原实现直接取单个对象,这里也取第一条。
|
*/
|
@Override
|
public VisitTaskRuleDto findTaskRule(String leaveldeptcode, String leavehospitaldistrictcode) {
|
ServiceTaskRule ruleQuery = new ServiceTaskRule();
|
ruleQuery.setDeptcode(leaveldeptcode);
|
ruleQuery.setWardcode(leavehospitaldistrictcode);
|
ServiceTaskRule rule = serviceTaskRuleMapper.selectServiceTaskRuleByDeptOrWard(ruleQuery);
|
if (rule == null) {
|
return null;
|
}
|
VisitTaskRuleDto dto = new VisitTaskRuleDto();
|
dto.setId(rule.getId());
|
dto.setDeptRule(rule.getDeptRule());
|
dto.setWradRule(rule.getWradRule());
|
dto.setDiagRule(rule.getDiagRule());
|
dto.setDeptcode(rule.getDeptcode());
|
dto.setWardcode(rule.getWardcode());
|
dto.setOrgid(rule.getOrgid());
|
return dto;
|
}
|
|
private InHospPublicityRowDto toRow(PatMedInhosp r) {
|
InHospPublicityRowDto dto = new InHospPublicityRowDto();
|
dto.setInhospid(r.getInhospid());
|
dto.setPatid(r.getPatid());
|
dto.setOrgid(r.getOrgid());
|
dto.setDeptcode(r.getDeptcode());
|
dto.setHospitaldistrictcode(r.getHospitaldistrictcode());
|
dto.setLongTaskReason(r.getLongTaskReason());
|
dto.setMedicalCode(r.getMedicalCode());
|
dto.setMedicalName(r.getMedicalName());
|
dto.setMedicalCompany(r.getMedicalCompany());
|
return dto;
|
}
|
}
|