ruoyi-admin/src/main/resources/application.yml
@@ -75,7 +75,7 @@ # 国际化资源文件路径 basename: i18n/messages profiles: active: sltd active: nhfy # 文件上传 servlet: ruoyi-common/src/main/java/com/ruoyi/common/core/service/dto/VisitTaskRuleDto.java
@@ -1,5 +1,7 @@ package com.ruoyi.common.core.service.dto; import lombok.Data; import java.io.Serializable; /** @@ -21,84 +23,45 @@ * 其它/null = 不参与 * </pre> */ @Data public class VisitTaskRuleDto implements Serializable { private static final long serialVersionUID = 1L; /** 规则ID */ /** * 规则ID */ private Long id; /** 科室规则:"1"=首要 "2"=次要 */ /** * 科室规则:"1"=首要 "2"=次要 */ private String deptRule; /** 病区规则(⚠️ 原实体拼写即为 {@code wradRule}) */ /** * 病区规则(⚠️ 原实体拼写即为 {@code wradRule}) */ private String wradRule; /** 疾病规则 */ /** * 疾病规则 */ private String diagRule; /** 离院科室编码 */ /** * 离院科室编码 */ private String deptcode; /** 离院病区编码 */ /** * 离院病区编码 */ private String wardcode; /** 机构ID */ /** * 机构ID */ private String orgid; public Long getId() { return id; } public void setId(Long id) { this.id = id; } 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; } public String getDeptcode() { return deptcode; } public void setDeptcode(String deptcode) { this.deptcode = deptcode; } public String getWardcode() { return wardcode; } public void setWardcode(String wardcode) { this.wardcode = wardcode; } public String getOrgid() { return orgid; } public void setOrgid(String orgid) { this.orgid = orgid; } } ruoyi-inhospvisit/src/main/java/com/ruoyi/inhospvisit/service/InhospSubtaskWriter.java
@@ -61,19 +61,29 @@ @Service public class InhospSubtaskWriter { /** 景宁机构ID(原 L798 硬编码;已逐字核对原实现 4 处) */ /** * 景宁机构ID(原 L798 硬编码;已逐字核对原实现 4 处) */ private static final String ORG_JINGNING = "47255004333112711A1001"; /** 出院随访的服务类型(再入院停机分支的必要条件,原 L803) */ /** * 出院随访的服务类型(再入院停机分支的必要条件,原 L803) */ private static final String SERVICE_TYPE_DISCHARGE = "2"; /** 待执行状态 */ /** * 待执行状态 */ private static final long SENDSTATE_PENDING = 2L; /** 停机状态(原实现把 sendstate 置 4 表示不再执行) */ /** * 停机状态(原实现把 sendstate 置 4 表示不再执行) */ private static final long SENDSTATE_STOPPED = 4L; /** 疾病维度(type=3)专用:停旧任务后把执行方式全置失败 */ /** * 疾病维度(type=3)专用:停旧任务后把执行方式全置失败 */ private static final String PREACHFORM_STATE_FAIL = "5"; @Autowired @@ -99,32 +109,29 @@ * 把患者写进随访子任务表 —— 原 {@code writeInSubTask(Long taskid, Boolean check, PatMedInhosp, * PatArchive, Integer type, String config)}。 * * @param taskid 命中的长期任务ID * @param check 是否需要做"再入院"去重校验 * @param inhosp 出入院记录 * @param archive 患者档案 * @param type 0=药品宣教 / 1=离院科室 / 2=离院病区 / 3=疾病 / 4=入院科室 / 5=入院病区 * @param config {@code sys_config: visit.early.day} * @param taskid 命中的长期任务ID * @param check 是否需要做"再入院"去重校验 * @param inhosp 出入院记录 * @param archive 患者档案 * @param type 0=药品宣教 / 1=离院科室 / 2=离院病区 / 3=疾病 / 4=入院科室 / 5=入院病区 * @param config {@code sys_config: visit.early.day} * @return <b>是否真的落了库</b>(或"本来就已经有")。 * 返回值是 2026-10"一个患者只生成一条随访任务"改造的判据: * 编排层按维度优先级逐个尝试,只有拿到 {@code true} 才算该维度成功并停止。 * 原实现是 {@code void},编排层用"调用了几次"当成功数 —— 那是错的 * (任务未发布/被去重时同样是"生成了一条")。 * 返回值是 2026-10"一个患者只生成一条随访任务"改造的判据: * 编排层按维度优先级逐个尝试,只有拿到 {@code true} 才算该维度成功并停止。 * 原实现是 {@code void},编排层用"调用了几次"当成功数 —— 那是错的 * (任务未发布/被去重时同样是"生成了一条")。 */ public boolean writeInSubTask(Long taskid, Boolean check, VisitInhospRow inhosp, VisitArchiveRow archive, Integer type, String config) { // 原 L683-685 public boolean writeInSubTask(Long taskid, Boolean check, VisitInhospRow inhosp, VisitArchiveRow archive, Integer type, String config) { String longTaskReason = inhosp.getLongTaskReason(); String orgId = inhosp.getOrgid(); if (StringUtils.isEmpty(longTaskReason)) { longTaskReason = ""; } // 原 L686-728:查"已发布的长期任务";查不到时按 type 写不同的标志位后 return // 查任务创建模块的方法是否可用 VisitTaskCreateApi taskApi = taskCreateApi(); if (taskApi == null) { log.warn("【出入院随访】VisitTaskCreateApi 不可用(任务创建模块未装配),本患者不生成子任务。" + "inhospid={}, taskid={}", inhosp.getInhospid(), taskid); log.warn("【出入院随访】VisitTaskCreateApi 不可用(任务创建模块未装配),本患者不生成子任务。" + "inhospid={}, taskid={}", inhosp.getInhospid(), taskid); return false; } VisitLongTaskDto serviceTask = taskApi.findPublishedLongTask(taskid); @@ -145,23 +152,19 @@ // 患者被重复扫到时会走"停旧 + 再 insert 一条 sendstate=4"的分支, // 每跑一轮多一条,最终同一患者几十条。 // ------------------------------------------------------------------ if (taskApi.existsSubtask(archive == null ? null : archive.getId(), inhosp.getInhospid(), taskid)) { log.info("【出入院随访】患者{}出院记录{}任务{}已有子任务,跳过重复生成。", inhosp.getPatid(), inhosp.getInhospid(), taskid); if (taskApi.existsSubtask(archive == null ? null : archive.getId(), inhosp.getInhospid(), taskid)) { log.info("【出入院随访】患者{}出院记录{}任务{}已有子任务,跳过重复生成。", inhosp.getPatid(), inhosp.getInhospid(), taskid); writeFlagByType(inhosp.getInhospid(), type, "1", null); return true; } // 原 L732-736:组装(副作用由本类实现,见 missingOutHospTime) VisitSubtaskDraft serviceSubtask = subtaskFactory.boxedServiceSubtask( toTaskSource(serviceTask, inhosp), inhosp, archive, config, type, row -> onMissingOutHospTime(row)); // serviceSubtask数据组装(副作用由本类实现,见 missingOutHospTime) VisitSubtaskDraft serviceSubtask = subtaskFactory.boxedServiceSubtask(toTaskSource(serviceTask, inhosp), inhosp, archive, config, type, row -> onMissingOutHospTime(row)); if (ObjectUtils.isEmpty(serviceSubtask)) { return false; } // 原 L737-753:就诊科室/病区填充 // 就诊科室/病区填充 if (type != null && type != 2) { // 科室和疾病 都用 科室填充 if ("1".equals(inhosp.getInhospstate())) { @@ -180,22 +183,19 @@ serviceSubtask.setVisitDeptName(inhosp.getLeavehospitaldistrictname()); } // 原 L754-755:记录 insertServiceSubtask 的返回状态 // 记录 insertServiceSubtask 的返回状态 int i = 0; try { // 原 L757-758 if (Boolean.TRUE.equals(check) && (serviceSubtask.getTaskSituation() == null || serviceSubtask.getTaskSituation() != 6)) { // 原 L759-764:查该患者 + 该 taskid 下所有待执行子任务 List<VisitPendingSubtaskDto> pendingList = taskApi.findPendingSubtasks(archive.getId(), taskid); if (Boolean.TRUE.equals(check) && (serviceSubtask.getTaskSituation() == null || serviceSubtask.getTaskSituation() != 6)) { // 查该患者 + 该 taskid 下所有待执行子任务 List<VisitPendingSubtaskDto> pendingList = taskApi.findPendingSubtasks(archive.getId(), taskid); if (pendingList != null && !pendingList.isEmpty()) { for (VisitPendingSubtaskDto pending : pendingList) { // 原 L768-771:不是长期任务,不处理 // 不是长期任务,不处理 if (pending.getLongSendTime() == null) { continue; } // 原 L772-773:type = 1或2 表示 "科室、病区" //type = 1或2 表示 "科室、病区" if (type != null && (type == 1 || type == 2)) { Date endTime = serviceSubtask.getEndtime(); Date endTime1 = pending.getEndtime(); @@ -222,14 +222,12 @@ } continue; } // 原 L795-800:景宁 出院首次(7天)随访一定要做 //景宁 出院首次(7天)随访一定要做 boolean firstVisit = ORG_JINGNING.equals(orgId); if (!firstVisit) { // 原 L802-803 // ⚠️ 原实现不判 serviceType 是否为 null(会 NPE),原样保留 if (endTime1.before(endTime) && SERVICE_TYPE_DISCHARGE.equals(pending.getServiceType())) { // 原 L804-813:把循环到的旧子任务停掉 if (endTime1.before(endTime) && SERVICE_TYPE_DISCHARGE.equals(pending.getServiceType())) { // 把循环到的旧子任务停掉 pending.setSendstate(SENDSTATE_STOPPED); if (StringUtils.isNotEmpty(serviceSubtask.getRemark())) { serviceSubtask.setRemark(serviceSubtask.getRemark() + ",科室/病区患者再入院"); @@ -243,12 +241,11 @@ i = 1; } } else { // 原 L814-824:相反,则把"当前的"数据停掉(置 error + sendstate=4 后 insert) //相反,则把"当前的"数据停掉(置 error + sendstate=4 后 insert) serviceSubtask.setResult("error"); serviceSubtask.setSendstate(SENDSTATE_STOPPED); if (StringUtils.isNotEmpty(serviceSubtask.getRemark())) { serviceSubtask.setRemark(serviceSubtask.getRemark() + ",当前患者出院时间后有待执行的出院随访长期任务"); serviceSubtask.setRemark(serviceSubtask.getRemark() + ",当前患者出院时间后有待执行的出院随访长期任务"); } else { serviceSubtask.setRemark("当前患者出院时间后有待执行的出院随访长期任务"); } @@ -258,7 +255,7 @@ } } } else if (type != null && type == 3) { // 原 L826-841:type=3 疾病 —— 无条件停旧 + 重新新增 //type=3 疾病 —— 无条件停旧 + 重新新增 pending.setSendstate(SENDSTATE_STOPPED); if (StringUtils.isNotEmpty(serviceSubtask.getRemark())) { serviceSubtask.setRemark(serviceSubtask.getRemark() + ",疾病患者再入院"); @@ -276,7 +273,7 @@ } } } else { // 原 L844-856:没有待执行任务 //没有待执行任务 markPhoneEmptyIfNeeded(serviceSubtask); serviceSubtask.setCreateBy(serviceTask.getCreateBy()); serviceSubtask.setCreateTime(new Date()); @@ -284,13 +281,13 @@ addServiceSubtaskPreachform(serviceSubtask, null); } } else if (serviceSubtask.getTaskSituation() != null && serviceSubtask.getTaskSituation() == 6) { // 原 L857-862:该患者被过滤,不用执行 // 该患者被过滤,不用执行 serviceSubtask.setCreateBy(serviceTask.getCreateBy()); serviceSubtask.setCreateTime(new Date()); serviceSubtask.setSendstate(SENDSTATE_STOPPED); i = insert(taskApi, serviceSubtask) ? 1 : 0; } else { // 原 L863-875:不需要校验(check=false) // 不需要校验(check=false) markPhoneEmptyIfNeeded(serviceSubtask); serviceSubtask.setCreateBy(serviceTask.getCreateBy()); serviceSubtask.setCreateTime(new Date()); @@ -298,19 +295,17 @@ addServiceSubtaskPreachform(serviceSubtask, null); } // 原 L877-899:把 check_flag 改成 1(已处理)/ 生成失败置 2 //把 check_flag 改成 1(已处理)/ 生成失败置 2 if (i == 1) { writeFlagByType(inhosp.getInhospid(), type, "1", null); } else { log.info("生成子任务失败serviceSubtask的taskid为:{},patid为:{}", serviceSubtask.getTaskid(), serviceSubtask.getPatid()); log.info("生成子任务失败serviceSubtask的taskid为:{},patid为:{}", serviceSubtask.getTaskid(), serviceSubtask.getPatid()); writeFlagByType(inhosp.getInhospid(), type, "2", "生成子任务失败 " + type); } return i == 1; } catch (Exception e) { // 原 L900-912:出错回滚事务 log.info("生成子任务报错回滚serviceSubtask的taskid为:{},patid为:{}", serviceSubtask.getTaskid(), serviceSubtask.getPatid()); // 出错回滚事务 log.info("生成子任务报错回滚serviceSubtask的taskid为:{},patid为:{}", serviceSubtask.getTaskid(), serviceSubtask.getPatid()); // ⚠️ 原实现的 catch 只处理 type 1/2/3/0 —— type=4/5 抛异常时不回写任何标志位。 // 原样保留(见类注释第 1 条)。 writeFlagByTypeForRollback(inhosp.getInhospid(), type, "2", "生成子任务报错回滚 " + type); @@ -319,10 +314,7 @@ } } // ========================================================================== // 原 L691-725:任务不存在时按 type 写标志位 // ========================================================================== // 任务不存在时按 type 写标志位 private void writeMissingTaskFlag(VisitInhospRow inhosp, Integer type, String longTaskReason, Long taskid) { if (type == null) { return; @@ -331,42 +323,31 @@ switch (type) { case 0: log.info("该患者药品宣教长期任务不存在,任务ID为:{}", taskid); flagWriter.setFlagWithReason(inhosp.getInhospid(), "medcheck_flag", "2", longTaskReason + " & 该患者药品宣教长期任务不存在,任务ID为:" + taskid); flagWriter.setFlagWithReason(inhosp.getInhospid(), "medcheck_flag", "2", longTaskReason + " & 该患者药品宣教长期任务不存在,任务ID为:" + taskid); return; case 1: log.info("该患者出院随访长期任务不存在,任务ID为:{}", taskid); flagWriter.setFlagWithReason(inhosp.getInhospid(), "deptcheck_flag", "2", longTaskReason + " & 该患者离院科室长期任务不存在,任务ID为:" + taskid); flagWriter.setFlagWithReason(inhosp.getInhospid(), "deptcheck_flag", "2", longTaskReason + " & 该患者离院科室长期任务不存在,任务ID为:" + taskid); return; case 2: flagWriter.setFlagWithReason(inhosp.getInhospid(), "wardcheck_flag", "2", longTaskReason + " & 该患者离院病区长期任务不存在,任务ID为:" + taskid); flagWriter.setFlagWithReason(inhosp.getInhospid(), "wardcheck_flag", "2", longTaskReason + " & 该患者离院病区长期任务不存在,任务ID为:" + taskid); return; case 3: flagWriter.setFlagWithReason(inhosp.getInhospid(), "diagcheck_flag", "2", longTaskReason + " & 该患者疾病长期任务不存在,任务ID为:" + taskid); flagWriter.setFlagWithReason(inhosp.getInhospid(), "diagcheck_flag", "2", longTaskReason + " & 该患者疾病长期任务不存在,任务ID为:" + taskid); return; case 4: flagWriter.setFlagWithReason(inhosp.getInhospid(), "in_deptcheck_flag", "2", longTaskReason + " & 该患者入院科室长期任务不存在,任务ID为:" + taskid); flagWriter.setFlagWithReason(inhosp.getInhospid(), "in_deptcheck_flag", "2", longTaskReason + " & 该患者入院科室长期任务不存在,任务ID为:" + taskid); return; case 5: flagWriter.setFlagWithReason(inhosp.getInhospid(), "in_wardcheck_flag", "2", longTaskReason + " & 该患者入院病区长期任务不存在,任务ID为:" + taskid); flagWriter.setFlagWithReason(inhosp.getInhospid(), "in_wardcheck_flag", "2", longTaskReason + " & 该患者入院病区长期任务不存在,任务ID为:" + taskid); return; default: // 原实现:type 不属于 0-5 时,if/else-if 链全部不命中 → 什么都不做、也不 return // → 继续往下走,此时 serviceTask 仍是 null,到 L729 之后会 NPE。 // 这里【不复制】那个 NPE(它只会掩盖问题),改为记录并返回。 log.warn("【出入院随访】任务不存在且 type={} 不属于 0-5,无法判定回写哪个标志位。" + "inhospid={}, taskid={}", type, inhosp.getInhospid(), taskid); log.warn("【出入院随访】任务不存在且 type={} 不属于 0-5,无法判定回写哪个标志位。" + "inhospid={}, taskid={}", type, inhosp.getInhospid(), taskid); } } // ========================================================================== // 原 L877-912:标志位回写(成功 / 失败 / 异常回滚) // ========================================================================== /** * type → 标志位回写。<b>照抄原 L881-885 与 L892-896 的分支</b>。 @@ -450,12 +431,8 @@ flagWriter.setFlagWithRemark(inhospid, column, flagValue, remark); } // ========================================================================== // 辅助 // ========================================================================== /** * 原 L845-851 / L864-870:手机号为空时追加备注、置 sendstate=4、result=error。 * 手机号为空时追加备注、置 sendstate=4、result=error。 */ private void markPhoneEmptyIfNeeded(VisitSubtaskDraft d) { if (StringUtils.isEmpty(d.getPhone())) { @@ -480,9 +457,7 @@ */ private void onMissingOutHospTime(VisitInhospRow row) { flagWriter.setFlag(row.getInhospid(), "deptcheck_flag", "2"); flagWriter.setFlagWithRemark(row.getInhospid(), "deptcheck_flag", "2", (row.getRemark() == null ? "" : row.getRemark()) + "& 该患者疾病出院时间为空,出院记录ID为:" + row.getInhospid()); flagWriter.setFlagWithRemark(row.getInhospid(), "deptcheck_flag", "2", (row.getRemark() == null ? "" : row.getRemark()) + "& 该患者疾病出院时间为空,出院记录ID为:" + row.getInhospid()); } /** @@ -495,8 +470,7 @@ * ⚠️ {@code type / sendDay / updateBy / createBy / serviceType / preachform} 等 * 都在 {@link VisitLongTaskDto} 里有对应字段,直接搬。 */ private com.ruoyi.inhospvisit.domain.row.VisitTaskRow toTaskSource(VisitLongTaskDto dto, VisitInhospRow inhosp) { private com.ruoyi.inhospvisit.domain.row.VisitTaskRow toTaskSource(VisitLongTaskDto dto, VisitInhospRow inhosp) { com.ruoyi.inhospvisit.domain.row.VisitTaskRow t = new com.ruoyi.inhospvisit.domain.row.VisitTaskRow(); t.setTaskid(dto.getTaskid()); t.setTaskName(dto.getTaskName()); @@ -560,16 +534,14 @@ try { SubtaskPreachformWriter writer = preachformWriterProvider.stream().findFirst().orElse(null); if (writer == null) { log.warn("【出入院随访】SubtaskPreachformWriter 未装配,跳过执行方式明细写入。subtaskId={}", subtask.getId()); log.warn("【出入院随访】SubtaskPreachformWriter 未装配,跳过执行方式明细写入。subtaskId={}", subtask.getId()); return; } writer.addServiceSubtaskPreachform(subtask, sendstate); } catch (Exception e) { // 这一步失败不应影响主流程(原实现在同一个 try 里,失败会走 catch 回滚标志位; // 但那是因为它与 insert 同属一次事务。这里拆开后按"尽力而为"处理并记录) log.warn("【出入院随访】写执行方式明细失败,已忽略。subtaskId={},原因={}", subtask.getId(), e.toString()); log.warn("【出入院随访】写执行方式明细失败,已忽略。subtaskId={},原因={}", subtask.getId(), e.toString()); } } ruoyi-inhospvisit/src/main/java/com/ruoyi/inhospvisit/service/InhospTaskOrchestrator.java
@@ -88,19 +88,29 @@ @Service public class InhospTaskOrchestrator { /** 出院随访的服务类型 */ /** * 出院随访的服务类型 */ private static final String SERVICE_TYPE_DISCHARGE = "2"; /** 维度:科室 */ /** * 维度:科室 */ private static final String DEPT_TYPE_DEPT = "1"; /** 维度:病区 */ /** * 维度:病区 */ private static final String DEPT_TYPE_WARD = "2"; /** 站点:新华(跳过病区+疾病维度) */ /** * 站点:新华(跳过病区+疾病维度) */ private static final String SITE_XH = "xh"; /** 站点:南华附一(疾病维度用诊断名) */ /** * 站点:南华附一(疾病维度用诊断名) */ private static final String SITE_NHFY = "nhfy"; @Value("${spring.profiles.active}") @@ -161,20 +171,15 @@ * <h3>站点差异(保留)</h3> * {@code xh} 只做科室维度 —— {@link #addDimension} 里统一挡掉病区/疾病。 * * @param inhosp 待处理的出院患者 * @param inhosp 待处理的出院患者 * @param archive 患者档案 * @param rule 已查好的规则;{@code null} = 该科室/病区未配置规则 * @param config {@code sys_config: visit.early.day} * @param rule 已查好的规则;{@code null} = 该科室/病区未配置规则 * @param config {@code sys_config: visit.early.day} */ public void addSubTaskByRule(VisitInhospRow inhosp, VisitArchiveRow archive, VisitTaskRule rule, String config) { public void addSubTaskByRule(VisitInhospRow inhosp, VisitArchiveRow archive, VisitTaskRule rule, String config) { try { List<Integer> order = resolveDimensionOrder(rule); log.info("【基于规则】患者{}规则:deptRule={}, wradRule={}, diagRule={};维度尝试顺序={}", inhosp.getInhospid(), rule == null ? null : rule.getDeptRule(), rule == null ? null : rule.getWradRule(), rule == null ? null : rule.getDiagRule(), order); log.info("【基于规则】患者{}规则:deptRule={}, wradRule={}, diagRule={};维度尝试顺序={}", inhosp.getInhospid(), rule == null ? null : rule.getDeptRule(), rule == null ? null : rule.getWradRule(), rule == null ? null : rule.getDiagRule(), order); Integer hit = null; for (Integer type : order) { @@ -242,7 +247,9 @@ return order; } /** 追加一个候选维度;{@code xh} 站点只允许科室维度(原 {@code !active.equals("xh")} 的等价写法) */ /** * 追加一个候选维度;{@code xh} 站点只允许科室维度(原 {@code !active.equals("xh")} 的等价写法) */ private void addDimension(List<Integer> order, int type, boolean xhSite) { if (xhSite && type != 1) { return; @@ -265,9 +272,10 @@ } } /** 尝试一个维度,返回"是否真的生成了(或本来就已经有)一条子任务" */ private boolean generateOneForDimension(VisitInhospRow inhosp, VisitArchiveRow archive, int type, String config) { /** * 尝试一个维度,返回"是否真的生成了(或本来就已经有)一条子任务" */ private boolean generateOneForDimension(VisitInhospRow inhosp, VisitArchiveRow archive, int type, String config) { switch (type) { case 1: return addDeptSubTaskForPatient(inhosp, archive, config) > 0; @@ -309,16 +317,16 @@ continue; } if (hit == type) { updateCheckFlag(inhosp, type, "1", composeRemark(inhosp, "按优先级生成一条随访任务(维度=" + dimensionName(type) + ")")); updateCheckFlag(inhosp, type, "1", composeRemark(inhosp, "按优先级生成一条随访任务(维度=" + dimensionName(type) + ")")); } else { updateCheckFlag(inhosp, type, "2", composeRemark(inhosp, "已生成一条随访任务(维度=" + dimensionName(hit) + "),本维度跳过")); updateCheckFlag(inhosp, type, "2", composeRemark(inhosp, "已生成一条随访任务(维度=" + dimensionName(hit) + "),本维度跳过")); } } } /** 拼 reason:沿用原实现的 {@code remark + ";xxx"} 形式 */ /** * 拼 reason:沿用原实现的 {@code remark + ";xxx"} 形式 */ private String composeRemark(VisitInhospRow inhosp, String text) { String remark = inhosp.getRemark(); return StringUtils.isNotEmpty(remark) ? remark + ";" + text : text; @@ -364,8 +372,7 @@ u.setWardcheckFlag(fresh == null ? null : fresh.getWardcheckFlag()); u.setDiagcheckFlag(fresh == null ? null : fresh.getDiagcheckFlag()); // 原实现不判 null(fresh.getLongTaskReason() + "..."),原样保留 u.setLongTaskReason((fresh == null ? null : fresh.getLongTaskReason()) + "该患者所在科室未配置离院长期任务;"); u.setLongTaskReason((fresh == null ? null : fresh.getLongTaskReason()) + "该患者所在科室未配置离院长期任务;"); updateInhosp(u); } else { for (VisitTaskdeptRow t : taskdepts) { @@ -373,8 +380,7 @@ if (subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 1, config)) { return 1; } log.info("【科室任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); log.info("【科室任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); } } } catch (Exception e) { @@ -430,8 +436,7 @@ u.setDeptcheckFlag(fresh == null ? null : fresh.getDeptcheckFlag()); u.setDiagcheckFlag(fresh == null ? null : fresh.getDiagcheckFlag()); // 原有判空(与科室维度那处不同),原样保留 u.setLongTaskReason(StringUtils.isNotEmpty(fresh == null ? null : fresh.getLongTaskReason()) ? fresh.getLongTaskReason() + errorreason : errorreason); u.setLongTaskReason(StringUtils.isNotEmpty(fresh == null ? null : fresh.getLongTaskReason()) ? fresh.getLongTaskReason() + errorreason : errorreason); updateInhosp(u); } else { for (VisitTaskdeptRow t : taskdeptList) { @@ -439,8 +444,7 @@ if (subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 2, config)) { return 1; } log.info("【病区任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); log.info("【病区任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); } } } catch (Exception e) { @@ -506,23 +510,19 @@ int flag = 0; for (VisitTaskdiagRow t : taskdiags) { // 原 L1975-1983:必须科室或病区能匹配上才建 if (StringUtils.isNotEmpty(t.getDeptCode()) && t.getDeptCode().equals(inhosp.getLeaveldeptcode())) { if (StringUtils.isNotEmpty(t.getDeptCode()) && t.getDeptCode().equals(inhosp.getLeaveldeptcode())) { flag = 1; // 一个患者只留一条:第一条真正落库的即返回 if (subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 3, config)) { return 1; } log.info("【疾病任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); } else if (StringUtils.isNotEmpty(t.getWardCode()) && t.getWardCode().equals(inhosp.getLeavehospitaldistrictcode())) { log.info("【疾病任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); } else if (StringUtils.isNotEmpty(t.getWardCode()) && t.getWardCode().equals(inhosp.getLeavehospitaldistrictcode())) { flag = 1; if (subtaskWriter.writeInSubTask(t.getTaskId(), true, inhosp, archive, 3, config)) { return 1; } log.info("【疾病任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); log.info("【疾病任务】患者{}任务{}未生成子任务,尝试下一个候选", inhosp.getInhospid(), t.getTaskId()); } } if (flag == 0) { @@ -571,13 +571,11 @@ try { VisitTaskCreateApi api = taskApi(); if (api == null) { log.warn("【出入院随访】VisitTaskCreateApi 不可用,跳过再入院处理。inhospid={}", newInHosp.getInhospid()); log.warn("【出入院随访】VisitTaskCreateApi 不可用,跳过再入院处理。inhospid={}", newInHosp.getInhospid()); return; } // 原 L1324-1328:patid + sendstate=2(不设 taskid) List<VisitPendingSubtaskDto> pendingList = api.findPendingSubtasks(newInHosp.getPatid(), null); List<VisitPendingSubtaskDto> pendingList = api.findPendingSubtasks(newInHosp.getPatid(), null); if (CollectionUtils.isEmpty(pendingList)) { return; } @@ -592,8 +590,7 @@ continue; } // 原 L1342-1350:旧子任务的出院时间在新入院时间之前 → 停掉 if (pending.getEndtime() != null && newInHosp.getStarttime() != null && pending.getEndtime().before(newInHosp.getStarttime())) { if (pending.getEndtime() != null && newInHosp.getStarttime() != null && pending.getEndtime().before(newInHosp.getStarttime())) { pending.setSendstate(4L); pending.setIsVisitAgain(2); pending.setRemark("患者再入院"); @@ -607,7 +604,9 @@ } } /** 景宁机构ID(原 L1338;与 InhospSubtaskWriter 里那份是同一常量,已逐字核对) */ /** * 景宁机构ID(原 L1338;与 InhospSubtaskWriter 里那份是同一常量,已逐字核对) */ private static final String ORG_JINGNING = "47255004333112711A1001"; // ========================================================================== @@ -655,13 +654,19 @@ */ public static class VisitTaskRule { /** 科室规则:"1"=首要 "2"=次要,其它=不参与 */ /** * 科室规则:"1"=首要 "2"=次要,其它=不参与 */ private String deptRule; /** 病区规则(原字段名就是拼错的 {@code wradRule},保持同名以免读错) */ /** * 病区规则(原字段名就是拼错的 {@code wradRule},保持同名以免读错) */ private String wradRule; /** 疾病规则 */ /** * 疾病规则 */ private String diagRule; public String getDeptRule() { @@ -689,13 +694,16 @@ } } /** 供测试/日志:本类是否拿到了必需的 Mapper */ /** * 供测试/日志:本类是否拿到了必需的 Mapper */ boolean hasMappers() { return inhospMapper != null && archiveMapper != null && taskdeptMapper != null && taskdiagMapper != null; return inhospMapper != null && archiveMapper != null && taskdeptMapper != null && taskdiagMapper != null; } /** 预留:供日志使用的时间戳(避免 IDE 报未使用 import Date) */ /** * 预留:供日志使用的时间戳(避免 IDE 报未使用 import Date) */ @SuppressWarnings("unused") private Date now() { return new Date(); ruoyi-inhospvisit/src/main/java/com/ruoyi/inhospvisit/service/InhospVisitTaskService.java
@@ -4,6 +4,7 @@ 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.VisitInhospUpdateDto; import com.ruoyi.common.core.service.dto.VisitTaskRuleDto; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.core.service.dto.VisitArchiveRow; @@ -179,8 +180,7 @@ // 原 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(); VisitInhospUpdateDto u = new VisitInhospUpdateDto(); u.setInhospid(row.getInhospid()); u.setDeptcheckFlag("2"); u.setLongTaskReason("患者基本信息为空"); @@ -197,8 +197,7 @@ // 原 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(); 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("基于规则处理异常"); @@ -245,8 +244,7 @@ * @return 原实现固定返回 1 */ public int dealOutHospInfoBySchedule(String config) { log.warn("【出入院随访】dealOutHospInfoBySchedule 本次未搬迁(含手术分支与任务组 builder," + "属第二批 ruoyi-othervisit 范围),仍由 smartor 的实现承接。config={}", config); log.warn("【出入院随访】dealOutHospInfoBySchedule 本次未搬迁(含手术分支与任务组 builder," + "属第二批 ruoyi-othervisit 范围),仍由 smartor 的实现承接。config={}", config); return 1; } @@ -254,12 +252,13 @@ // 辅助 // ========================================================================== /** 原 L1703-1707:按离院科室/病区查规则(取不到端口时返回 null = 未配置规则 → 全部生成) */ /** * 原 L1703-1707:按离院科室/病区查规则(取不到端口时返回 null = 未配置规则 → 全部生成) */ private VisitTaskRuleDto findTaskRule(VisitInhospRow row) { PublicityDataPort port = dataPort(); if (port == null) { log.warn("【出入院随访】PublicityDataPort 不可用,规则按「未配置」处理(全部生成)。inhospid={}", row.getInhospid()); log.warn("【出入院随访】PublicityDataPort 不可用,规则按「未配置」处理(全部生成)。inhospid={}", row.getInhospid()); return null; } try { @@ -270,7 +269,9 @@ } } /** 把端口的规则 DTO 适配成编排器内嵌的规则视图(字段一一对应,含拼错的 {@code wradRule}) */ /** * 把端口的规则 DTO 适配成编排器内嵌的规则视图(字段一一对应,含拼错的 {@code wradRule}) */ private InhospTaskOrchestrator.VisitTaskRule toOrchestratorRule(VisitTaskRuleDto dto) { if (dto == null) { return null; @@ -282,7 +283,7 @@ return r; } private void updateInhosp(com.ruoyi.common.core.service.dto.VisitInhospUpdateDto u) { private void updateInhosp(VisitInhospUpdateDto u) { PublicityDataPort port = dataPort(); if (port == null) { log.warn("【出入院随访】PublicityDataPort 不可用,无法写回标志位。inhospid={}", u.getInhospid()); ruoyi-message/pom.xml
@@ -95,6 +95,16 @@ <artifactId>commons-collections4</artifactId> <version>4.4</version> </dependency> <dependency> <groupId>jakarta.jws</groupId> <artifactId>jakarta.jws-api</artifactId> <version>2.1.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>jakarta.xml.ws</groupId> <artifactId>jakarta.xml.ws-api</artifactId> </dependency> </dependencies> </project> ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -76,6 +76,7 @@ import com.ruoyi.task.domain.ServiceSubtaskPreachform; import com.ruoyi.task.service.IServiceSubtaskService; import com.ruoyi.task.domain.SendMagParam; /** * 定时任务调度测试 * @@ -367,8 +368,7 @@ //1.获取发送人工号 ServiceSubtaskDetailTraceVO subtaskDetailTraceVO = new ServiceSubtaskDetailTraceVO(); subtaskDetailTraceVO.setHandleFlag("0"); List<ServiceSubtaskDetailTrace> serviceSubtaskDetailTraces = serviceSubtaskDetailTraceService.selectServiceSubtaskDetailTtraceList(subtaskDetailTraceVO); List<ServiceSubtaskDetailTrace> serviceSubtaskDetailTraces = serviceSubtaskDetailTraceService.selectServiceSubtaskDetailTtraceList(subtaskDetailTraceVO); // 获取“异常内容提醒" SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique("send.except.content"); String exceptContent = sysConfig.getConfigValue(); @@ -376,12 +376,12 @@ // 获取“内容提醒测试人工号” SysConfig testPersonConfig = sysConfigMapper.checkConfigKeyUnique("send.test.person"); String testPerson = ObjectUtils.isNotEmpty(testPersonConfig) ? testPersonConfig.getConfigValue() : ""; if(CollectionUtils.isNotEmpty(serviceSubtaskDetailTraces)){ if (CollectionUtils.isNotEmpty(serviceSubtaskDetailTraces)) { for (ServiceSubtaskDetailTrace serviceSubtaskDetailTrace : serviceSubtaskDetailTraces) { List<String> dutyDeptPersonCodeList = new ArrayList<>(); // 把 dutyDeptPersonCode 整理到 list 中(如果多个人用逗号分割,则展开并去重) // 把 dutyDeptPersonCode 整理到 list 中(如果多个人用逗号分割,则展开并去重) String dutyDeptPersonCodeStr = serviceSubtaskDetailTrace.getDutyDeptPersonCode(); if(StringUtils.isNotEmpty(dutyDeptPersonCodeStr)){ if (StringUtils.isNotEmpty(dutyDeptPersonCodeStr)) { String[] dutyDeptPersonCodeSplit = dutyDeptPersonCodeStr.split(","); if (CollectionUtils.isEmpty(dutyDeptPersonCodeList)) { dutyDeptPersonCodeList = new ArrayList<>(Arrays.asList(dutyDeptPersonCodeSplit)); @@ -409,18 +409,18 @@ sysUserVo.setOrgid(serviceSubtaskDetailTrace.getOrgid()); List<SysUser> users = userService.getUserList(sysUserVo); String phoneNumber = ""; if(ObjectUtils.isNotEmpty(users)){ if (ObjectUtils.isNotEmpty(users)) { sysUser = users.get(0); phoneNumber = sysUser.getPhonenumber(); }else { } else { continue; } if(StringUtils.isEmpty(phoneNumber)){ if (StringUtils.isEmpty(phoneNumber)) { continue; } String dutyDeptPerson = dingTalkService.getDingTalkUserByMobile(phoneNumber); if(StringUtils.isNotBlank(dutyDeptPerson)){ if (StringUtils.isNotBlank(dutyDeptPerson)) { String context = TemplateUtils.render(exceptContent, serviceSubtaskDetailTrace); dingTalkService.sendDingTalkText(context, dutyDeptPerson); //更新状态 @@ -566,8 +566,7 @@ log.error("【dealHisDataMZ】SysConfig中的院区ID为空!"); return; } List<Long> campusidList = Arrays.stream(sysConfigcampusids.get(0).getConfigValue().split(",")) .map(String::trim).filter(StringUtils::isNotBlank).map(Long::valueOf).collect(Collectors.toList()); List<Long> campusidList = Arrays.stream(sysConfigcampusids.get(0).getConfigValue().split(",")).map(String::trim).filter(StringUtils::isNotBlank).map(Long::valueOf).collect(Collectors.toList()); log.info("【dealHisDataMZ】院区:{},单轮预算:{}分钟,每页:{}条", campusidList, budgetMinutes, pageSize); for (Long cid : campusidList) { @@ -590,15 +589,13 @@ redisCache.deleteObject(lockKey); } } log.info("【dealHisDataMZ】门急诊采集本轮结束,耗时:{}秒", java.time.Duration.between(now, LocalDateTime.now()).getSeconds()); log.info("【dealHisDataMZ】门急诊采集本轮结束,耗时:{}秒", java.time.Duration.between(now, LocalDateTime.now()).getSeconds()); } /** * 采集某个院区:先补一次性回溯日期,再做昨天的收尾采集,最后整轮刷新今天 */ private void collectOutpatientDaily(Long cid, String backfillParam, long pageSize, long maxPage, LocalDateTime deadline) { private void collectOutpatientDaily(Long cid, String backfillParam, long pageSize, long maxPage, LocalDateTime deadline) { String cursorKey = "mzDayCursor:" + cid; MzDayCursor cursor = loadMzDayCursor(cursorKey); LocalDate today = LocalDate.now(); @@ -625,8 +622,7 @@ // 1) 一次性回溯 if (cursor.backfillDay != null) { LocalDate day = LocalDate.parse(cursor.backfillDay); MzGatherStatusEnum status = runDayPass(cid, cursorKey, cursor, MzGatherSlotEnum.BACKFILL, day, cursor.backfillPage, pageSize, maxPage, deadline); MzGatherStatusEnum status = runDayPass(cid, cursorKey, cursor, MzGatherSlotEnum.BACKFILL, day, cursor.backfillPage, pageSize, maxPage, deadline); if (status != MzGatherStatusEnum.DONE) { log.warn("【dealHisDataMZ】回溯日期 {} 本轮未完成({}),断点已保留,院区:{}", day, status.getDesc(), cid); return; @@ -639,8 +635,7 @@ // 2) 昨天收尾采集:每天一次,兜住 23:xx 之后才写进 HIS 的记录和跨零点的数据 if (!yesterday.toString().equals(cursor.finalDay)) { MzGatherStatusEnum status = runDayPass(cid, cursorKey, cursor, MzGatherSlotEnum.FINAL, yesterday, cursor.finalPage, pageSize, maxPage, deadline); MzGatherStatusEnum status = runDayPass(cid, cursorKey, cursor, MzGatherSlotEnum.FINAL, yesterday, cursor.finalPage, pageSize, maxPage, deadline); if (status != MzGatherStatusEnum.DONE) { log.warn("【dealHisDataMZ】昨日收尾采集 {} 本轮未完成({}),断点已保留,院区:{}", yesterday, status.getDesc(), cid); return; @@ -656,8 +651,7 @@ cursor.page = 1L; saveMzDayCursor(cursorKey, cursor); } MzGatherStatusEnum status = runDayPass(cid, cursorKey, cursor, MzGatherSlotEnum.TODAY, today, cursor.page, pageSize, maxPage, deadline); MzGatherStatusEnum status = runDayPass(cid, cursorKey, cursor, MzGatherSlotEnum.TODAY, today, cursor.page, pageSize, maxPage, deadline); if (status == MzGatherStatusEnum.DONE) { // 整轮刷新完成,页码归位,下一轮重新从第一页整轮刷新 cursor.page = 1L; @@ -672,9 +666,7 @@ * @param slot 断点槽位(今天整轮刷新 / 昨日收尾 / 手工回溯),决定中断页码记到哪个字段 * @return {@link MzGatherStatusEnum} 采集完成 / 预算用尽 / 接口连续失败提前结束 */ private MzGatherStatusEnum runDayPass(Long cid, String cursorKey, MzDayCursor cursor, MzGatherSlotEnum slot, LocalDate day, long startPage, long pageSize, long maxPage, LocalDateTime deadline) { private MzGatherStatusEnum runDayPass(Long cid, String cursorKey, MzDayCursor cursor, MzGatherSlotEnum slot, LocalDate day, long startPage, long pageSize, long maxPage, LocalDateTime deadline) { String startStr = day.format(MzGatherConstant.DAY_FMT); String endStr = day.plusDays(1).format(MzGatherConstant.DAY_FMT); @@ -705,8 +697,7 @@ // 本轮有跳过的页时,断点回到第一个失败页,保证下一轮先把这些页补回来 long resumePage = (skippedPages > 0 && firstFailPage > 0) ? firstFailPage : page; saveMzDayPage(cursorKey, cursor, slot, resumePage); log.warn("【dealHisDataMZ】本轮时间预算用尽,{} 本轮已采{}页{}条{},下次从第{}页继续,院区:{}", day, pageCount, total, skippedLog(skippedPages), resumePage, cid); log.warn("【dealHisDataMZ】本轮时间预算用尽,{} 本轮已采{}页{}条{},下次从第{}页继续,院区:{}", day, pageCount, total, skippedLog(skippedPages), resumePage, cid); return MzGatherStatusEnum.BUDGET_UP; } reqVO.setCurrent(page); @@ -740,13 +731,10 @@ firstFailPage = page; } consecutiveFail++; log.error("【dealHisDataMZ】接口层采集失败,已记 error 日志并跳过该页,本轮继续往后采;" + "院区:{},日期:{},第{}页,接口入参:{},异常:{}", cid, day, page, new Gson().toJson(reqVO), lastError.getMessage(), lastError); log.error("【dealHisDataMZ】接口层采集失败,已记 error 日志并跳过该页,本轮继续往后采;" + "院区:{},日期:{},第{}页,接口入参:{},异常:{}", cid, day, page, new Gson().toJson(reqVO), lastError.getMessage(), lastError); if (consecutiveFail >= MzGatherConstant.MAX_CONSECUTIVE_PAGE_FAIL) { // 连续多页接口失败,判定来未来接口整体不可用,本轮提前结束(不再空转后面几十页),断点留在第一个失败页 log.error("【dealHisDataMZ】连续{}页接口失败,判定来未来接口不可用,本轮提前结束;院区:{},日期:{},断点保留在第{}页", consecutiveFail, cid, day, firstFailPage); log.error("【dealHisDataMZ】连续{}页接口失败,判定来未来接口不可用,本轮提前结束;院区:{},日期:{},断点保留在第{}页", consecutiveFail, cid, day, firstFailPage); saveMzDayPage(cursorKey, cursor, slot, firstFailPage); return MzGatherStatusEnum.FAILED; } @@ -756,8 +744,7 @@ consecutiveFail = 0; if (CollectionUtils.isEmpty(pageData)) { log.info("【dealHisDataMZ】{} 第{}页无数据,采集结束,院区:{},本轮{}页共{}条{}", day, page, cid, pageCount, total, skippedLog(skippedPages)); log.info("【dealHisDataMZ】{} 第{}页无数据,采集结束,院区:{},本轮{}页共{}条{}", day, page, cid, pageCount, total, skippedLog(skippedPages)); return MzGatherStatusEnum.DONE; } pageCount++; @@ -777,7 +764,9 @@ } } /** 按槽位保存中断页码 */ /** * 按槽位保存中断页码 */ private void saveMzDayPage(String cursorKey, MzDayCursor cursor, MzGatherSlotEnum slot, long page) { if (slot == MzGatherSlotEnum.FINAL) { cursor.finalPage = page; @@ -789,12 +778,16 @@ saveMzDayCursor(cursorKey, cursor); } /** 日志里提示接口层失败跳过了几页(下一轮整轮刷新会重新采到) */ /** * 日志里提示接口层失败跳过了几页(下一轮整轮刷新会重新采到) */ private String skippedLog(long skippedPages) { return skippedPages > 0L ? ",接口失败跳过" + skippedPages + "页(下一轮整轮刷新会重新采到)" : ""; } /** 读取 sys_config 里的门急诊采集参数(机构 orgid=20001001),取不到或超范围时用枚举里的默认值 */ /** * 读取 sys_config 里的门急诊采集参数(机构 orgid=20001001),取不到或超范围时用枚举里的默认值 */ private long getMzConfigLong(MzGatherParamEnum param) { try { SysConfig config = new SysConfig(); @@ -806,8 +799,7 @@ if (value >= param.getMin() && value <= param.getMax()) { return value; } log.warn("【dealHisDataMZ】配置{}的值{}超出范围[{},{}],使用默认值{}", param.getKey(), value, param.getMin(), param.getMax(), param.getDefaultValue()); log.warn("【dealHisDataMZ】配置{}的值{}超出范围[{},{}],使用默认值{}", param.getKey(), value, param.getMin(), param.getMax(), param.getDefaultValue()); } } catch (Exception e) { log.warn("【dealHisDataMZ】读取配置{}失败,使用默认值{}", param.getKey(), param.getDefaultValue(), e); @@ -815,7 +807,9 @@ return param.getDefaultValue(); } /** 解析任务入参(要一次性回溯的日期),支持 yyyy-MM-dd HH:mm:ss 与 yyyy-MM-dd */ /** * 解析任务入参(要一次性回溯的日期),支持 yyyy-MM-dd HH:mm:ss 与 yyyy-MM-dd */ private LocalDateTime parseMzStartTime(String text) { LocalDateTime time = parseMzTimeQuiet(text); if (time == null && StringUtils.isNotBlank(text)) { @@ -824,7 +818,9 @@ return time; } /** 静默解析日期,支持 yyyy-MM-dd HH:mm:ss 与 yyyy-MM-dd,格式不符返回 null(不刷日志) */ /** * 静默解析日期,支持 yyyy-MM-dd HH:mm:ss 与 yyyy-MM-dd,格式不符返回 null(不刷日志) */ private LocalDateTime parseMzTimeQuiet(String text) { if (StringUtils.isBlank(text)) { return null; @@ -842,7 +838,9 @@ } } /** 保存门急诊采集断点(各轮次中断的页码) */ /** * 保存门急诊采集断点(各轮次中断的页码) */ private void saveMzDayCursor(String cursorKey, MzDayCursor cursor) { JSONObject json = new JSONObject(); json.put("day", cursor.day); @@ -856,7 +854,9 @@ redisCache.setCacheObject(cursorKey, json.toJSONString(), MzGatherConstant.CACHE_TTL_HOURS, TimeUnit.HOURS); } /** 读取门急诊采集断点,读不到或解析失败时返回空断点(等价于从第一页开始整轮刷新) */ /** * 读取门急诊采集断点,读不到或解析失败时返回空断点(等价于从第一页开始整轮刷新) */ private MzDayCursor loadMzDayCursor(String cursorKey) { MzDayCursor cursor = new MzDayCursor(); try { @@ -893,16 +893,24 @@ * (页码为 1 表示该轮次下一轮从第一页整轮开始) */ private static class MzDayCursor { /** 今天的整轮刷新 */ /** * 今天的整轮刷新 */ private String day; private long page = 1L; /** 昨天的收尾采集 */ /** * 昨天的收尾采集 */ private String finalDay; private long finalPage = 1L; /** 待做的一次性回溯 */ /** * 待做的一次性回溯 */ private String backfillDay; private long backfillPage = 1L; /** 已经补过的回溯日期,避免定时任务每轮重复补同一天 */ /** * 已经补过的回溯日期,避免定时任务每轮重复补同一天 */ private String backfillDone; } @@ -1153,8 +1161,7 @@ //2判断一下,当前的sort是不是等于需要执行的总个数,如果等于的话,说明是最后一个,直接将servuce_subtask的状态改成5,执行失败就行了 Long cs = currentSort.get(); if (cs.equals(serviceSubtaskPreachforms.size()) && isSubtaskDeadline(serviceSubtask, serviceSubtaskPreachforms)) { if (cs.equals(serviceSubtaskPreachforms.size()) && isSubtaskDeadline(serviceSubtask, serviceSubtaskPreachforms)) { serviceSubtask.setSendstate(7L); serviceSubtask.setRemark("处理补偿任务,当前处理最后补偿,全部执行失败(超时)"); @@ -1217,6 +1224,7 @@ serviceSubtaskPreachform.setSubid(serviceSubtask.getId()); //获取发送方式 String sendPreachform = getSendPreachform(serviceSubtaskPreachform, serviceSubtask); log.info("sendPreachform的发送方式是啥:{}", sendPreachform); if (sendPreachform.equals("-1") || serviceSubtask.getSendstate() == 6 || serviceSubtask.getSendstate() == 5 || serviceSubtask.getSendstate() == 4) { //说明已经全部遍历完了,当前的执行方式序号是最后一个了 或者 该患者已经随访结束(成功或失败了或不执行) log.error("说明已经全部遍历完了,当前的执行方式序号是最后一个了 或者 该患者已经随访结束(成功或失败了或不执行)"); @@ -1224,6 +1232,7 @@ } //先判断患者是不是还要发送 PatArchive patArchive = patArchiveService.selectPatArchiveByPatid(serviceSubtask.getPatid()); log.info("patArchive的值为:{}", patArchive); if (!Objects.isNull(patArchive) && patArchive.getNotrequiredFlag() == "1") { //不需要发送 serviceSubtask.setResult("患者不需要随访"); @@ -1247,8 +1256,7 @@ // 在开关恢复后永远丢失。保持 sendstate 不动,开关打开后下一次 longTaskSend/compensateTask // 会照常把它捞出来发出。 if ("3".equals(serviceSubtask.getType()) && !publicityFacade.isEnabled()) { log.warn("【宣教】模块未启用(参数配置 publicity.enabled=false),跳过宣教发送。subtaskId={}, patid={}", serviceSubtask.getId(), serviceSubtask.getPatid()); log.warn("【宣教】模块未启用(参数配置 publicity.enabled=false),跳过宣教发送。subtaskId={}, patid={}", serviceSubtask.getId(), serviceSubtask.getPatid()); return; } @@ -1337,6 +1345,7 @@ String url = ""; String format = String.format("%03X", serviceOutPath.getId()); //先判断一下发的是不是宣教 log.info("serviceSubtask.getType的值为:{}", serviceSubtask.getType()); if (!"3".equals(serviceSubtask.getType())) { sendMagParam.setPhone(serviceSubtask.getPhone()); url = StringUtils.isNotEmpty(req_path) ? localIP + ":" + req_path + "/wt?p=" + format : localIP + "/wt?p=" + format; @@ -1344,7 +1353,7 @@ //如果type是语音随访的话(说明补偿发送方式中有电话随访的方式,这里的外链就地址只能用/sf) if (serviceSubtask.getType().equals("1")) url = StringUtils.isNotEmpty(req_path) ? localIP + ":" + req_path + "/sf?p=" + format : localIP + "/sf?p=" + format; sendMagParam.setUrl(url); sendMagParam.setUrl(url); // SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique("wj.sms.template"); SysConfig sysConfig = new SysConfig(); @@ -1355,6 +1364,7 @@ } else { sysConfig = sysConfigMapper.checkConfigKeyUnique("wj.sms.template"); } log.info("sysConfig的值为:{}", sysConfig); if (!Objects.isNull(sysConfig)) { String smsContemt = sysConfig.getConfigValue(); // sendMagParam.setContent("您好,邀请您填写出院调查表,请点击" + sendMagParam.getUrl() + "填写。感谢您配合!"); @@ -1399,6 +1409,7 @@ //已收口到 ruoyi-message 模块,这里只负责组装入参并路由 log.info("active的值为:{}", active); SendResult smsSendResult = sendSmsByActive(serviceSubtask, sendMagParam); log.info("smsSendResult的值为:{}", smsSendResult); String isSuccess = smsSendResult.asLegacyFlag(); //任务发送记录 ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord(); @@ -1414,6 +1425,7 @@ serviceSubtaskRecordService.insertServiceSubtaskRecord(serviceSubtaskRecord); //发送完成后,修改状态 log.info("serviceSubtaskRecord的值为:{}", serviceSubtaskRecord); serviceSubtask.setSendstate(3L); if (StringUtils.isNotEmpty(isSuccess) && isSuccess.equals("true")) { serviceSubtask.setResult("success"); @@ -1617,8 +1629,7 @@ return "-1"; } // 当sort等于列表size时,并且所有sendstate都不等于9,则返回-1 if (currentSort.get().equals((long) serviceSubtaskPreachforms.size()) && isSubtaskDeadline(serviceSubtask, serviceSubtaskPreachforms)) { if (currentSort.get().equals((long) serviceSubtaskPreachforms.size()) && isSubtaskDeadline(serviceSubtask, serviceSubtaskPreachforms)) { if (allSendStateNot1AndNot9) { //说明全发送失败了 serviceSubtask.setSendstate(7L); @@ -1735,23 +1746,24 @@ // 获取daysBefore 0点到daysBefore-1 0点的时间范围 LocalDateTime todayZero = LocalDateTime.now().with(LocalTime.MIN); LocalDateTime daysBeforeZeroStart = todayZero.minusDays(daysBefore); LocalDateTime daysBeforeZeroEnd = todayZero.minusDays(daysBefore-1L); LocalDateTime daysBeforeZeroEnd = todayZero.minusDays(daysBefore - 1L); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String startTime = daysBeforeZeroStart.format(formatter); String endTime = daysBeforeZeroEnd.format(formatter); // HIS数据采集 log.info("【collectHISDaysBefore】HIS开始采集数据: startTime:{}, endtime:{}",startTime, endTime); log.info("【collectHISDaysBefore】HIS开始采集数据: startTime:{}, endtime:{}", startTime, endTime); // 立即返回,后台异步执行 CompletableFuture.runAsync(() -> { ichService.syncMedInhosp(startTime, endTime); }); log.info("【collectHISDaysBefore】HIS结束采集数据: startTime:{}, endtime:{}",startTime, endTime); log.info("【collectHISDaysBefore】HIS结束采集数据: startTime:{}, endtime:{}", startTime, endTime); } catch (Exception e) { log.error("【collectHISDaysBefore】HIS数据采集异常", e); } } public Boolean setSuccessPreachForm(ServiceSubtask serviceSubtask, String preachform, String remark, String failSendstate) { log.info("setSuccessPreachForm的入参为:serviceSubtask = {}, preachform = {}, remark = {}, failSendstate = {}", serviceSubtask, preachform, remark, failSendstate); if (preachform.equals("-1")) { //说明都不需要执行了 ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform(); @@ -1769,7 +1781,7 @@ serviceSubtaskPreachform.setTaskid(serviceSubtask.getTaskid()); serviceSubtaskPreachform.setOrgid(serviceSubtask.getOrgid()); List<ServiceSubtaskPreachform> sspCount = serviceSubtaskPreachformMapper.selectServiceSubtaskPreachformList(serviceSubtaskPreachform); log.info("sspCount的值为:{}", sspCount); serviceSubtaskPreachform.setPreachform(preachform); //1:未发送状态 serviceSubtaskPreachform.setSendstate("1"); @@ -1779,7 +1791,7 @@ // //人工的未发送状态,需要查2 // if (preachform.equals("1") && StringUtils.isNotEmpty(failSendstate) && Long.valueOf(failSendstate) > 2) // serviceSubtaskPreachform.setSendstate("2"); log.error("-----serviceSubtaskPreachform的入参是?{}", serviceSubtaskPreachform); log.info("-----serviceSubtaskPreachform的入参是?{}", serviceSubtaskPreachform); List<ServiceSubtaskPreachform> serviceSubtaskPreachforms = serviceSubtaskPreachformMapper.selectServiceSubtaskPreachformList(serviceSubtaskPreachform); if (CollectionUtils.isNotEmpty(serviceSubtaskPreachforms)) { ServiceSubtaskPreachform serviceSubtaskPreachform1 = serviceSubtaskPreachforms.get(0); @@ -1790,10 +1802,9 @@ } serviceSubtaskPreachform1.setRemark(remark); serviceSubtaskPreachformMapper.updateServiceSubtaskPreachform(serviceSubtaskPreachform1); log.info("---------serviceSubtaskPreachform1的入参是 : {}", serviceSubtaskPreachform1); //如果当前的preachform已经是最后一个了,那直接将serviceSubtask的sendstate状态改成7就行了(全超时了) if (serviceSubtaskPreachform1.getSort() == sspCount.size() && isSubtaskDeadline(serviceSubtask, serviceSubtaskPreachforms)) { if (serviceSubtaskPreachform1.getSort() == sspCount.size() && isSubtaskDeadline(serviceSubtask, serviceSubtaskPreachforms)) { if (failSendstate.equals("4") || failSendstate.equals("5")) { serviceSubtask.setCurrentPreachform(preachform); serviceSubtask.setSendstate(7L); @@ -1808,7 +1819,7 @@ log.error("【定时任务中该患者没有查询到属于他的发送方式,subid:{},prechform:{},orgid:{}】", serviceSubtask.getId(), preachform, serviceSubtask.getOrgid()); return false; } log.info("到这里了不????"); //将下一次的随访时间与当前执行方式记录一下 serviceSubtask.setVisitTime(getNextVisitTime(serviceSubtask.getId(), serviceSubtask.getTaskid(), serviceSubtask.getVisitTime())); serviceSubtask.setCurrentPreachform(preachform); @@ -1821,6 +1832,7 @@ } } serviceSubtask.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + "," + remark : remark); log.info("serviceSubtask的入参是:{}",serviceSubtask); serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); return true; } @@ -1926,15 +1938,15 @@ /** * 随访任务是否超时 * * @param serviceSubtask * @param serviceSubtaskPreachforms * @return */ public boolean isSubtaskDeadline(ServiceSubtask serviceSubtask, List<ServiceSubtaskPreachform> serviceSubtaskPreachforms){ public boolean isSubtaskDeadline(ServiceSubtask serviceSubtask, List<ServiceSubtaskPreachform> serviceSubtaskPreachforms) { if (ObjectUtils.isNotEmpty(serviceSubtask)) { //只有出院随访和专病随访有超时 if(StringUtils.isNotEmpty(serviceSubtask.getServiceType()) && !serviceSubtask.getServiceType().equals("2") && !serviceSubtask.getServiceType().equals("13")){ if (StringUtils.isNotEmpty(serviceSubtask.getServiceType()) && !serviceSubtask.getServiceType().equals("2") && !serviceSubtask.getServiceType().equals("13")) { return true; } } @@ -1945,9 +1957,7 @@ ServiceTask serviceTask = serviceTaskMapper.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); int sendDay = serviceTask != null && serviceTask.getSendDay() != null ? serviceTask.getSendDay().intValue() : 1; Date deadline = DateUtils.addDays(serviceSubtask.getEndtime(), sendDay); int sumCompensateTime = CollectionUtils.isEmpty(serviceSubtaskPreachforms) ? 0 : serviceSubtaskPreachforms.stream() .mapToInt(x -> x.getCompensateTime() == null ? 0 : Integer.parseInt(x.getCompensateTime())) .sum(); int sumCompensateTime = CollectionUtils.isEmpty(serviceSubtaskPreachforms) ? 0 : serviceSubtaskPreachforms.stream().mapToInt(x -> x.getCompensateTime() == null ? 0 : Integer.parseInt(x.getCompensateTime())).sum(); sumCompensateTime = sumCompensateTime + 1; // 只补偿最后一步自身的补偿时间(天),脏值一律按 0 兜底 deadline = DateUtils.addDays(deadline, sumCompensateTime); @@ -1969,7 +1979,7 @@ * <p> * ⚠️ 全仓只有 {@code ServiceSubtaskController#dealSubtaskDeadline} 一个调用者。 */ public void dealSubtaskDeadline(String orgid, Integer deadlineDay){ public void dealSubtaskDeadline(String orgid, Integer deadlineDay) { // ⚠️ 开关守卫:先判再干活,与 dealOutHospInfo / rollTaskGroupCycle 同口径。 // 关闭时直接返回,不扫描、不建"再次随访"子任务。 if (!inhospVisitFacade.isEnabled()) { @@ -1994,8 +2004,7 @@ serviceSubtaskVO.setVisitCount(1); serviceSubtaskVO.setTaskid(serviceTask.getTaskid()); serviceSubtaskVO.setEndOutHospTime(deadline); List<ServiceSubtask> serviceSubtaskList = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskVO); List<ServiceSubtask> serviceSubtaskList = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskVO); log.info("【dealSubtaskDeadline】获取到{}个超时任务, taskid={}", serviceSubtaskList.size(), serviceTask.getTaskid()); for (ServiceSubtask serviceSubtask : serviceSubtaskList) { PatMedInhosp patMedInhospVo = new PatMedInhosp(); @@ -2006,10 +2015,10 @@ PatMedInhosp patMedInhosp = new PatMedInhosp(); if (!patMedInhospList.isEmpty()) { patMedInhosp = patMedInhospList.get(0); }else { } else { continue; } if(ObjectUtils.isEmpty(serviceTask.getNexttaskid())){ if (ObjectUtils.isEmpty(serviceTask.getNexttaskid())) { continue; } ServiceTask nextServiceTask = serviceTaskMapper.selectServiceTaskByTaskid(serviceTask.getNexttaskid()); @@ -2018,8 +2027,7 @@ Integer type = 1; //新增再次随访任务 ServiceSubtask serviceSubtaskAgain = iPatMedInhospService.boxedServiceSubtask(nextServiceTask, patMedInhosp, patArchive, config, type); ServiceSubtask serviceSubtaskAgain = iPatMedInhospService.boxedServiceSubtask(nextServiceTask, patMedInhosp, patArchive, config, type); serviceSubtaskAgain.setId(null); serviceSubtaskAgain.setUpid(serviceSubtask.getId().intValue()); serviceSubtaskAgain.setVisitCount(serviceSubtask.getVisitCount() + 1); ruoyi-task/src/main/java/com/ruoyi/task/api/contract/VisitTaskCreateApiImpl.java
@@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.ruoyi.task.domain.entity.ServiceSubtaskEntity; import com.ruoyi.task.domain.ServiceSubtask; import com.ruoyi.task.domain.ServiceTask; @@ -334,8 +335,7 @@ // 见 OuthospSubtaskDraft 的类注释。 // ------------------------------------------------------------------ if (d instanceof com.ruoyi.common.core.service.dto.OuthospSubtaskDraft) { com.ruoyi.common.core.service.dto.OuthospSubtaskDraft o = (com.ruoyi.common.core.service.dto.OuthospSubtaskDraft) d; com.ruoyi.common.core.service.dto.OuthospSubtaskDraft o = (com.ruoyi.common.core.service.dto.OuthospSubtaskDraft) d; // hospno 已上提到父类 VisitSubtaskDraft(上面统一映射过了),这里不再重复 // 只有父类没有的字段才在这里补映射(其余如 sendname/sfzh/phone/sex/age 父类已有) e.setAdmindate(o.getAdmitdate()); ruoyi-task/src/main/java/com/ruoyi/task/service/impl/ServiceTaskServiceImpl.java
@@ -296,23 +296,6 @@ } } if (CollectionUtils.isNotEmpty(selectServiceSubtaskList)) { //将当前方式的发送状态改成已领取 ServiceSubtask serviceSubtask = selectServiceSubtaskList.get(0); ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform(); serviceSubtaskPreachform.setPreachform(serviceSubtask.getCurrentPreachform()); serviceSubtaskPreachform.setSubid(serviceSubtask.getId()); serviceSubtaskPreachform.setTaskid(serviceTask.getTaskid()); List<ServiceSubtaskPreachform> serviceSubtaskPreachforms = taskPreachformPort.selectServiceSubtaskPreachformList(serviceSubtaskPreachform); if (CollectionUtils.isNotEmpty(serviceSubtaskPreachforms)) { ServiceSubtaskPreachform ssp = serviceSubtaskPreachforms.get(0); //人工无-3已领取状态(service_subtask_preachform.sendstate = 3) if(StringUtils.isNotEmpty(ssp.getPreachform()) && !ssp.getPreachform().equals("1")){ ssp.setSendstate("3"); taskPreachformPort.updateServiceSubtaskPreachform(ssp); } } } if (serviceTask != null) { if (StringUtils.isNotEmpty(serviceTask.getKcb())) map.put("kcb", serviceTask.getKcb()); map.put("script", info);