| | |
| | | import com.ruoyi.common.core.domain.entity.SysUser; |
| | | import com.ruoyi.common.core.domain.entity.SysUserDept; |
| | | import com.ruoyi.common.core.domain.entity.SysUserRole; |
| | | import com.ruoyi.common.utils.DateUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.smartor.common.enums.EmergencyLevelEnum; |
| | | import com.smartor.common.enums.ImportFlagEnum; |
| | |
| | | |
| | | @Value("${spring.profiles.active}") |
| | | private String active; |
| | | @Autowired |
| | | private ServiceSubtaskServiceImpl serviceSubtaskServiceImpl; |
| | | @Autowired |
| | | private ServiceSubtaskMapper serviceSubtaskMapper; |
| | | @Autowired |
| | | private PatMedInhospMapper patMedInhospMapper; |
| | | |
| | | @Autowired |
| | | private PatMedOuthospErrorMapper patMedOuthospErrorMapper; |
| | | |
| | | @Override |
| | | public List<ServiceSLTDInhospResDTO> queryHealthcareRecordList(ServiceSLTDInhospReqVO reqVO) { |
| | |
| | | log.info("【parseResponseData】成功解析dataList:{}条健康记录数据", dataList.size()); |
| | | List<ServiceSLTDInhospResDTO> resultList = new ArrayList<>(); |
| | | for (Map<String, Object> dataItem : dataList) { |
| | | resultList.add(convertToDTO(dataItem)); |
| | | try { |
| | | resultList.add(convertToDTO(dataItem)); |
| | | } catch (Exception e) { |
| | | // 单条数据转换失败不再让整页失败:写错误表交给人工处理,其余记录照常入库 |
| | | saveOuthospError(dataItem, "CONVERT", e.getMessage(), new Gson().toJson(dataItem), e); |
| | | } |
| | | } |
| | | log.info("【parseResponseData】成功解析resultList:{}条健康记录数据", resultList.size()); |
| | | |
| | |
| | | } |
| | | |
| | | private Boolean outHospitalDate(List<ServiceSLTDInhospResDTO> resultList) { |
| | | List<PatMedOuthosp> batchList = new ArrayList<>(); |
| | | |
| | | // 门急诊在 HIS 侧只能按天查,采集任务每半小时都会把当天上万个诊疗记录重新拉一遍, |
| | | // 其中绝大多数上一轮已经入库。这里先整页批量查重,把已入库的直接跳过, |
| | | // 只对新增记录做"档案处理 + 入库",否则每条重复数据都要走一遍 |
| | | // Redis加锁 + 档案查重 + 档案更新 + countBySerialnum + insert,一万条就是几万次 SQL,半小时跑不完。 |
| | | Set<String> pageSerialnums = new HashSet<>(); |
| | | List<ServiceSLTDInhospResDTO> uniqueList = new ArrayList<>(); |
| | | List<ServiceSLTDInhospResDTO> noSerialnumList = new ArrayList<>(); |
| | | Map<String, List<String>> serialnumsByDate = new LinkedHashMap<>(); |
| | | for (ServiceSLTDInhospResDTO dto : resultList) { |
| | | if (Objects.isNull(dto)) { |
| | | continue; |
| | | } |
| | | PatArchive patArchive = processPatientArchive(dto); |
| | | PatMedOuthosp patMedOuthosp = buildPatMedOuthosp(dto, patArchive); |
| | | batchList.add(patMedOuthosp); |
| | | String serialnum = dto.getHealthcareRecordNo(); |
| | | if (StringUtils.isEmpty(serialnum)) { |
| | | // 没有诊疗记录号就没法查重,保持原有行为照常处理(入库环节还会再兜一次) |
| | | log.warn("【outHospitalDate】诊疗记录号为空,无法查重,按新增处理:patientName={}", dto.getPatientName()); |
| | | noSerialnumList.add(dto); |
| | | continue; |
| | | } |
| | | if (!pageSerialnums.add(serialnum)) { |
| | | // 同一页内重复的记录,只处理一条 |
| | | continue; |
| | | } |
| | | uniqueList.add(dto); |
| | | // 按就诊日期分组(跨零点时最多两个日期),保证查重的分表和入库时分表路由一致 |
| | | String admitDateStr = formatAdmitDate(dto.getLastEncounterTime()); |
| | | serialnumsByDate.computeIfAbsent(admitDateStr == null ? "" : admitDateStr, k -> new ArrayList<>()).add(serialnum); |
| | | } |
| | | |
| | | // 一次 SQL 查出这一页里已经入库的 serialnum(取代逐条 countBySerialnum) |
| | | Set<String> existingSerialnums = new HashSet<>(); |
| | | for (Map.Entry<String, List<String>> entry : serialnumsByDate.entrySet()) { |
| | | try { |
| | | existingSerialnums.addAll(patMedOuthospService.selectExistingSerialnums(entry.getKey(), entry.getValue())); |
| | | } catch (Exception e) { |
| | | // 批量查重失败不影响正确性:下面入库时每条还会再查一次重,只是慢一些 |
| | | log.error("【outHospitalDate】批量查重失败,退化为逐条查重,就诊日期:{}", entry.getKey(), e); |
| | | } |
| | | } |
| | | |
| | | List<PatMedOuthosp> batchList = new ArrayList<>(); |
| | | int existSkipCount = 0; |
| | | for (ServiceSLTDInhospResDTO dto : uniqueList) { |
| | | if (existingSerialnums.contains(dto.getHealthcareRecordNo())) { |
| | | existSkipCount++; |
| | | continue; |
| | | } |
| | | PatMedOuthosp patMedOuthosp = buildNewOuthosp(dto); |
| | | if (patMedOuthosp != null) { |
| | | batchList.add(patMedOuthosp); |
| | | } |
| | | } |
| | | for (ServiceSLTDInhospResDTO dto : noSerialnumList) { |
| | | PatMedOuthosp patMedOuthosp = buildNewOuthosp(dto); |
| | | if (patMedOuthosp != null) { |
| | | batchList.add(patMedOuthosp); |
| | | } |
| | | } |
| | | log.info("【outHospitalDate】本页{}条,去重后{}条,已入库跳过{}条,待入库{}条", |
| | | resultList.size(), uniqueList.size(), existSkipCount, batchList.size()); |
| | | |
| | | // 批量插入,自动忽略重复 |
| | | if (CollectionUtils.isNotEmpty(batchList)) { |
| | | try { |
| | | //这里用自动忽略重复会有问题,如果表重新建,serialnum索引会丢失。就无法去重,所以还是需要内存里去重 |
| | | batchList = DistinctByProperty.distinctByProperty(batchList, PatMedOuthosp::getSerialnum); |
| | | Integer insertCount = 0; |
| | | for (PatMedOuthosp patMedOuthosp : batchList) { |
| | | insertCount += patMedOuthospService.insertPatMedOuthosp(patMedOuthosp); |
| | | //这里用自动忽略重复会有问题,如果表重新建,serialnum索引会丢失。就无法去重,所以还是需要内存里去重 |
| | | batchList = DistinctByProperty.distinctByProperty(batchList, PatMedOuthosp::getSerialnum); |
| | | int insertCount = 0; |
| | | int skipCount = 0; |
| | | List<String> failSerialnums = new ArrayList<>(); |
| | | for (PatMedOuthosp patMedOuthosp : batchList) { |
| | | try { |
| | | int inserted = patMedOuthospService.insertPatMedOuthosp(patMedOuthosp); |
| | | if (inserted > 0) { |
| | | insertCount += inserted; |
| | | } else { |
| | | // 返回0表示已存在(重复采集)或就诊时间为空,属于正常跳过,不算失败 |
| | | skipCount++; |
| | | } |
| | | } catch (Exception e) { |
| | | // 单条入库失败不再中断采集:落错误表交给人工处理,采集断点照常推进 |
| | | failSerialnums.add(patMedOuthosp.getSerialnum()); |
| | | saveOuthospError(null, patMedOuthosp, "INSERT", e.getMessage(), new Gson().toJson(patMedOuthosp), e); |
| | | } |
| | | } |
| | | log.info("批量插入门急诊记录,总数:{}, 实际插入:{}, 跳过(已存在/无就诊时间):{}, 落错误表:{}", |
| | | batchList.size(), insertCount, skipCount, failSerialnums.size()); |
| | | |
| | | log.info("批量插入门急诊记录,总数:{}, 实际插入:{}", batchList.size(), insertCount); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("批量插入门急诊记录失败", e); |
| | | if (!failSerialnums.isEmpty()) { |
| | | // 失败明细已逐条写进 pat_med_outhosp_error,这里只提示,不再抛异常阻断采集 |
| | | log.error("门急诊记录入库失败 {} 条,已写入 pat_med_outhosp_error 等人工处理,serialnum:{}", |
| | | failSerialnums.size(), failSerialnums); |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 处理单条新增记录的患者档案并构建门急诊记录; |
| | | * 失败时写错误表交给人工处理并返回 null,不影响同一页里其他记录 |
| | | */ |
| | | private PatMedOuthosp buildNewOuthosp(ServiceSLTDInhospResDTO dto) { |
| | | try { |
| | | PatArchive patArchive = processPatientArchive(dto); |
| | | return buildPatMedOuthosp(dto, patArchive); |
| | | } catch (Exception e) { |
| | | saveOuthospError(dto, null, "ARCHIVE", e.getMessage(), new Gson().toJson(dto), e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** 采集失败的原始记录来自接口返回的 Map(数据转换失败时还没有 DTO) */ |
| | | private void saveOuthospError(Map<String, Object> dataItem, String errorType, String errorMsg, |
| | | String rawData, Throwable cause) { |
| | | PatMedOuthospError error = new PatMedOuthospError(); |
| | | error.setSerialnum(getStringValue(dataItem, "healthcareRecordNo")); |
| | | error.setOuthospno(getStringValue(dataItem, "healthcareRecordNo")); |
| | | error.setPatname(getStringValue(dataItem, "patientName")); |
| | | error.setPatno(getStringValue(dataItem, "medicalRecordNo")); |
| | | error.setCampusid(getStringValue(dataItem, "campusId")); |
| | | error.setOrgid(getStringValue(dataItem, "orgId")); |
| | | doSaveOuthospError(error, errorType, errorMsg, rawData, cause); |
| | | } |
| | | |
| | | /** 采集失败的原始记录来自 DTO / 待入库的 PatMedOuthosp */ |
| | | private void saveOuthospError(ServiceSLTDInhospResDTO dto, PatMedOuthosp patMedOuthosp, String errorType, |
| | | String errorMsg, String rawData, Throwable cause) { |
| | | PatMedOuthospError error = new PatMedOuthospError(); |
| | | if (dto != null) { |
| | | error.setSerialnum(dto.getHealthcareRecordNo()); |
| | | error.setOuthospno(dto.getHealthcareRecordNo()); |
| | | error.setPatname(dto.getPatientName()); |
| | | error.setPatno(dto.getMedicalRecordNo()); |
| | | error.setCampusid(dto.getCampusId()); |
| | | error.setOrgid(dto.getOrgId()); |
| | | error.setAdmitdate(parseDate(dto.getLastEncounterTime())); |
| | | } |
| | | if (patMedOuthosp != null) { |
| | | if (StringUtils.isEmpty(error.getSerialnum())) { |
| | | error.setSerialnum(patMedOuthosp.getSerialnum()); |
| | | } |
| | | error.setOuthospno(patMedOuthosp.getOuthospno()); |
| | | error.setPatname(patMedOuthosp.getPatname()); |
| | | error.setPatno(patMedOuthosp.getPatno()); |
| | | error.setCampusid(patMedOuthosp.getCampusid()); |
| | | error.setOrgid(patMedOuthosp.getOrgid()); |
| | | error.setAdmitdate(patMedOuthosp.getAdmitdate()); |
| | | } |
| | | doSaveOuthospError(error, errorType, errorMsg, rawData, cause); |
| | | } |
| | | |
| | | /** |
| | | * 写门急诊采集失败记录表,交给人工处理。 |
| | | * 这里兜住一切异常:连错误表都写不进去时只打日志(带原始数据),绝不因为写错误表失败而中断采集 |
| | | */ |
| | | private void doSaveOuthospError(PatMedOuthospError error, String errorType, String errorMsg, |
| | | String rawData, Throwable cause) { |
| | | error.setErrorType(errorType); |
| | | error.setErrorMsg(truncate(errorMsg == null || errorMsg.isEmpty() ? String.valueOf(cause) : errorMsg, 1000)); |
| | | error.setRawData(rawData); |
| | | error.setFailCount(1); |
| | | error.setHandleStatus("0"); |
| | | error.setGatherDate(new SimpleDateFormat("yyyy-MM-dd").format(new Date())); |
| | | error.setCreateTime(DateUtils.getNowDate()); |
| | | try { |
| | | patMedOuthospErrorMapper.insertPatMedOuthospError(error); |
| | | log.error("【门急诊采集】{} 失败,已写入 pat_med_outhosp_error 等人工处理,serialnum={},患者={},原因={}", |
| | | errorType, error.getSerialnum(), error.getPatname(), error.getErrorMsg(), cause); |
| | | } catch (Exception ex) { |
| | | log.error("【门急诊采集】{} 失败,且写入错误表也失败,serialnum={},原始数据:{}", |
| | | errorType, error.getSerialnum(), rawData, ex); |
| | | } |
| | | } |
| | | |
| | | /** 截断超长文本,避免超出错误表字段长度导致落表失败 */ |
| | | private String truncate(String text, int maxLength) { |
| | | if (text == null) { |
| | | return null; |
| | | } |
| | | return text.length() <= maxLength ? text : text.substring(0, maxLength); |
| | | } |
| | | |
| | | private PatMedOuthosp buildPatMedOuthosp(ServiceSLTDInhospResDTO dto, PatArchive patArchive) { |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 取就诊日期的 yyyy-MM-dd 部分,与 buildPatMedOuthosp 里的 admitdate 保持完全一致的解析规则, |
| | | * 保证批量查重用的分表和入库时路由到的分表是同一张 |
| | | */ |
| | | private String formatAdmitDate(String lastEncounterTime) { |
| | | Date admitDate = parseDate(lastEncounterTime); |
| | | if (admitDate == null) { |
| | | return null; |
| | | } |
| | | return new SimpleDateFormat("yyyy-MM-dd").format(admitDate); |
| | | } |
| | | |
| | | private Boolean inHospitalDate(List<ServiceSLTDInhospResDTO> resultList, String cry) { |
| | | for (ServiceSLTDInhospResDTO dto : resultList) { |
| | | if (Objects.isNull(dto)) continue; |
| | |
| | | queryInhosp.setInhospstate("1"); |
| | | patMedInhospList = patMedInhospService.selectPatMedInhosp(queryInhosp); |
| | | if (CollectionUtils.isNotEmpty(patMedInhospList)) { |
| | | //如果不为空,则将入院时间进行更新 |
| | | PatMedInhosp pmOut = patMedInhospList.get(0); |
| | | //本次入院数据(这里 existingInhosps 必为空,取 existingInhosps.get(0) 会下标越界) |
| | | PatMedInhosp pmIn = patMedInhosp; |
| | | pmOut.setStarttime(pmIn.getStarttime()); |
| | | pmOut.setUpdateTime(new Date()); |
| | | patMedInhospService.updatePatMedInhosp(pmOut); |
| | | //省立同德需要将开始日期与入院日期相等的任务,改成不执行 |
| | | try { |
| | | if ("sltd".equals(active) && DateUtils.isSameDay(pmOut.getStarttime(), pmIn.getEndtime())) { |
| | | ServiceSubtask serviceSubtask = new ServiceSubtask(); |
| | | serviceSubtask.setInhospid(pmOut.getInhospid()); |
| | | serviceSubtask.setSendstate(4L); |
| | | serviceSubtaskMapper.updateServiceSubtaskByCondition(serviceSubtask); |
| | | } |
| | | //通过入院时间,和患者的编号去查一下出院时间,是否存在,如果存在,则该患者是再入院的,需要改成不执行 |
| | | //出院时间只比较到天,不比较时分秒 |
| | | if (pmIn.getStarttime() != null) { |
| | | Date dayStart = DateUtils.truncate(pmIn.getStarttime(), Calendar.DAY_OF_MONTH); |
| | | Date dayEnd = DateUtils.addDays(dayStart, 1); |
| | | List<PatMedInhosp> outInhosps = patMedInhospMapper.selectOutInhospByPatnoAndOutDate( |
| | | StringUtils.trim(pmIn.getPatno()), dayStart, dayEnd); |
| | | if (CollectionUtils.isNotEmpty(outInhosps)) { |
| | | //存在出院记录,说明该患者是再入院,把上一次出院产生的子任务改成不执行 |
| | | ServiceSubtask outSubtask = new ServiceSubtask(); |
| | | outSubtask.setInhospid(outInhosps.get(0).getInhospid()); |
| | | outSubtask.setSendstate(4L); |
| | | outSubtask.setRemark("患者再入院"); |
| | | outSubtask.setUpdateTime(new Date()); |
| | | serviceSubtaskMapper.updateServiceSubtaskByCondition(outSubtask); |
| | | log.info("患者再入院,将上次出院记录的子任务改为不执行:patno={}, 出院inhospid={}, 出院时间={}, 本次入院时间={}", |
| | | pmIn.getPatno(), outInhosps.get(0).getInhospid(), outInhosps.get(0).getEndtime(), pmIn.getStarttime()); |
| | | } |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | log.error("将出院时间等于入院时间的inhospid的任务更新失败了, inhospid={}", pmOut.getInhospid(), e); |
| | | } |
| | | return; |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | if (active.equals("nhfy") || active.equals("sltd")) { |
| | | //目前只有省立同德 |
| | | if (active.equals("sltd")) { |
| | | Map<String, String> drugInfoMap = queryMedicalOrders(dto); |
| | | if (drugInfoMap != null && !drugInfoMap.isEmpty()) { |
| | | patMedInhosp.setMedicalName(drugInfoMap.get("drugItemNames")); |