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 侧的桥接(宣教模块的数据来源)。 * *

为什么需要它

* {@code ruoyi-publicity} 要读 {@code service_taskdept} / {@code service_taskmedical} / * {@code pat_med_inhosp} / {@code pat_archive},但这些表和 Mapper 全在 smartor; * 而 smartor 又要依赖 publicity(它负责触发宣教)→ 直接依赖会循环。 * 所以接口下沉到 ruoyi-common,实现在这里。 * *

⚠️ 本类必须保持"纯搬运"

* 只做字段映射与转发,不得加入去重、排序、默认值补齐等新逻辑 —— * 那些语义在 {@code ruoyi-publicity} 的 {@code PublicityService} 里(逐行照抄原实现)。 * *

逐方法来源(迁移前 {@code PatMedInhospServiceImpl#dealOutHospInfo},@ b25c3b51)

* * * * * * * *
本类方法原实现位置
{@link #findInHospForPublicity}L325-329(科室)、L376-379(病区)
{@link #findDeptPublicityTaskIds}L347-353(deptType=1)、L390-396(deptType=2)
{@link #findMedicalPublicityTaskIds}L424-434
{@link #existsPatArchive}L338-344、L382-388
{@link #updatePublicityFlag}L340-342 / L357-359 / L367-370(科室)、L384-386 / L400-402(病区)
* *

将来动作

* 若将来宣教与随访都收进同一个"随访平台模块",或 smartor 不再依赖 publicity, * 本类可整体删除并把实现挪进新模块 —— 接口不变,调用方零改动。 * * @see PublicityDataPort */ @Slf4j @Component public class PublicityDataPortBridge implements PublicityDataPort { /** * 允许的 check_flag 列名(白名单,拼进 SQL 的是常量,不做字符串拼接,避免注入) */ private static final Set 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 用)。 *

* 虽然本端口名义上服务宣教,但 {@link PublicityDataPort#findTaskRule} 服务的是 * 出院随访的规则编排 —— 见接口上的说明。 */ @Autowired private com.ruoyi.task.mapper.ServiceTaskRuleMapper serviceTaskRuleMapper; /** * 扫在院患者。 *

* 原实现两段查询的入参: *

     * // 科室维度(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);
     * 
* ⚠️ 注意病区维度那段是复用同一个 pmks 对象并改字段,所以 * {@code inhospstate="1"} 与 {@code fuflag="1"} 是一直留着的。 * 这里用白名单参数区分两个维度,并保持"两段独立查询"。 */ @Override public List 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 rows = inhospVisitFacade.selectPatMedInhospList(query); if (CollectionUtils.isEmpty(rows)) { return Collections.emptyList(); } List out = new ArrayList<>(rows.size()); for (PatMedInhosp r : rows) { out.add(toRow(r)); } return out; } @Override public List 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 tasks = serviceTaskdeptMapper.selectServiceTaskdeptList(query); if (CollectionUtils.isEmpty(tasks)) { return Collections.emptyList(); } List 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)。 *

* ⚠️ 三个字段都照原样透传,绝不做 null/空串归一: * {@code ServiceTaskmedicalMapper.xml} 的条件是 * {@code } 与 * {@code } —— * 空串与 null 都会跳过该条件,但语义来源不同(越界分支传空串、正常分支传 null), * 这里保留原样才能保证"哪些条件生效"与迁移前完全一致。 */ @Override public List findMedicalPublicityTaskIds(String medicalCode, String medicalName, String medicalCompany, Long inhospid) { ServiceTaskmedical query = new ServiceTaskmedical(); query.setMedicalCode(medicalCode); query.setMedicalName(medicalName); query.setMedicalCompany(medicalCompany); List tasks = serviceTaskmedicalMapper.selectServiceTaskmedicalList(query); if (CollectionUtils.isEmpty(tasks)) { return Collections.emptyList(); } List 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; } /** * 患者档案是否存在。 *

* ⚠️ 原实现取的是完整对象然后判 null(L338-344 / L382-388), * 这里只判存在性 —— 宣教层确实只用到 null 判断 * (真正的患者信息填充发生在 inhospvisit 组装子任务时)。 * 这是有意的等价简化:把"取完整对象"降级为"取 existence", * 不影响任何分支走向,但省掉一次大字段查询。 *

* 若将来发现 {@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。 *

* ⚠️ 语义要点(照抄原实现,别"顺手修"): *

    *
  1. {@code appendMode=true} 时,原实现在写之前会先按 inhospid 重查一次记录 * 拿 {@code long_task_reason} 再拼接,这里也照做(避免用调用方手里可能过期的值);
  2. *
  3. 拼接方式是 {@code 旧值 + 新文案},不判断旧值是否为 null * —— 旧值为 null 时会真的拼出 {@code "null该患者所在病区..."},这是原行为,保留;
  4. *
  5. {@code appendMode=false} 时直接覆盖(对应「患者基本信息为空」「人员...处理出异常了」两个分支);
  6. *
  7. {@code reason=null} 时只写 flag、不动 long_task_reason。
  8. *
*/ @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} 使用。 *

* 语义与原实现完全一致:原代码是"在对象上 set 几个字段,然后 * {@code inhospVisitFacade.updatePatMedInhosp(obj)}", * 而 {@code updatePatMedInhosp} 的 XML 对每个字段各自 {@code }, * 所以只有非 null 字段生效。这里逐字段搬运,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)。 *

* ⚠️ 该查询最多返回一条 —— 原实现直接取单个对象,这里也取第一条。 */ @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; } }