liusheng
5 天以前 cc479e6a056785103996cc7ea67dbd5faa3e800e
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -118,8 +118,11 @@
    @Autowired
    private IServicePatientTempService iServicePatientTempService;
    @Autowired
    private IHNGatherPatArchiveService ihnGatherPatArchiveService;
    // ⚠️ 2026-10 移除:原 `IHNGatherPatArchiveService ihnGatherPatArchiveService` 字段。
    //    它在本类里**只**被 dealHisData 的河南分支使用(死字段检查:改前全文件仅 1 处使用),
    //    该分支已随方法体搬进 ruoyi-datacollection,故字段连带删除 ——
    //    留着它会让人误以为 smartor 的河南采集还有本类这个使用方
    //    (真正的使用方已是数据采集模块经契约 HisGatherApi 调用的 Bridge)。
    @Autowired
    private IServiceSLTDHealthcareRecordService serviceSLTDHealthcareRecordService;
@@ -160,8 +163,9 @@
    @Autowired
    private IServiceSubtaskRecordService serviceSubtaskRecordService;
    @Autowired
    IIcd10Service iIcd10Service;
    // ⚠️ 2026-10 移除:原 `IIcd10Service iIcd10Service` 字段。
    //    它在本类里**只**被 dealHisData 的"疾病字典增量采集"一行调用,已随方法体搬进
    //    ruoyi-datacollection(经契约 HisGatherApi#gatherIcd10 调回 smartor 的实现)。
    @Autowired
    IPatArchiveService patArchiveService;
@@ -227,8 +231,27 @@
    @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;
@@ -442,262 +465,53 @@
     * 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);
            }
            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);
    }
    /**