| | |
| | | import com.ruoyi.common.enums.ServiceFromEnum; |
| | | import com.ruoyi.common.utils.*; |
| | | import com.ruoyi.common.utils.http.HttpUtils; |
| | | import com.ruoyi.common.utils.sms.SmsUtils; |
| | | import com.ruoyi.message.SendResult; |
| | | import com.ruoyi.message.sms.SmsSendRequest; |
| | | import com.ruoyi.message.sms.SmsSenderFactory; |
| | |
| | | import com.ruoyi.system.service.ISysUserService; |
| | | import com.ruoyi.message.sms.provider.sltd.ws.MessageSend; |
| | | import com.smartor.common.QwenLLMUtil; |
| | | import com.smartor.common.SpecificArchiveConstants; |
| | | import com.smartor.domain.*; |
| | | import com.smartor.domain.VO.ServiceSubtaskDetailTraceVO; |
| | | import com.smartor.domain.entity.ServiceSubtaskEntity; |
| | | import com.ruoyi.task.domain.VO.ServiceSubtaskDetailTraceVO; |
| | | import com.smartor.mapper.*; |
| | | import com.smartor.service.*; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.collections4.CollectionUtils; |
| | | import org.apache.commons.lang3.ObjectUtils; |
| | | import org.apache.commons.lang3.math.NumberUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Component; |
| | |
| | | import com.ruoyi.template.domain.HeLibrary; |
| | | import com.ruoyi.inhospvisit.domain.PatMedInhosp; |
| | | import com.ruoyi.template.api.TemplateFacade; |
| | | import com.ruoyi.common.core.domain.entity.PatArchive; |
| | | import com.ruoyi.task.domain.entity.ServiceSubtaskEntity; |
| | | import com.ruoyi.task.domain.ServiceSubtask; |
| | | import com.ruoyi.task.domain.ServiceSubtaskRecord; |
| | | import com.ruoyi.task.domain.ServiceTask; |
| | | import com.ruoyi.task.domain.ServiceTaskSchedule; |
| | | import com.ruoyi.task.domain.ServiceTaskScheduleDetail; |
| | | import com.ruoyi.task.mapper.ServiceSubtaskMapper; |
| | | import com.ruoyi.task.mapper.ServiceTaskMapper; |
| | | import com.ruoyi.task.mapper.ServiceTaskScheduleMapper; |
| | | import com.ruoyi.task.mapper.ServiceTaskScheduleDetailMapper; |
| | | import com.ruoyi.task.domain.ServiceSubtaskDetailTrace; |
| | | import com.ruoyi.task.domain.VO.ServiceSubtaskDetailTraceVO; |
| | | import com.ruoyi.task.service.IServiceSubtaskDetailTraceService; |
| | | import com.ruoyi.task.domain.ServiceOutPath; |
| | | import com.ruoyi.task.service.IServiceOutPathService; |
| | | import com.ruoyi.task.domain.ServiceSubtaskPreachform; |
| | | import com.ruoyi.task.service.IServiceSubtaskService; |
| | | import com.ruoyi.task.domain.SendMagParam; |
| | | import com.ruoyi.task.service.IServiceSubtaskRecordService; |
| | | import com.ruoyi.task.mapper.ServiceSubtaskPreachformMapper; |
| | | import com.ruoyi.patarchive.service.IPatArchiveService; |
| | | import com.ruoyi.datacollection.service.IXHGatherPatArchiveService; |
| | | import com.ruoyi.datacollection.service.IServiceSLTDHealthcareRecordService; |
| | | import com.ruoyi.datacollection.domain.ServiceSLTDInhospReqVO; |
| | | import com.ruoyi.datacollection.domain.ServiceSLTDInhospResDTO; |
| | | |
| | | /** |
| | | * 定时任务调度测试 |
| | | * |
| | |
| | | @Autowired |
| | | private IServicePatientTempService iServicePatientTempService; |
| | | |
| | | @Autowired |
| | | private IHNGatherPatArchiveService ihnGatherPatArchiveService; |
| | | // ⚠️ 2026-10 移除:原 `IHNGatherPatArchiveService ihnGatherPatArchiveService` 字段。 |
| | | // 它在本类里**只**被 dealHisData 的河南分支使用(死字段检查:改前全文件仅 1 处使用), |
| | | // 该分支已随方法体搬进 ruoyi-datacollection,故字段连带删除 —— |
| | | // 留着它会让人误以为 smartor 的河南采集还有本类这个使用方 |
| | | // (真正的使用方已是数据采集模块经契约 HisGatherApi 调用的 Bridge)。 |
| | | |
| | | @Autowired |
| | | private IServiceSLTDHealthcareRecordService serviceSLTDHealthcareRecordService; |
| | |
| | | private ServiceSubtaskMapper serviceSubtaskMapper; |
| | | |
| | | @Autowired |
| | | private ServiceTaskScheduleMapper serviceTaskScheduleMapper; |
| | | |
| | | @Autowired |
| | | private ServiceTaskScheduleDetailMapper serviceTaskScheduleDetailMapper; |
| | | |
| | | @Autowired |
| | | private RedisCache redisCache; |
| | | |
| | | @Autowired |
| | | private IServiceSubtaskRecordService serviceSubtaskRecordService; |
| | | |
| | | @Autowired |
| | | IIcd10Service iIcd10Service; |
| | | // ⚠️ 2026-10 移除:原 `IIcd10Service iIcd10Service` 字段。 |
| | | // 它在本类里**只**被 dealHisData 的"疾病字典增量采集"一行调用,已随方法体搬进 |
| | | // ruoyi-datacollection(经契约 HisGatherApi#gatherIcd10 调回 smartor 的实现)。 |
| | | |
| | | @Autowired |
| | | IPatArchiveService patArchiveService; |
| | |
| | | @Autowired |
| | | private com.ruoyi.publicity.api.PublicityFacade publicityFacade; |
| | | |
| | | @Value("${appid}") |
| | | private String appid; |
| | | /** |
| | | * 数据采集模块的<b>常驻门面</b>。 |
| | | * <p> |
| | | * ⚠️ 2026-10 模块化:本类的 {@code dealHisData(String)} 方法体(连同私有帮手 |
| | | * {@code collectSegmentData})已整体搬进 {@code ruoyi-datacollection}, |
| | | * 本类只保留定时任务入口(sys_job 的 invoke_target 是 {@code ryTask.dealHisData('...')}, |
| | | * 入口名不能变)。站点分支、时间窗口、分段回溯、日志文案都在模块里,一行未改。 |
| | | * <p> |
| | | * 注入门面而不是模块内部 Service:门面<b>永远存在</b>,且只有门面判 |
| | | * {@code sys_config: datacollection.enabled} 开关。 |
| | | */ |
| | | @Autowired |
| | | private com.ruoyi.datacollection.api.DataCollectionFacade dataCollectionFacade; |
| | | |
| | | // ⚠️ 2026-10 删除:原 `@Value("${appid}") private String appid;` |
| | | // 它是「新华微信公众号推送」的遗留字段(提交 8590ef6a 引入,只被 |
| | | // xinHuaWXReq.setAppid(appid) 用过)。该推送逻辑已搬进 ruoyi-message |
| | | // (公众号凭据改由 sys_config.wx.mp.config.* 下发),本类里已无引用; |
| | | // 但它的 @Value 没有默认值,而 application-nhfy.yml / -xh.yml / -ls.yml |
| | | // 都没定义 appid 这个 key,这些 profile 启动时会直接报 |
| | | // `Could not resolve placeholder 'appid'`。留着是定时炸弹,故删除。 |
| | | |
| | | @Value("${server.port}") |
| | | private String port; |
| | |
| | | //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(); |
| | |
| | | // 获取“内容提醒测试人工号” |
| | | 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)); |
| | |
| | | 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); |
| | | //更新状态 |
| | |
| | | * HIS数据采集 |
| | | * 定时任务:采集患者信息、出院、门诊数据 |
| | | * todayZeroStr 当前的时间 例如:2026-05-06 00:00:00 |
| | | * <p> |
| | | * ⚠️ <b>2026-10 模块化</b>:方法体(连同私有帮手 {@code collectSegmentData})已整体搬进 |
| | | * {@code ruoyi-datacollection},实现见 |
| | | * {@code com.ruoyi.datacollection.service.impl.HisDataCollectionServiceImpl}。 |
| | | * 这里只保留<b>定时任务入口</b>:sys_job 的 invoke_target 仍是 |
| | | * {@code ryTask.dealHisData('...')},入口名不能变,所以本方法是<b>纯转发</b>。 |
| | | * <p> |
| | | * 关闭数据采集模块({@code sys_config: datacollection.enabled=false})时门面会抛 |
| | | * {@link com.ruoyi.datacollection.api.DataCollectionModuleDisabledException}; |
| | | * 本入口捕获后只打一条 WARN —— 那是"模块被主动关掉",不是采集失败, |
| | | * 不该让定时任务记成执行异常(否则运维会看到一片红色失败记录)。 |
| | | */ |
| | | public void dealHisData(String todayZeroStr) { |
| | | log.info("【dealHisData】开始执行HIS数据采集任务"); |
| | | // 获取昨天0点到今天0点的时间范围 |
| | | LocalDateTime todayZero = LocalDateTime.now().with(LocalTime.MIN); |
| | | if (StringUtils.isNotBlank(todayZeroStr)) { |
| | | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
| | | todayZero = LocalDateTime.parse(todayZeroStr, formatter); |
| | | } |
| | | LocalDateTime yesterdayZero = todayZero.minusDays(1); |
| | | log.info("【dealHisData】采集时间范围:{} ~ {}", yesterdayZero, todayZero); |
| | | |
| | | if (active.trim().equals("xh")) { |
| | | try { |
| | | // 采集入院数据 |
| | | log.info("【dealHisData】新华开始采集入院数据"); |
| | | ixhGatherPatArchiveService.getInHospDataGather("0", yesterdayZero, todayZero); |
| | | log.info("【dealHisData】新华入院数据采集完成"); |
| | | |
| | | // 采集出院数据 |
| | | log.info("【dealHisData】新华开始采集出院数据"); |
| | | ixhGatherPatArchiveService.getInHospDataGather("1", yesterdayZero, todayZero); |
| | | log.info("【dealHisData】新华出院数据采集完成"); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("【dealHisData】新华数据采集异常", e); |
| | | } |
| | | } else if (active.trim().equals("hn")) { |
| | | try { |
| | | // 河南数据采集 |
| | | HnDataGatherVO hnDataGatherVO = new HnDataGatherVO(); |
| | | log.info("【dealHisData】河南开始采集数据"); |
| | | Date startTime = Date.from(yesterdayZero.atZone(ZoneId.systemDefault()).toInstant()); |
| | | Date endTime = Date.from(todayZero.atZone(ZoneId.systemDefault()).toInstant()); |
| | | |
| | | hnDataGatherVO.setStartTime(startTime); |
| | | hnDataGatherVO.setEndTime(endTime); |
| | | ihnGatherPatArchiveService.hnDataGather(hnDataGatherVO); |
| | | log.info("【dealHisData】河南结束采集数据"); |
| | | } catch (Exception e) { |
| | | log.error("【dealHisData】河南数据采集异常", e); |
| | | } |
| | | } else if (active.trim().equals("sltd") || active.trim().equals("nhfy")) { |
| | | //省立同德与华南附一用同一套采集方法(都是来未来提供) |
| | | |
| | | try { |
| | | //获取是否需要采集用户、部门信息 |
| | | SysConfig config = new SysConfig(); |
| | | config.setConfigKey("sltd.gather.sysuser"); |
| | | config.setOrgid("20001001"); |
| | | List<SysConfig> sysConfigs = configService.selectConfigList(config); |
| | | |
| | | //获取省立同德要采集的院区 |
| | | SysConfig cf = new SysConfig(); |
| | | cf.setConfigKey("sltd.gather.campusid"); |
| | | cf.setOrgid("20001001"); |
| | | List<SysConfig> sysConfigcampusids = configService.selectConfigList(cf); |
| | | |
| | | List<Long> campusidList = new ArrayList<>(); |
| | | if (CollectionUtils.isNotEmpty(sysConfigcampusids)) { |
| | | String[] split = sysConfigcampusids.get(0).getConfigValue().split(","); |
| | | campusidList = Arrays.stream(split).map(Long::valueOf).collect(Collectors.toList()); |
| | | } else { |
| | | log.error("SysConfig中的院区ID为空!"); |
| | | return; |
| | | } |
| | | |
| | | //configValue = 1 不需要采集 configValue = 2需要采集 |
| | | if (CollectionUtils.isNotEmpty(sysConfigs) && sysConfigs.get(0).getConfigValue().equals("2")) { |
| | | log.info("【dealHisData】省立同德“部门信息”开始采集数据"); |
| | | ServiceSLTDDeptReqVO serviceSLTDDeptReqVO = new ServiceSLTDDeptReqVO(); |
| | | serviceSLTDDeptReqVO.setOrgId("20001001"); |
| | | serviceSLTDDeptReqVO.setCampusIds(campusidList); |
| | | Boolean aBoolean1 = serviceSLTDHealthcareRecordService.queryDeptWardAreaInfoList(serviceSLTDDeptReqVO); |
| | | log.info("【dealHisData】省立同德“部门信息”结束采集数据:{}", aBoolean1); |
| | | |
| | | Boolean aBoolean = false; |
| | | for (Long cid : campusidList) { |
| | | log.info("【dealHisData】省立同德“用户信息”开始采集数据,院区:{}", cid); |
| | | ServiceSLTDInhospReqVO reqVO = new ServiceSLTDInhospReqVO(); |
| | | reqVO.setOrgId("20001001"); |
| | | reqVO.setCampusId(cid); |
| | | ArrayList<String> statusList = new ArrayList<>(); |
| | | statusList.add("1"); |
| | | reqVO.setStatusList(statusList); |
| | | aBoolean = serviceSLTDHealthcareRecordService.queryHospUserInfoList(reqVO); |
| | | } |
| | | log.info("【dealHisData】省立同德“用户信息”结束采集数据:{}", aBoolean); |
| | | //疾病字典采集 |
| | | iIcd10Service.queryAddIcd10(); |
| | | } |
| | | |
| | | // nhfy/sltd回溯30天分段采集判断(每段最多4天) |
| | | boolean isBackfill = (active.trim().equals("nhfy") || active.trim().equals("sltd")) && StringUtils.isNotBlank(todayZeroStr); |
| | | if (isBackfill) { |
| | | DateTimeFormatter dateFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | LocalDate segStart = todayZero.toLocalDate().minusDays(30); |
| | | LocalDate segEndDate = todayZero.toLocalDate(); |
| | | int segCount = 0; |
| | | while (!segStart.isAfter(segEndDate)) { |
| | | LocalDate segEnd = segStart.plusDays(3); |
| | | if (segEnd.isAfter(segEndDate)) { |
| | | segEnd = segEndDate; |
| | | } |
| | | segCount++; |
| | | log.info("【dealHisData】{}回溯第{}段采集,日期范围:{} ~ {}", active.trim(), segCount, segStart.format(dateFmt), segEnd.format(dateFmt)); |
| | | for (Long cid : campusidList) { |
| | | collectSegmentData(cid, segStart.format(dateFmt), segEnd.format(dateFmt)); |
| | | } |
| | | segStart = segEnd.plusDays(1); |
| | | } |
| | | log.info("【dealHisData】{}回溯采集完成,共{}段", active.trim(), segCount); |
| | | } |
| | | |
| | | // 省立同德数据采集(非回溯模式才执行) |
| | | if (!isBackfill) { |
| | | for (Long cid : campusidList) { |
| | | log.info("【dealHisData】省立同德预入院开始采集数据,院区:{}", cid); |
| | | String nowTime = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); |
| | | ServiceSLTDInhospReqVO serviceSLTDInhospReqVO = new ServiceSLTDInhospReqVO(); |
| | | serviceSLTDInhospReqVO.setOrgId("20001001"); |
| | | serviceSLTDInhospReqVO.setCampusId(cid); |
| | | serviceSLTDInhospReqVO.setEncounterTimeStart(nowTime); |
| | | serviceSLTDInhospReqVO.setEncounterTimeEnd(nowTime); |
| | | if (active.trim().equals("nhfy")) { |
| | | serviceSLTDInhospReqVO.setEncounterTimeStart(yesterdayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); |
| | | serviceSLTDInhospReqVO.setEncounterTimeEnd(todayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); |
| | | } |
| | | List<String> list = new ArrayList<>(); |
| | | list.add("FH0109.22"); |
| | | list.add("FH0109.23"); |
| | | list.add("FH0109.53"); |
| | | serviceSLTDInhospReqVO.setStatusList(list); |
| | | // log.info("【dealHisData】省立同德“预入院”采集数据入参:{}", serviceSLTDInhospReqVO); |
| | | serviceSLTDHealthcareRecordService.queryHealthcareRecordList(serviceSLTDInhospReqVO); |
| | | log.info("【dealHisData】省立同德“预入院”結束采集数据,院区:{}", cid); |
| | | |
| | | log.info("【dealHisData】省立同德“入院”开始采集数据,院区:{}", cid); |
| | | serviceSLTDInhospReqVO.setOrgId("20001001"); |
| | | serviceSLTDInhospReqVO.setCampusId(cid); |
| | | serviceSLTDInhospReqVO.setEncounterTimeStart(null); |
| | | serviceSLTDInhospReqVO.setEncounterTimeEnd(null); |
| | | serviceSLTDInhospReqVO.setStartHeadTime(nowTime); |
| | | serviceSLTDInhospReqVO.setStartTailTime(nowTime); |
| | | if (active.trim().equals("nhfy")) { |
| | | serviceSLTDInhospReqVO.setStartHeadTime(yesterdayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); |
| | | serviceSLTDInhospReqVO.setStartTailTime(todayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); |
| | | } |
| | | List<String> listStr = new ArrayList<>(); |
| | | listStr.add("FH0109.24"); |
| | | serviceSLTDInhospReqVO.setStatusList(listStr); |
| | | // log.info("【dealHisData】省立同德“入院”采集数据入参:{}", serviceSLTDInhospReqVO); |
| | | serviceSLTDHealthcareRecordService.queryHealthcareRecordList(serviceSLTDInhospReqVO); |
| | | log.info("【dealHisData】省立同德“入院”結束采集数据,院区:{}", cid); |
| | | |
| | | log.info("【dealHisData】省立同德“出院”开始采集数据,院区:{}", cid); |
| | | serviceSLTDInhospReqVO.setStartHeadTime(null); |
| | | serviceSLTDInhospReqVO.setStartTailTime(null); |
| | | serviceSLTDInhospReqVO.setEncounterTimeStart(null); |
| | | serviceSLTDInhospReqVO.setEncounterTimeEnd(null); |
| | | serviceSLTDInhospReqVO.setPreOutHospitalHeadDate(nowTime); |
| | | serviceSLTDInhospReqVO.setPreOutHospitalTailDate(nowTime); |
| | | if (active.trim().equals("nhfy")) { |
| | | serviceSLTDInhospReqVO.setPreOutHospitalHeadDate(yesterdayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); |
| | | serviceSLTDInhospReqVO.setPreOutHospitalTailDate(todayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); |
| | | } |
| | | serviceSLTDInhospReqVO.setHealthcareRecordTypeList(null); |
| | | List<String> list2 = new ArrayList<>(); |
| | | // list2.add("FH0108.02"); |
| | | //出院 |
| | | list2.add("FH0109.27"); |
| | | //预出院 |
| | | list2.add("FH0109.25"); |
| | | serviceSLTDInhospReqVO.setStatusList(list2); |
| | | // log.info("【dealHisData】省立同德“出院”采集数据入参:{}", serviceSLTDInhospReqVO); |
| | | serviceSLTDHealthcareRecordService.queryHealthcareRecordList(serviceSLTDInhospReqVO); |
| | | log.info("【dealHisData】省立同德“出院”束采集数据,院区:{}", cid); |
| | | } |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | log.error("【dealHisData】省立同德数据采集异常", e); |
| | | } |
| | | try { |
| | | dataCollectionFacade.dealHisData(todayZeroStr); |
| | | } catch (com.ruoyi.datacollection.api.DataCollectionModuleDisabledException e) { |
| | | log.warn("【dealHisData】{}(本轮定时采集跳过)", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 分段采集数据(预入院、入院、出院) |
| | | * 用于nhfy/sltd回溯采集,每次最多查询7天数据 |
| | | * 在院患者"科室 / 病区 / 床位"刷新(南华附一,每天一次)。 |
| | | * <p> |
| | | * 在院患者会转科、转病区、换床,而 {@code pat_med_inhosp} 的位置字段只在入院时写过一次, |
| | | * 之后不会自己更新 —— 这里每天按患者查一遍 HIS 里<b>该患者当前的在院信息</b> |
| | | * ({@code dtcQueryHealthcareRecordList},与入院采集是同一个接口,按 |
| | | * {@code healthcareRecordNo} 单查 + {@code statusList=["FH0109.24"]}), |
| | | * 与库内比对后只把变化的字段写回去。实现与判定口径见 |
| | | * {@code com.ruoyi.datacollection.service.impl.InhospDeptRefreshServiceImpl}。 |
| | | * <p> |
| | | * 与 {@link #dealHisData(String)} 同样的降级口径:模块被关掉( |
| | | * {@code sys_config: datacollection.enabled=false})时门面抛 |
| | | * {@link com.ruoyi.datacollection.api.DataCollectionModuleDisabledException}, |
| | | * 这里只打一条 WARN —— 那是"模块被主动关掉",不该记成定时任务执行异常。 |
| | | * <p> |
| | | * sys_job 的 invoke_target 为 {@code ryTask.refreshInhospDeptInfo()}, |
| | | * 建议每天 03:30(避开 01:00 入院采集与 02:00 出院处理),并发策略选"禁止并发"。 |
| | | * |
| | | * @param cid 院区ID |
| | | * @param startDateStr 日期开始 yyyy-MM-dd |
| | | * @param endDateStr 日期结束 yyyy-MM-dd |
| | | * @return 本次实际更新的在院记录条数(0 = 无变化 / 站点不匹配 / 模块被关闭) |
| | | */ |
| | | private void collectSegmentData(Long cid, String startDateStr, String endDateStr) { |
| | | // // 预入院 |
| | | log.info("【dealHisData】预入院开始采集数据,院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr); |
| | | ServiceSLTDInhospReqVO reqVO = new ServiceSLTDInhospReqVO(); |
| | | reqVO.setOrgId("20001001"); |
| | | reqVO.setCampusId(cid); |
| | | reqVO.setEncounterTimeStart(startDateStr); |
| | | reqVO.setEncounterTimeEnd(endDateStr); |
| | | List<String> preAdmitStatus = new ArrayList<>(); |
| | | preAdmitStatus.add("FH0109.22"); |
| | | preAdmitStatus.add("FH0109.23"); |
| | | preAdmitStatus.add("FH0109.53"); |
| | | reqVO.setStatusList(preAdmitStatus); |
| | | log.info("【dealHisData】预入院采集数据入参:{}", reqVO); |
| | | public int refreshInhospDeptInfo() { |
| | | try { |
| | | serviceSLTDHealthcareRecordService.queryHealthcareRecordList(reqVO); |
| | | } catch (Exception e) { |
| | | // 单类数据失败不再向上抛:否则一段失败会让本次回溯后面的所有段都不执行 |
| | | log.error("【dealHisData】预入院采集失败,跳过该类继续。院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr, e); |
| | | return dataCollectionFacade.refreshInhospDeptInfo(); |
| | | } catch (com.ruoyi.datacollection.api.DataCollectionModuleDisabledException e) { |
| | | log.warn("【refreshInhospDeptInfo】{}(本轮在院信息刷新跳过)", e.getMessage()); |
| | | return 0; |
| | | } |
| | | log.info("【dealHisData】预入院終了采集数据,院区:{}", cid); |
| | | |
| | | // 入院 |
| | | log.info("【dealHisData】入院开始采集数据,院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr); |
| | | reqVO = new ServiceSLTDInhospReqVO(); |
| | | reqVO.setOrgId("20001001"); |
| | | reqVO.setCampusId(cid); |
| | | reqVO.setStartHeadTime(startDateStr); |
| | | reqVO.setStartTailTime(endDateStr); |
| | | List<String> admitStatus = new ArrayList<>(); |
| | | admitStatus.add("FH0109.24"); |
| | | reqVO.setStatusList(admitStatus); |
| | | log.info("【dealHisData】入院采集数据入参:{}", reqVO); |
| | | try { |
| | | serviceSLTDHealthcareRecordService.queryHealthcareRecordList(reqVO); |
| | | } catch (Exception e) { |
| | | // 单类数据失败不再向上抛:否则一段失败会让本次回溯后面的所有段都不执行 |
| | | log.error("【dealHisData】入院采集失败,跳过该类继续。院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr, e); |
| | | } |
| | | log.info("【dealHisData】入院終了采集数据,院区:{}", cid); |
| | | |
| | | // 出院 |
| | | log.info("【dealHisData】出院开始采集数据,院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr); |
| | | reqVO = new ServiceSLTDInhospReqVO(); |
| | | reqVO.setOrgId("20001001"); |
| | | reqVO.setCampusId(cid); |
| | | reqVO.setPreOutHospitalHeadDate(startDateStr); |
| | | reqVO.setPreOutHospitalTailDate(endDateStr); |
| | | List<String> dischargeStatus = new ArrayList<>(); |
| | | //出院 |
| | | dischargeStatus.add("FH0109.27"); |
| | | //预出院 |
| | | dischargeStatus.add("FH0109.25"); |
| | | reqVO.setStatusList(dischargeStatus); |
| | | log.info("【dealHisData】出院采集数据入参:{}", reqVO); |
| | | try { |
| | | serviceSLTDHealthcareRecordService.queryHealthcareRecordList(reqVO); |
| | | } catch (Exception e) { |
| | | // 单类数据失败不再向上抛:否则一段失败会让本次回溯后面的所有段都不执行 |
| | | log.error("【dealHisData】出院采集失败,跳过该类继续。院区:{},时间段:{} ~ {}", cid, startDateStr, endDateStr, e); |
| | | } |
| | | log.info("【dealHisData】出院结束采集数据,院区:{}", cid); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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) { |
| | |
| | | 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(); |
| | |
| | | // 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; |
| | |
| | | |
| | | // 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; |
| | |
| | | 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; |
| | |
| | | * @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); |
| | | |
| | |
| | | // 本轮有跳过的页时,断点回到第一个失败页,保证下一轮先把这些页补回来 |
| | | 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); |
| | |
| | | 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; |
| | | } |
| | |
| | | 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++; |
| | |
| | | } |
| | | } |
| | | |
| | | /** 按槽位保存中断页码 */ |
| | | /** |
| | | * 按槽位保存中断页码 |
| | | */ |
| | | private void saveMzDayPage(String cursorKey, MzDayCursor cursor, MzGatherSlotEnum slot, long page) { |
| | | if (slot == MzGatherSlotEnum.FINAL) { |
| | | cursor.finalPage = page; |
| | |
| | | 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(); |
| | |
| | | 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); |
| | |
| | | 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)) { |
| | |
| | | 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; |
| | |
| | | } |
| | | } |
| | | |
| | | /** 保存门急诊采集断点(各轮次中断的页码) */ |
| | | /** |
| | | * 保存门急诊采集断点(各轮次中断的页码) |
| | | */ |
| | | private void saveMzDayCursor(String cursorKey, MzDayCursor cursor) { |
| | | JSONObject json = new JSONObject(); |
| | | json.put("day", cursor.day); |
| | |
| | | redisCache.setCacheObject(cursorKey, json.toJSONString(), MzGatherConstant.CACHE_TTL_HOURS, TimeUnit.HOURS); |
| | | } |
| | | |
| | | /** 读取门急诊采集断点,读不到或解析失败时返回空断点(等价于从第一页开始整轮刷新) */ |
| | | /** |
| | | * 读取门急诊采集断点,读不到或解析失败时返回空断点(等价于从第一页开始整轮刷新) |
| | | */ |
| | | private MzDayCursor loadMzDayCursor(String cursorKey) { |
| | | MzDayCursor cursor = new MzDayCursor(); |
| | | try { |
| | |
| | | * (页码为 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; |
| | | } |
| | | |
| | |
| | | * 处理患者信息,进入子任务表(任务组) |
| | | * <p> |
| | | * ⚠️ 2025 修复(开关失效):原走 smartor 遗留实现,不判开关(同 {@link #dealOutHospInfo()})。 |
| | | * 现走模块门面。注意模块侧的 {@code dealOutHospInfoBySchedule} 内部仍会转发回 smartor |
| | | * ({@code OtherVisitApiBridge},见该类注释),但<b>开关在门面这一层判</b>,所以关闭时不会走到那里。 |
| | | * 现走模块门面。模块侧 {@code dealOutHospInfoBySchedule} 经 {@code OtherVisitApi} 契约 |
| | | * 回调 {@code ruoyi-task/api/contract/OtherVisitApiImpl}(2026-11 起实现整簇搬进 task 模块, |
| | | * smartor 侧的 {@code OtherVisitApiBridge} 已删除),但<b>开关在门面这一层判</b>, |
| | | * 所以关闭时不会走到那里。 |
| | | */ |
| | | public void dealOutHospInfoBySchedule() { |
| | | String config = configService.selectConfigByKey("visit.early.day"); |
| | |
| | | serviceSubtaskList.addAll(compensateServiceSubtaskList); |
| | | } |
| | | for (ServiceSubtask serviceSubtask : serviceSubtaskList) { |
| | | //currentPreachform为空,说明该子任务还没有执行过任何一种发送方式(sendstate=1,属于首次补偿), |
| | | //这里直接交给sfHandlle处理,它会按sort=1的第一种发送方式发送,与getSendPreachform中的处理保持一致 |
| | | if (StringUtils.isEmpty(serviceSubtask.getCurrentPreachform())) { |
| | | log.warn("【compensateTask】subid:{},currentPreachform为空,按第一种发送方式补偿处理", serviceSubtask.getId()); |
| | | sfHandlle(serviceSubtask); |
| | | continue; |
| | | } |
| | | //根据当前的执行方式,获取下一种执行方式 |
| | | ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform(); |
| | | serviceSubtaskPreachform.setTaskid(serviceSubtask.getTaskid()); |
| | |
| | | Optional<Long> currentSort = serviceSubtaskPreachforms.stream().filter(item -> serviceSubtask.getCurrentPreachform().equals(item.getPreachform())).map(ServiceSubtaskPreachform::getSort).findFirst(); |
| | | Optional<Long> id = serviceSubtaskPreachforms.stream().filter(item -> serviceSubtask.getCurrentPreachform().equals(item.getPreachform())).map(ServiceSubtaskPreachform::getId).findFirst(); |
| | | if (currentSort.isPresent()) { |
| | | // 方案B:只处理当前子任务——公众号已领取(service_subtask.sendstate=1 + current_preachform=5) |
| | | // 且未填写时,按默认选项完成,避免进入下一种发送方式或最后一步超时。 |
| | | // 先用内存字段快速过滤,其他医院/任务/发送方式不会进入下面的默认完成逻辑。 |
| | | if (IServiceSubtaskService.WECHAT_DEFAULT_COMPLETE_ORGID.equals(serviceSubtask.getOrgid()) |
| | | && "2".equals(serviceSubtask.getType()) |
| | | && "5".equals(serviceSubtask.getCurrentPreachform()) |
| | | && Long.valueOf(1L).equals(serviceSubtask.getSendstate()) |
| | | && !Long.valueOf(1L).equals(serviceSubtask.getSubmit())) { |
| | | try { |
| | | if (iServiceSubtaskService.completeWechatClaimedByDefault(serviceSubtask)) { |
| | | log.info("【compensateTask】subid:{},微信公众号已领取未填写,按默认选项完成", serviceSubtask.getId()); |
| | | continue; |
| | | } |
| | | } catch (Exception e) { |
| | | // 单个子任务失败不影响其他任务,回滚后继续原补偿流程 |
| | | log.error("【compensateTask】微信公众号默认完成失败,subid:{},继续原补偿流程", serviceSubtask.getId(), e); |
| | | } |
| | | } |
| | | |
| | | //1先检查一下,是不是有执行状态是完成的(怕之前已经有完的了,没有将servuce_subtask的状态改成功,这里再检查一下) |
| | | boolean finishState = serviceSubtaskPreachforms.stream().allMatch(item -> item.getSendstate().equals("9")); |
| | | if (finishState) { |
| | |
| | | //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("处理补偿任务,当前处理最后补偿,全部执行失败(超时)"); |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 按机构查询配置值(同一 key 在不同机构下可配置不同值) |
| | | * |
| | | * @param configKey 配置key |
| | | * @param orgid 机构ID |
| | | * @return 配置值,未配置返回 null |
| | | */ |
| | | private String getConfigValue(String configKey, String orgid) { |
| | | SysConfig query = new SysConfig(); |
| | | query.setConfigKey(configKey); |
| | | query.setOrgid(orgid); |
| | | List<SysConfig> sysConfigs = configService.selectConfigList(query); |
| | | if (CollectionUtils.isNotEmpty(sysConfigs)) { |
| | | return sysConfigs.get(0).getConfigValue(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 随访-补偿处理 |
| | | * |
| | | * @param serviceSubtask |
| | |
| | | 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("说明已经全部遍历完了,当前的执行方式序号是最后一个了 或者 该患者已经随访结束(成功或失败了或不执行)"); |
| | |
| | | } |
| | | //先判断患者是不是还要发送 |
| | | PatArchive patArchive = patArchiveService.selectPatArchiveByPatid(serviceSubtask.getPatid()); |
| | | log.info("patArchive的值为:{}", patArchive); |
| | | if (!Objects.isNull(patArchive) && patArchive.getNotrequiredFlag() == "1") { |
| | | //不需要发送 |
| | | serviceSubtask.setResult("患者不需要随访"); |
| | |
| | | // 在开关恢复后永远丢失。保持 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; |
| | | } |
| | | |
| | |
| | | serviceOutPath.setOrgid(serviceSubtask.getOrgid()); |
| | | iServiceOutPathService.insertServiceOutPath(serviceOutPath); |
| | | String url = ""; |
| | | String format = String.format("%03X", serviceOutPath.getId()); |
| | | String format = ShortCode.encode(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; |
| | |
| | | //如果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(); |
| | |
| | | } else { |
| | | sysConfig = sysConfigMapper.checkConfigKeyUnique("wj.sms.template"); |
| | | } |
| | | if (!Objects.isNull(sysConfig)) { |
| | | log.info("sysConfig的值为:{}", sysConfig); |
| | | if (!Objects.isNull(sysConfig) && StringUtils.isNotEmpty(sysConfig.getConfigValue())) { |
| | | String smsContemt = sysConfig.getConfigValue(); |
| | | // sendMagParam.setContent("您好,邀请您填写出院调查表,请点击" + sendMagParam.getUrl() + "填写。感谢您配合!"); |
| | | sendMagParam.setContent(smsContemt.replace("${wjurl}", sendMagParam.getUrl())); |
| | |
| | | sendMagParam.setUrl(url); |
| | | |
| | | SysConfig sysConfig = sysConfigMapper.checkConfigKeyUnique("xj.sms.template"); |
| | | if (!Objects.isNull(sysConfig)) { |
| | | if (!Objects.isNull(sysConfig) && StringUtils.isNotEmpty(sysConfig.getConfigValue())) { |
| | | String smsContemt = sysConfig.getConfigValue(); |
| | | // sendMagParam.setContent("您好,邀请您填写出院宣教调查表,请点击" + sendMagParam.getUrl() + "查看。感谢您配合!"); |
| | | sendMagParam.setContent(smsContemt.replace("${xjurl}", sendMagParam.getUrl())); |
| | |
| | | |
| | | } |
| | | |
| | | //内容为空说明短信模板未配置或宣教/通知内容为空,直接按发送失败处理,避免后续发送时出现空指针 |
| | | if (StringUtils.isEmpty(sendMagParam.getContent())) { |
| | | log.error("【sfHandlle】长期任务短信内容为空,无法发送,subid:{},taskid:{},type:{},机构ID:{}", serviceSubtask.getId(), serviceSubtask.getTaskid(), serviceSubtask.getType(), serviceSubtask.getOrgid()); |
| | | setSuccessPreachForm(serviceSubtask, sendPreachform, "长期任务短信发送失败:短信内容为空(短信模板未配置或内容为空), 时间: " + now, "5"); |
| | | return; |
| | | } |
| | | |
| | | serviceOutPath.setRadix(format); |
| | | serviceOutPath.setUpdateTime(new Date()); |
| | | serviceOutPath.setUrl(url); |
| | |
| | | //短信发送:各站点对接方式差异较大(报文/签名/token/返回判定都不同), |
| | | //已收口到 ruoyi-message 模块,这里只负责组装入参并路由 |
| | | log.info("active的值为:{}", active); |
| | | SendResult smsSendResult = sendSmsByActive(serviceSubtask, sendMagParam); |
| | | String isSuccess = smsSendResult.asLegacyFlag(); |
| | | String isSuccess = ""; |
| | | if ("ls".equals(active) && "47226079133110211G1001".equals(serviceSubtask.getOrgid())) { |
| | | //丽水妇幼走中国移动云MAS,不依赖 ls.msg.info 与 token; |
| | | //ruoyi-message 的 ls 通道尚未覆盖云MAS,故此处保留 SmsUtils 直连 |
| | | SmsUtils.YunMasConfig yunMasConfig = SmsUtils.YunMasConfig.of(getConfigValue("ls.msg.yunmas", serviceSubtask.getOrgid())); |
| | | SmsUtils.SmsResult yunMasSmsResult = SmsUtils.sendYunMasSms(yunMasConfig, sendMagParam.getPhone(), sendMagParam.getContent()); |
| | | log.info("【sfHandlle】云MAS短信发送结果:ok={},rspcod={},msgGroup={},message={}", |
| | | yunMasSmsResult.isOk(), yunMasSmsResult.getRspcod(), yunMasSmsResult.getMsgGroup(), yunMasSmsResult.getMessage()); |
| | | isSuccess = yunMasSmsResult.isOk() ? "true" : "false"; |
| | | if (!yunMasSmsResult.isOk()) { |
| | | log.error("【sfHandlle】云MAS短信发送失败,subid:{},手机号:{},原因:{}", |
| | | serviceSubtask.getId(), sendMagParam.getPhone(), yunMasSmsResult.getMessage()); |
| | | } |
| | | } else { |
| | | SendResult smsSendResult = sendSmsByActive(serviceSubtask, sendMagParam); |
| | | log.info("smsSendResult的值为:{}", smsSendResult); |
| | | isSuccess = smsSendResult.asLegacyFlag(); |
| | | } |
| | | //任务发送记录 |
| | | ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord(); |
| | | serviceSubtaskRecord.setTaskid(serviceSubtask.getTaskid().toString()); |
| | |
| | | serviceSubtaskRecordService.insertServiceSubtaskRecord(serviceSubtaskRecord); |
| | | |
| | | //发送完成后,修改状态 |
| | | log.info("serviceSubtaskRecord的值为:{}", serviceSubtaskRecord); |
| | | serviceSubtask.setSendstate(3L); |
| | | if (StringUtils.isNotEmpty(isSuccess) && isSuccess.equals("true")) { |
| | | serviceSubtask.setResult("success"); |
| | |
| | | serviceOutPath.setCreateTime(new Date()); |
| | | serviceOutPath.setOrgid(serviceSubtask.getOrgid()); |
| | | iServiceOutPathService.insertServiceOutPath(serviceOutPath); |
| | | String format = String.format("%03X", serviceOutPath.getId()); |
| | | String format = ShortCode.encode(serviceOutPath.getId()); |
| | | serviceOutPath.setRadix(format); |
| | | serviceOutPath.setUpdateTime(new Date()); |
| | | serviceOutPath.setUrl(url); |
| | |
| | | //任务发送记录 |
| | | ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord(); |
| | | serviceSubtaskRecord.setTaskid(serviceSubtask.getTaskid().toString()); |
| | | serviceSubtaskRecord.setSubtaskId(serviceSubtask.getId()); |
| | | serviceSubtaskRecord.setUuid(UUID.randomUUID().toString()); |
| | | serviceSubtaskRecord.setTasktype(serviceSubtask.getType()); |
| | | serviceSubtaskRecord.setPreachform("4"); |
| | |
| | | private String resolveShiyiCampus(ServiceSubtask serviceSubtask) { |
| | | SysDept sysDeptVo = new SysDept(); |
| | | List<SysDept> sysDepts = new ArrayList<>(); |
| | | if (ObjectUtils.isNotEmpty(serviceSubtask.getDeptcode())) { |
| | | if (ObjectUtils.isNotEmpty(serviceSubtask.getLeaveldeptcode())) { |
| | | sysDeptVo.setDeptType("1"); |
| | | sysDeptVo.setOrgid(serviceSubtask.getOrgid()); |
| | | sysDeptVo.setDeptCode(serviceSubtask.getDeptcode()); |
| | | sysDeptVo.setDeptCode(serviceSubtask.getLeaveldeptcode()); |
| | | sysDeptService.selectDeptList(sysDeptVo); |
| | | } else if (ObjectUtils.isNotEmpty(serviceSubtask.getLeavehospitaldistrictcode())) { |
| | | sysDeptVo.setDeptType("2"); |
| | |
| | | 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); |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 采集丽水市妇幼保健院门诊接诊完成记录,并按配置生成妇科/儿科档案。 |
| | | * 定时任务默认采集昨天 00:00:00 ~ 今天 00:00:00。 |
| | | */ |
| | | public void collectMchArchive() { |
| | | try { |
| | | LocalDateTime todayZero = LocalDateTime.now().with(LocalTime.MIN); |
| | | LocalDateTime yesterdayZero = todayZero.minusDays(1); |
| | | Date startTime = Date.from(yesterdayZero.atZone(ZoneId.systemDefault()).toInstant()); |
| | | Date endTime = Date.from(todayZero.atZone(ZoneId.systemDefault()).toInstant()); |
| | | log.info("【collectMchArchive】开始采集丽水妇幼门诊接诊完成记录"); |
| | | ichService.collectMchOuthospAndGenerateArchive( |
| | | SpecificArchiveConstants.LISHUI_MCH_ORG_ID, startTime, endTime); |
| | | log.info("【collectMchArchive】丽水妇幼门诊接诊完成记录采集结束"); |
| | | } catch (Exception e) { |
| | | log.error("【collectMchArchive】丽水妇幼门诊接诊完成记录采集异常", e); |
| | | } |
| | | } |
| | | |
| | | public void collectHISYesterday() { |
| | | try { |
| | | // 获取昨天0点到今天0点的时间范围 |
| | |
| | | // 获取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(); |
| | |
| | | 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"); |
| | |
| | | // //人工的未发送状态,需要查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); |
| | |
| | | } |
| | | 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); |
| | |
| | | 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); |
| | |
| | | } |
| | | } |
| | | serviceSubtask.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + "," + remark : remark); |
| | | log.info("serviceSubtask的入参是:{}",serviceSubtask); |
| | | serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); |
| | | return true; |
| | | } |
| | |
| | | * @return |
| | | */ |
| | | private String getObject(ServiceSubtask serviceSubtask, String scriptContent) { |
| | | //内容为空时直接返回,避免下面的replace出现空指针 |
| | | if (StringUtils.isEmpty(scriptContent)) { |
| | | return scriptContent; |
| | | } |
| | | Map<String, Map<String, String>> param = getParam(serviceSubtask.getTaskid()); |
| | | for (Map<String, String> map : param.values()) { |
| | | for (String key : map.keySet()) { |
| | | scriptContent = scriptContent.replace(key, StringUtils.isNotEmpty(map.get(key)) ? map.get(key) : ""); |
| | | } |
| | | } |
| | | if (StringUtils.isEmpty(scriptContent)) { |
| | | return scriptContent; |
| | | } |
| | | scriptContent = scriptContent.replace("${name}", StringUtils.isNotEmpty(serviceSubtask.getSendname()) ? serviceSubtask.getSendname() : ""); |
| | | scriptContent = scriptContent.replace("${dzz}", StringUtils.isNotEmpty(serviceSubtask.getAddr()) ? serviceSubtask.getAddr() : ""); |
| | |
| | | |
| | | /** |
| | | * 随访任务是否超时 |
| | | * |
| | | * @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; |
| | | } |
| | | } |
| | | if (Objects.isNull(serviceSubtask) || CollectionUtils.isEmpty(serviceSubtaskPreachforms)) { |
| | | return false; |
| | | } |
| | | // 基准时刻:出院时间 + 发送日(sendDay 缺省按 1),并兜底避免 NPE/脏数据 |
| | | 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())) |
| | | Date deadline; |
| | | if (serviceTask != null && "5".equals(serviceTask.getAppltype())) { |
| | | // 任务组:基准 = schedule_benchmark 解析出的时间 + day_offset,不叠加 send_day |
| | | deadline = resolveTaskGroupDeadlineBase(serviceSubtask); |
| | | if (deadline == null) { |
| | | log.warn("【isSubtaskDeadline】subid:{} 任务组基准时间为空,无法计算超时,按未超时处理", serviceSubtask.getId()); |
| | | return false; |
| | | } |
| | | } else { |
| | | // 非任务组:基准 = 出院时间 + 发送日(sendDay 缺省按 1) |
| | | if (serviceSubtask.getEndtime() == null) { |
| | | log.warn("【isSubtaskDeadline】subid:{} endtime为空,无法计算超时,按未超时处理", serviceSubtask.getId()); |
| | | return false; |
| | | } |
| | | int sendDay = serviceTask != null && serviceTask.getSendDay() != null ? serviceTask.getSendDay().intValue() : 1; |
| | | deadline = DateUtils.addDays(serviceSubtask.getEndtime(), sendDay); |
| | | } |
| | | int sumCompensateTime = serviceSubtaskPreachforms.stream() |
| | | .mapToInt(x -> NumberUtils.toInt(x.getCompensateTime(), 0)) |
| | | .sum(); |
| | | sumCompensateTime = sumCompensateTime + 1; |
| | | // 只补偿最后一步自身的补偿时间(天),脏值一律按 0 兜底 |
| | | // 补偿时间脏值一律按 0 兜底,最后再加 1 天宽限 |
| | | deadline = DateUtils.addDays(deadline, sumCompensateTime); |
| | | // 出院(发送)时间 + 本步补偿时间 小于当前时间 -> 超时 |
| | | return deadline.getTime() < new Date().getTime(); |
| | | } |
| | | |
| | | /** |
| | | * 任务组基准时间:按 schedule_benchmark 解析后加 day_offset。 |
| | | * 0/其它-自定义时间 1-入院 2-出院 3-就诊 4-入组 |
| | | */ |
| | | private Date resolveTaskGroupDeadlineBase(ServiceSubtask serviceSubtask) { |
| | | if (serviceSubtask.getScheduleId() == null || serviceSubtask.getScheduleDetailId() == null) { |
| | | return null; |
| | | } |
| | | ServiceTaskSchedule schedule = serviceTaskScheduleMapper.selectServiceTaskScheduleById(serviceSubtask.getScheduleId()); |
| | | ServiceTaskScheduleDetail detail = serviceTaskScheduleDetailMapper.selectServiceTaskScheduleDetailById(serviceSubtask.getScheduleDetailId()); |
| | | if (schedule == null || detail == null || detail.getDayOffset() == null) { |
| | | return null; |
| | | } |
| | | Date base; |
| | | Integer benchmark = schedule.getScheduleBenchmark() == null ? 0 : schedule.getScheduleBenchmark(); |
| | | switch (benchmark) { |
| | | case 1: |
| | | base = serviceSubtask.getStarttime(); |
| | | break; |
| | | case 2: |
| | | base = serviceSubtask.getEndtime(); |
| | | break; |
| | | case 3: |
| | | base = serviceSubtask.getAdmindate(); |
| | | break; |
| | | case 4: |
| | | base = serviceSubtask.getCreateTime(); |
| | | break; |
| | | default: |
| | | base = schedule.getScheduleBenchmarkCustomTime(); |
| | | break; |
| | | } |
| | | return base == null ? null : DateUtils.addDays(base, detail.getDayOffset()); |
| | | } |
| | | |
| | | /** |
| | | * 处理超时随访任务(随访超时 → 生成"再次随访") |
| | | * <p> |
| | | * 扫描「出院满 deadlineDay 天仍未完成」的首次随访任务(sendstate=7 + visit_count=1), |
| | | * 按当前任务的 nexttaskid 生成再次随访任务,科室固定为随访中心科室。 |
| | | * <p> |
| | | * 注意:截止时间必须在循环外计算一次。历史实现把 {@code deadlineDay = -deadlineDay} |
| | | * 写在循环体内,每轮取反一次,导致偶数轮用「今天 + deadlineDay」做截止时间, |
| | | * 把出院不满 28 天的超时任务也扫了进来。 |
| | | * <p> |
| | | * ⚠️ 2025 修复(开关失效):本方法原不判任何模块开关 —— |
| | | * 关掉 {@code inhospvisit.enabled} 照样建"再次随访"子任务。 |
| | | * <p> |
| | | * <b>为什么这里是"入口判开关"而不是像 {@link #dealOutHospInfo()} 那样切到门面</b>: |
| | | * 模块侧只提供了 {@code InhospVisitApi#buildAgainVisitDraft}(组装草稿), |
| | | * <b>没有</b>完整的"扫描超时 → 落库 → 回写原任务状态"入口, |
| | | * 而本方法自己组装并 insert(见下方 {@code insertServiceSubtask})。 |
| | | * 造一个模块入口属于新增能力、超出"只解决开关"的范围,所以取最小改动:入口守卫。 |
| | | * 超时扫描与回写原任务状态的入口由 service 侧承接(见下方 {@code addSubTaskAgainByDeadline})。 |
| | | * <p> |
| | | * ⚠️ 全仓只有 {@code ServiceSubtaskController#dealSubtaskDeadline} 一个调用者。 |
| | | * |
| | | * @param orgid 机构ID |
| | | * @param deadlineDay 出院满多少天算超时(正数,如 28) |
| | | */ |
| | | public void dealSubtaskDeadline(String orgid, Integer deadlineDay){ |
| | | public void dealSubtaskDeadline(String orgid, Integer deadlineDay) { |
| | | // ⚠️ 开关守卫:先判再干活,与 dealOutHospInfo / rollTaskGroupCycle 同口径。 |
| | | // 关闭时直接返回,不扫描、不建"再次随访"子任务。 |
| | | if (!inhospVisitFacade.isEnabled()) { |
| | |
| | | st.setNexttaskflag("1"); |
| | | st.setOrgid(orgid); |
| | | List<ServiceTask> serviceTasks = serviceTaskMapper.selectServiceTaskList(st); |
| | | |
| | | // 截止时间只算一次:出院时间 <= 今天 - deadlineDay |
| | | int days = Math.abs(ObjectUtils.isEmpty(deadlineDay) ? 28 : deadlineDay); |
| | | Date deadline = DateUtils.addDays(new Date(), -days); |
| | | log.info("【dealSubtaskDeadline】开始扫描, orgid={}, day={}, deadline={}, 任务数={}", |
| | | orgid, days, deadline, serviceTasks.size()); |
| | | |
| | | for (ServiceTask serviceTask : serviceTasks) { |
| | | deadlineDay = -deadlineDay; |
| | | Date deadline = DateUtils.addDays(new Date(), deadlineDay); |
| | | if (ObjectUtils.isEmpty(serviceTask.getNexttaskid())) { |
| | | continue; |
| | | } |
| | | //通过任务ID拿到患者信息,并且随访时间得是今天之前的 |
| | | ServiceSubtaskEntity serviceSubtaskVO = new ServiceSubtaskEntity(); |
| | | serviceSubtaskVO.setOrgid(orgid); |
| | |
| | | = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskVO); |
| | | log.info("【dealSubtaskDeadline】获取到{}个超时任务, taskid={}", serviceSubtaskList.size(), serviceTask.getTaskid()); |
| | | for (ServiceSubtask serviceSubtask : serviceSubtaskList) { |
| | | PatMedInhosp patMedInhospVo = new PatMedInhosp(); |
| | | patMedInhospVo.setOrgid(serviceSubtask.getOrgid()); |
| | | patMedInhospVo.setInhospid(serviceSubtask.getInhospid()); |
| | | patMedInhospVo.setCry(1); |
| | | List<PatMedInhosp> patMedInhospList = iPatMedInhospService.selectPatMedInhospList(patMedInhospVo); |
| | | PatMedInhosp patMedInhosp = new PatMedInhosp(); |
| | | if (!patMedInhospList.isEmpty()) { |
| | | patMedInhosp = patMedInhospList.get(0); |
| | | }else { |
| | | continue; |
| | | } |
| | | if(ObjectUtils.isEmpty(serviceTask.getNexttaskid())){ |
| | | continue; |
| | | } |
| | | ServiceTask nextServiceTask = serviceTaskMapper.selectServiceTaskByTaskid(serviceTask.getNexttaskid()); |
| | | PatArchive patArchive = patArchiveService.selectPatArchiveByPatid(serviceSubtask.getPatid()); |
| | | String config = configService.selectConfigByKey("visit.early.day"); |
| | | Integer type = 1; |
| | | |
| | | //新增再次随访任务 |
| | | ServiceSubtask serviceSubtaskAgain = iPatMedInhospService.boxedServiceSubtask(nextServiceTask, |
| | | patMedInhosp, patArchive, config, type); |
| | | serviceSubtaskAgain.setId(null); |
| | | serviceSubtaskAgain.setUpid(serviceSubtask.getId().intValue()); |
| | | serviceSubtaskAgain.setVisitCount(serviceSubtask.getVisitCount() + 1); |
| | | serviceSubtaskAgain.setSendstate(2L); |
| | | serviceSubtaskAgain.setCreateTime(new Date()); |
| | | serviceSubtaskAgain.setUpdateTime(new Date()); |
| | | serviceSubtaskAgain.setVisitType(serviceSubtask.getVisitType()); |
| | | serviceSubtaskAgain.setVisitDeptCode(serviceSubtask.getVisitDeptCode()); |
| | | serviceSubtaskAgain.setVisitDeptName(serviceSubtask.getVisitDeptName()); |
| | | int i = serviceSubtaskMapper.insertServiceSubtask(serviceSubtaskAgain); |
| | | if (i == 1) { |
| | | //再次随访任务创建成功后,原任务改为失败状态 |
| | | serviceSubtask.setSendstate(5L); |
| | | serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); |
| | | try { |
| | | // 生成逻辑统一收口到 service:锁 + 幂等 + 事务 + 发送方式明细初始化 |
| | | String result = iServiceSubtaskService.addSubTaskAgainByDeadline(serviceSubtask); |
| | | log.info("【dealSubtaskDeadline】生成再次随访, subId={}, taskid={}, 结果={}", |
| | | serviceSubtask.getId(), serviceTask.getTaskid(), result); |
| | | } catch (Exception e) { |
| | | // 单条失败不影响其他患者 |
| | | log.error("【dealSubtaskDeadline】生成再次随访失败, subId=" + serviceSubtask.getId() |
| | | + ", taskid=" + serviceTask.getTaskid(), e); |
| | | } |
| | | } |
| | | } |