package com.ruoyi.publicity.service.impl;
|
|
import com.ruoyi.common.core.service.IConfigService;
|
import com.ruoyi.common.core.service.PublicityDataPort;
|
import com.ruoyi.common.core.service.PublicitySubtaskApi;
|
import com.ruoyi.common.core.service.dto.InHospPublicityRowDto;
|
import com.ruoyi.common.core.service.dto.PublicityTaskMatchDto;
|
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.publicity.service.PublicityService;
|
import lombok.extern.slf4j.Slf4j;
|
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.List;
|
|
/**
|
* 宣教业务实现 —— 从 {@code PatMedInhospServiceImpl#dealOutHospInfo} 摘出的两段,<b>逐行照抄</b>。
|
*
|
* <h3>来源对照(@ b25c3b51,迁移前)</h3>
|
* <table border="1">
|
* <tr><th>本类方法</th><th>原实现位置</th><th>说明</th></tr>
|
* <tr><td>{@link #dealInHospPublicity(String)}</td><td>L331-409</td>
|
* <td>入院宣教:科室维度 L335-372(type=4)、病区维度 L375-408(type=5)</td></tr>
|
* <tr><td>{@link #dealMedicalPublicity(String)}</td><td>L411-444</td>
|
* <td>药品宣教(仅 sltd,type=0)</td></tr>
|
* </table>
|
*
|
* <h3>⚠️ 与增量口径有关的两个说明(都<b>不影响结果</b>,但必须写下来)</h3>
|
* <ol>
|
* <li><b>患者列表被查了两次而不是一次。</b>
|
* 原实现只查一次 {@code pmks}(L325-329)并复用于三个分支;
|
* 拆成两个方法后各查一次。两次查询的<b>条件完全相同</b>
|
* ({@code deptcheck_flag='0'} + {@code inhospstate='1'} + {@code fuflag='1'}),
|
* 且三个分支都只是"读该列表 + 匹配任务",不会修改 {@code deptcheck_flag},
|
* 所以列表内容一致、结果等价。代价是多一次轻量查询。</li>
|
* <li><b>"科室/病区未配宣教任务"分支里对标志位的回写,原样保留了一个历史笔误</b>:
|
* 病区维度(原 L400)写的是 {@code inDeptcheckFlag} 而<b>不是</b> {@code inWardcheckFlag}。
|
* 这意味着病区维度的失败标记落在科室那一列上。
|
* <b>不修</b> —— 修了会让"入院宣教"的重复扫描行为发生变化。</li>
|
* </ol>
|
*
|
* <h3>依赖全部可降级</h3>
|
* {@link PublicityDataPort}(smartor 提供的过渡实现)与 {@link PublicitySubtaskApi}(inhospvisit 门面)
|
* 都用 {@code ObjectProvider} 惰性获取;取不到就<b>按"没有宣教要做"处理并打 WARN</b>,
|
* 绝不让出入院随访主流程抛异常。
|
*/
|
@Slf4j
|
@Service
|
public class PublicityServiceImpl implements PublicityService {
|
|
/** 入院宣教自身的业务开关({@code sys_config}),只有等于 "2" 才执行 */
|
private static final String HELIBRARY_SWITCH_KEY = "in.helibrary.switch";
|
|
/** 这个值才开启入院宣教 */
|
private static final String HELIBRARY_SWITCH_ON = "2";
|
|
/** 入院宣教长期任务的 service_type */
|
private static final String IN_HOSP_PUBLICITY_SERVICE_TYPE = "17";
|
|
/** 药品宣教只在省立同德执行 */
|
private static final String SITE_SLTD = "sltd";
|
|
/** 维度:科室 */
|
private static final String DEPT_TYPE_DEPT = "1";
|
|
/** 维度:病区 */
|
private static final String DEPT_TYPE_WARD = "2";
|
|
/** 科室维度用的标志位列(与原实现 L340/L357/L368 一致) */
|
private static final String FLAG_COL_IN_DEPT = "in_deptcheck_flag";
|
|
/**
|
* 病区维度用的标志位列。
|
* <p>
|
* ⚠️ 注意:原实现病区维度的<b>回写也用 in_deptcheck_flag</b>(L384/L400),
|
* 所以这里两个常量值相同 —— <b>刻意保留</b>,不是笔误。
|
* 拆成两个常量是为了让 {@code verify_inhospvisit_semantics.py} 能分别断言。
|
*/
|
private static final String FLAG_COL_IN_DEPT_FOR_WARD_QUIRK = "in_deptcheck_flag";
|
|
/** 标志位:未处理 */
|
private static final String FLAG_PENDING = "0";
|
|
/** 标志位:跳过/失败 */
|
private static final String FLAG_SKIPPED = "2";
|
|
/** 宣教子任务类型:药品宣教(走出院维度) */
|
private static final int TYPE_MEDICAL = 0;
|
|
/** 宣教子任务类型:入院科室 */
|
private static final int TYPE_IN_DEPT = 4;
|
|
/** 宣教子任务类型:入院病区 */
|
private static final int TYPE_IN_WARD = 5;
|
|
@Value("${spring.profiles.active}")
|
private String active;
|
|
@Autowired
|
private IConfigService configService;
|
|
/**
|
* 数据端口(smartor 提供过渡实现)。取不到时两个方法都直接返回 0。
|
*/
|
@Autowired
|
private ObjectProvider<PublicityDataPort> dataPortProvider;
|
|
/**
|
* inhospvisit 的下沉接口:由它代建子任务(含 type=0/4/5 分支与 check_flag 回写)。
|
*/
|
@Autowired
|
private ObjectProvider<PublicitySubtaskApi> subtaskApiProvider;
|
|
// ==========================================================================
|
// 1. 入院宣教(原 L331-409)
|
// ==========================================================================
|
|
@Override
|
public int dealInHospPublicity(String config) {
|
// 原 L332-333:受 in.helibrary.switch 控制,不是 "2" 就整块不执行
|
String helibrarySwitch = configService == null ? null : readHelibrarySwitch();
|
if (StringUtils.isEmpty(helibrarySwitch) || !HELIBRARY_SWITCH_ON.equals(helibrarySwitch)) {
|
log.debug("【宣教】in.helibrary.switch={},不是 \"2\",跳过入院宣教", helibrarySwitch);
|
return 0;
|
}
|
|
PublicityDataPort port = dataPort();
|
PublicitySubtaskApi subtaskApi = subtaskApi();
|
if (port == null || subtaskApi == null) {
|
return 0;
|
}
|
|
int handled = 0;
|
handled += dealInHospPublicityByDept(port, subtaskApi, config);
|
handled += dealInHospPublicityByWard(port, subtaskApi, config);
|
return handled;
|
}
|
|
/**
|
* 1.1 科室维度(原 L335-372)。
|
* <p>
|
* 逐行对应:
|
* <pre>
|
* 原 L336 for (PatMedInhosp patMedInhosp1 : patMedInhosps)
|
* 原 L338 PatArchive patArchive = patArchiveMapper.selectPatArchiveByPatid(...)
|
* 原 L339-344 为空 → setInDeptcheckFlag("2") + setLongTaskReason("患者基本信息为空")
|
* + update,continue
|
* 原 L347-353 按 dept_type="1" + service_type="17" 查长期宣教任务
|
* 原 L355-359 为空 → 重查记录、拼接 reason、写 in_deptcheck_flag="2"
|
* 原 L361-363 逐任务 writeInSubTask(..., 4, config)
|
* 原 L366-371 异常 → setInDeptcheckFlag("2") + "人员入院科室处理出异常了" + update
|
* </pre>
|
*/
|
private int dealInHospPublicityByDept(PublicityDataPort port, PublicitySubtaskApi subtaskApi, String config) {
|
List<InHospPublicityRowDto> rows = port.findInHospForPublicity(FLAG_COL_IN_DEPT);
|
int handled = 0;
|
for (InHospPublicityRowDto row : rows) {
|
// 原 L338-344:患者基本信息为空 → in_deptcheck_flag="2" + reason 覆盖
|
if (!port.existsPatArchive(row.getPatid())) {
|
port.updatePublicityFlag(row.getInhospid(), FLAG_COL_IN_DEPT,
|
FLAG_SKIPPED, "患者基本信息为空", false);
|
continue;
|
}
|
try {
|
// 原 L347-353:按患者所在科室查该科室的入院长期宣教任务
|
List<PublicityTaskMatchDto> tasks = port.findDeptPublicityTaskIds(
|
row.getDeptcode(), DEPT_TYPE_DEPT, row.getOrgid(), row.getInhospid());
|
if (tasks == null || tasks.isEmpty()) {
|
// 原 L355-359:未配置 → in_deptcheck_flag="2" + 追加 reason
|
// 文案照抄(注意:原代码在旧值前不补分隔符,这里也不补)
|
port.updatePublicityFlag(row.getInhospid(), FLAG_COL_IN_DEPT, FLAG_SKIPPED,
|
"该患者所在科室未配置入院长期宣教任务;", true);
|
} else {
|
for (PublicityTaskMatchDto task : tasks) {
|
// 原 L362:writeInSubTask(taskId, true, patMedInhosp1, patArchive, 4, config)
|
subtaskApi.generatePublicitySubtask(task.getTaskId(), row.getPatid(),
|
row.getInhospid(), TYPE_IN_DEPT, config);
|
handled++;
|
}
|
}
|
} catch (Exception e) {
|
// 原 L366-371:异常 → in_deptcheck_flag="2" + reason 覆盖
|
log.error("【宣教】人员入院科室处理出异常了:{}", e.getMessage());
|
port.updatePublicityFlag(row.getInhospid(), FLAG_COL_IN_DEPT,
|
FLAG_SKIPPED, "人员入院科室处理出异常了", false);
|
}
|
}
|
return handled;
|
}
|
|
/**
|
* 1.2 病区维度(原 L375-408)。
|
* <p>
|
* ⚠️ 两个必须原样保留的点:
|
* <ul>
|
* <li>查患者用的是 {@code in_wardcheck_flag='0'};</li>
|
* <li>但"未配宣教任务"时回写的仍是 {@code in_deptcheck_flag}</b>(原 L400 的笔误)。</li>
|
* </ul>
|
*/
|
private int dealInHospPublicityByWard(PublicityDataPort port, PublicitySubtaskApi subtaskApi, String config) {
|
List<InHospPublicityRowDto> rows = port.findInHospForPublicity("in_wardcheck_flag");
|
int handled = 0;
|
for (InHospPublicityRowDto row : rows) {
|
// 原 L382-388:患者基本信息为空 → setInDeptcheckFlag("2") + reason 覆盖
|
if (!port.existsPatArchive(row.getPatid())) {
|
port.updatePublicityFlag(row.getInhospid(), FLAG_COL_IN_DEPT,
|
FLAG_SKIPPED, "患者基本信息为空", false);
|
continue;
|
}
|
// 原 L389 注释:「会出现一个问题,科室和病区发的同一个模板」—— 原样保留该行为
|
List<PublicityTaskMatchDto> tasks;
|
try {
|
tasks = port.findDeptPublicityTaskIds(
|
row.getHospitaldistrictcode(), DEPT_TYPE_WARD, row.getOrgid(), row.getInhospid());
|
} catch (Exception e) {
|
// ⚠️ 原实现这里【没有 try/catch】—— 病区分支是裸露的调用。
|
// 为了不让一次查询异常打断整个随访主流程(这属于"健壮性"而非"行为"),
|
// 这里选择捕获并记录,同时把该条标记为跳过。
|
// 该差异已登记在 verify_inhospvisit_semantics.py 的"刻意差异"清单里。
|
log.error("【宣教】人员入院病区处理出异常了:{}", e.getMessage());
|
port.updatePublicityFlag(row.getInhospid(), FLAG_COL_IN_DEPT_FOR_WARD_QUIRK,
|
FLAG_SKIPPED, "人员入院病区处理出异常了", false);
|
continue;
|
}
|
if (tasks == null || tasks.isEmpty()) {
|
// 原 L398-402:⚠️ 回写的是 in_deptcheck_flag(笔误),文案同样不补分隔符、不判旧值 null
|
port.updatePublicityFlag(row.getInhospid(), FLAG_COL_IN_DEPT_FOR_WARD_QUIRK, FLAG_SKIPPED,
|
"该患者所在病区未配置入院长期宣教任务;", true);
|
} else {
|
for (PublicityTaskMatchDto task : tasks) {
|
// 原 L405:writeInSubTask(taskId, true, patMedInhosp1, patArchive, 5, config)
|
subtaskApi.generatePublicitySubtask(task.getTaskId(), row.getPatid(),
|
row.getInhospid(), TYPE_IN_WARD, config);
|
handled++;
|
}
|
}
|
}
|
return handled;
|
}
|
|
// ==========================================================================
|
// 2. 药品宣教(原 L411-444)
|
// ==========================================================================
|
|
@Override
|
public int dealMedicalPublicity(String config) {
|
// 原 L412:仅 sltd 站点
|
if (!SITE_SLTD.equals(active)) {
|
log.debug("【宣教】active={},非 sltd,跳过药品宣教", active);
|
return 0;
|
}
|
|
PublicityDataPort port = dataPort();
|
PublicitySubtaskApi subtaskApi = subtaskApi();
|
if (port == null || subtaskApi == null) {
|
return 0;
|
}
|
|
// ⚠️ 原实现复用的是"科室维度未处理"的那份列表(L325-329 + L413),
|
// 所以这里也用 deptcheck_flag='0' 维度取。
|
List<InHospPublicityRowDto> rows = port.findInHospForPublicity(FLAG_COL_IN_DEPT);
|
int handled = 0;
|
for (InHospPublicityRowDto row : rows) {
|
String medicalCode = row.getMedicalCode();
|
if (StringUtils.isEmpty(medicalCode)) {
|
continue;
|
}
|
String[] split = medicalCode.split(",");
|
// 原 L417-421:防御性编程,确保两个串不为 null
|
String medicalCompanyStr = row.getMedicalCompany() != null ? row.getMedicalCompany() : "";
|
String medicalNameStr = row.getMedicalName() != null ? row.getMedicalName() : "";
|
String[] medicalCompanyArray = medicalCompanyStr.split(",");
|
// 原实现同样解析了 name 数组但【从未使用】——保留解析以提示这一点
|
@SuppressWarnings("unused")
|
String[] medicalNameArray = medicalNameStr.split(",");
|
|
for (int i = 0; i < split.length; i++) {
|
String medicalNameForQuery = null;
|
String companyForQuery;
|
if (i < medicalCompanyArray.length) {
|
// 原 L428-429:正常分支只设 company
|
companyForQuery = medicalCompanyArray[i];
|
} else {
|
// 原 L430-433:越界分支 —— 打 WARN,并【把 medicalName 置为空串】、
|
// 【不设 medicalCompany】(保持 null)。
|
// 这两个值会真实影响 SQL:name 空串 → 跳过 name 条件;
|
// company null → 跳过 company 条件。即越界时只按 medical_code 查。
|
// 【原样保留】—— 改了就会改变查询条件(这正是原代码的历史笔误)。
|
log.warn("【药品宣教】药品编码{}对应的公司信息下标越界,medicalCompany 数组长度={}, 当前索引={}",
|
split[i], medicalCompanyArray.length, i);
|
medicalNameForQuery = "";
|
companyForQuery = null;
|
}
|
|
List<PublicityTaskMatchDto> tasks = port.findMedicalPublicityTaskIds(
|
split[i], medicalNameForQuery, companyForQuery, row.getInhospid());
|
if (tasks != null && !tasks.isEmpty()) {
|
// 原 L435-438:命中就取第一条建子任务(check=true,由 writeInSubTask 内部去重)
|
subtaskApi.generatePublicitySubtask(tasks.get(0).getTaskId(), row.getPatid(),
|
row.getInhospid(), TYPE_MEDICAL, config);
|
handled++;
|
}
|
// 原 L440 注释:查不到的药品就是不需要宣教的,就不管了
|
}
|
}
|
return handled;
|
}
|
|
// ==========================================================================
|
// 依赖获取(全部可降级)
|
// ==========================================================================
|
|
private String readHelibrarySwitch() {
|
try {
|
return configService.selectConfigByKey(HELIBRARY_SWITCH_KEY);
|
} catch (Exception e) {
|
log.warn("【宣教】读取 sys_config 的 {} 失败,本次按未开启处理:{}", HELIBRARY_SWITCH_KEY, e.toString());
|
return null;
|
}
|
}
|
|
/**
|
* 取数据端口;取不到时打 WARN 并返回 null(调用方据此返回 0)
|
*/
|
private PublicityDataPort dataPort() {
|
if (dataPortProvider == null) {
|
return null;
|
}
|
try {
|
PublicityDataPort port = dataPortProvider.stream().findFirst().orElse(null);
|
if (port == null) {
|
log.warn("【宣教】容器里没有 PublicityDataPort 实现(smartor 的 PublicityDataPortBridge 未装配),"
|
+ "本次不做任何宣教。");
|
}
|
return port;
|
} catch (Exception e) {
|
log.warn("【宣教】获取 PublicityDataPort 失败,本次不做任何宣教:{}", e.toString());
|
return null;
|
}
|
}
|
|
/**
|
* 取 inhospvisit 的下沉接口;取不到时打 WARN 并返回 null(调用方据此返回 0)
|
*/
|
private PublicitySubtaskApi subtaskApi() {
|
if (subtaskApiProvider == null) {
|
return null;
|
}
|
try {
|
PublicitySubtaskApi api = subtaskApiProvider.stream().findFirst().orElse(null);
|
if (api == null) {
|
log.warn("【宣教】容器里没有 PublicitySubtaskApi 实现(ruoyi-inhospvisit 的门面未装配),"
|
+ "本次不做任何宣教。");
|
}
|
return api;
|
} catch (Exception e) {
|
log.warn("【宣教】获取 PublicitySubtaskApi 失败,本次不做任何宣教:{}", e.toString());
|
return null;
|
}
|
}
|
|
/**
|
* 原实现里 {@code PatMedInhosp.patid} 与 {@code PatArchive.id} 都是 {@code Long}
|
* ({@code PatMedInhosp.java:186} / {@code PatArchive.java:31}),
|
* 而 {@code generatePublicitySubtask} 的入参也是 {@code Long},所以这里是<b>直接透传</b>,
|
* 不存在类型转换。
|
*/
|
// 说明:早期草稿曾以为 patid 是 String 而写过一个 toLong() 帮助方法;
|
// 核实源码后确认是 Long,该方法已删除(保留此注释以避免后人重复踩这个误判)。
|
}
|