liusheng
8 天以前 f0a09ee996ab2605869844d6a3d812709e2bf0fa
smartor/src/main/java/com/smartor/service/impl/ServiceSLTDHealthcareRecordServiceImpl.java
@@ -8,11 +8,19 @@
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;
import com.ruoyi.common.utils.http.HttpUtils;
import com.smartor.common.DistinctByProperty;
import com.smartor.domain.*;
import com.ruoyi.outpatientvisit.domain.PatMedOuthospError;
import com.ruoyi.outpatientvisit.domain.PatMedOuthosp;
import com.ruoyi.inhospvisit.api.InhospVisitFacade;
import com.ruoyi.inhospvisit.domain.PatMedInhosp;
import com.smartor.mapper.*;
import com.ruoyi.outpatientvisit.mapper.PatMedOuthospErrorMapper;
import com.smartor.service.IPatArchiveService;
import com.smartor.service.IPatMedInhospService;
import com.smartor.service.IPatMedOuthospService;
@@ -34,6 +42,10 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.ruoyi.common.core.domain.entity.PatArchive;
import com.ruoyi.task.domain.ServiceSubtask;
import com.ruoyi.task.mapper.ServiceSubtaskMapper;
import com.ruoyi.task.service.impl.ServiceSubtaskServiceImpl;
@Slf4j
@Service
@@ -63,6 +75,12 @@
    private SysUserRole2Mapper sysUserRoleMapper;
    @Autowired
    private MedicationItemMapper medicationItemMapper;
    @Autowired
    private MedicationDoseInfoMapper medicationDoseInfoMapper;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Value("${sltd_pub_path}")
@@ -74,13 +92,31 @@
    @Value("${lwl_app_key}")
    private String APP_KEY;
    @Value("${spring.profiles.active}")
    private String active;
    @Autowired
    private ServiceSubtaskServiceImpl serviceSubtaskServiceImpl;
    @Autowired
    private ServiceSubtaskMapper serviceSubtaskMapper;
    @Autowired
    private InhospVisitFacade inhospVisitFacade;
    @Autowired
    private PatMedOuthospErrorMapper patMedOuthospErrorMapper;
    @Override
    public List<ServiceSLTDInhospResDTO> queryHealthcareRecordList(ServiceSLTDInhospReqVO reqVO) {
        try {
            Map<String, Object> requestParams = buildRequestParams(reqVO);
            Map<String, String> headers = buildRequestHeaders();
            log.info("请求参数出院数据地址:{},appKey:{}", sltdPubPath + "/osj/hbos-thirdparty-integration/standard/common/healthcareRecord/dtcQueryHealthcareRecordList", APP_KEY);
            String result = HttpUtils.sendPostByHeader(sltdPubPath + "/osj/hbos-thirdparty-integration/standard/common/healthcareRecord/dtcQueryHealthcareRecordList", new Gson().toJson(requestParams), headers);
            String result = null;
            if (reqVO.getSize() == null && reqVO.getCurrent() == null)
                result = HttpUtils.sendPostByHeader(sltdPubPath + "/osj/hbos-thirdparty-integration/standard/common/healthcareRecord/dtcQueryHealthcareRecordList", new Gson().toJson(requestParams), headers);
            else
                result = HttpUtils.sendPostByHeader(sltdPubPath + "/osj/hbos-thirdparty-integration/standard/common/healthcareRecord/dtcPageHealthcareRecordList", new Gson().toJson(requestParams), headers);
            log.info("queryHealthcareRecordList--result返回的结果值:{}", result == null ? "空值" : "有值");
            String cry = determineCry(reqVO);
            log.info("cry的值为:{}", cry);
@@ -90,6 +126,60 @@
        } catch (Exception e) {
            log.error("【queryHealthcareRecordList】调用省立同德健康记录查询接口异常,请求参数:{}", reqVO, e);
            throw new RuntimeException("调用省立同德健康记录查询接口失败:" + e.getMessage());
        }
    }
    @Override
    /**
     * 查询药品信息列表
     * @return 返回实际获取到的药品数据条数,如果出错则返回 null
     */ public Integer queryMedicationItemList(ServiceSLTDInhospReqVO reqVO) {
        try {
            Map<String, Object> requestParams = buildRequestParams(reqVO);
            Map<String, String> headers = buildRequestHeaders();
            log.info("请求参药品信息地址:{},appKey:{}", sltdPubPath + "/hbos-thirdparty-integration/standard/base/mc/service/medication/queryMedicationInfoPage", APP_KEY);
            String result = HttpUtils.sendPostByHeader(sltdPubPath + "/hbos-thirdparty-integration/standard/base/mc/service/medication/queryMedicationInfoPage", new Gson().toJson(requestParams), headers);
            List<Map<String, Object>> dataList = getDataList(result, 2);
            log.info("【queryMedicationItemList】共获取到{}条药品数据", dataList.size());
            int successCount = 0;
            int failCount = 0;
            for (Map<String, Object> dataItem : dataList) {
                try {
                    MedicationItem medicationItem = convertToMedication(dataItem);
                    log.info("准备插入药品数据:itemName={}, itemCode={}", medicationItem.getItemName(), medicationItem.getItemCode());
                    medicationItem.setCreateTime(new Date());
                    medicationItemMapper.insertMedicationItem(medicationItem);
                    // 如果插入成功且返回了自增 ID,则插入子表
                    if (medicationItem.getId() != null) {
                        List<MedicationDoseInfo> doseInfoList = medicationItem.getDoseInfoList();
                        if (CollectionUtils.isNotEmpty(doseInfoList)) {
                            for (MedicationDoseInfo doseInfo : doseInfoList) {
                                try {
                                    doseInfo.setMedicationItemId(medicationItem.getId());
                                    medicationDoseInfoMapper.insertMedicationDoseInfo(doseInfo);
                                    log.debug("插入剂量信息成功:itemId={}, doseUnit={}", medicationItem.getId(), doseInfo.getDoseUnit());
                                } catch (Exception e) {
                                    log.warn("插入剂量信息失败:itemId={}, doseUnit={}, 错误:{}", medicationItem.getId(), doseInfo.getDoseUnit(), e.getMessage(), e);
                                    failCount++;
                                }
                            }
                        }
                    }
                    successCount++;
                    log.info("✓ 药品数据插入成功:{}", medicationItem.getItemName());
                } catch (Exception e) {
                    log.warn("【queryMedicationItemList】单条药品数据插入失败:itemName={}, itemCode={}, 错误:{}", dataItem.get("itemName"), dataItem.get("itemCode"), e.getMessage(), e);
                    failCount++;
                    // 继续处理下一条数据,不中断整个循环
                }
            }
            log.info("【queryMedicationItemList】批量插入完成,总数:{}, 成功:{}, 失败:{}", dataList.size(), successCount, failCount);
            // 返回实际获取到的药品数据条数,用于分页判断是否还有更多数据
            return dataList.size();
        } catch (Exception e) {
            log.error("【queryMedicationItemList】调用省立同德药品信息查询接口异常,请求参数:{}", reqVO, e);
            throw new RuntimeException(e);
        }
    }
@@ -132,7 +222,7 @@
            Map<String, String> headers = buildRequestHeaders();
            String result = HttpUtils.sendPostByHeader(sltdPubPath + "/osj/hbos-thirdparty-integration/standard/common/dept/queryDeptList", new Gson().toJson(params), headers);
            log.info("【queryDeptWardAreaInfoList】接口响应结果:{}", StringUtils.isEmpty(result) ? "空的" : "不空");
            List<Map<String, Object>> dataList = getDataList(result);
            List<Map<String, Object>> dataList = getDataList(result, 1);
            log.info("-----------dataList接口响应结果:{}", dataList.size());
            for (Map<String, Object> dataItem : dataList) {
                SysDept sysDept = new SysDept();
@@ -188,20 +278,6 @@
        return true;
    }
    @Override
    public void aa(Map<String, Object> map) {
        List<String> types = new ArrayList<>();
        types.add("FH0109.27");
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            String jsonString = objectMapper.writeValueAsString(map);
            parseResponseData(jsonString, types, "1", "20001001");
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
    /**
     * 采集医院用户信息
@@ -215,7 +291,7 @@
        Map<String, String> headers = buildRequestHeaders();
        String result = HttpUtils.sendPostByHeader(sltdPubPath + "/osj/hbos-thirdparty-integration/standard/common/staff/queryStaffList", new Gson().toJson(requestParams), headers);
        log.info("【queryHealthcareRecordList】接口响应结果是否有值:{}", StringUtils.isEmpty(result) ? "没值" : "有值");
        List<Map<String, Object>> dataList = getDataList(result);
        List<Map<String, Object>> dataList = getDataList(result, 1);
        for (Map<String, Object> dataItem : dataList) {
            SysUser sysUser = new SysUser();
            if (StringUtils.isEmpty(getStringValue(dataItem, "accountNo"))) {
@@ -351,17 +427,25 @@
    }
    private String determineCry(ServiceSLTDInhospReqVO reqVO) {
        if (CollectionUtils.isEmpty(reqVO.getStatusList())) return null;
        if (reqVO.getStatusList().contains("FH0109.24")) {
            //入院
            return "0";
        } else if (reqVO.getStatusList().contains("FH0109.27")) {
            //出院
            return "1";
        } else if (reqVO.getStatusList().contains("FH0109.22") || reqVO.getStatusList().contains("FH0109.23") || reqVO.getStatusList().contains("FH0109.53")) {
            //预入院
            return "3";
        if (CollectionUtils.isEmpty(reqVO.getStatusList()) && CollectionUtils.isEmpty(reqVO.getHealthcareRecordTypeList()))
            return null;
        if (reqVO.getStatusList() != null) {
            if (reqVO.getStatusList().contains("FH0109.24")) {
                //入院
                return "0";
            } else if (reqVO.getStatusList().contains("FH0109.27")) {
                //出院
                return "1";
            } else if (reqVO.getStatusList().contains("FH0109.22") || reqVO.getStatusList().contains("FH0109.23") || reqVO.getStatusList().contains("FH0109.53")) {
                //预入院
                return "3";
            }
        }
        if (reqVO.getHealthcareRecordTypeList() != null) {
            if (reqVO.getHealthcareRecordTypeList().contains("FH0108.03") || reqVO.getHealthcareRecordTypeList().contains("FH0108.01")) {
                //门急诊
                return "4";
            }
        }
        return null;
    }
@@ -372,7 +456,7 @@
            params.put("orgId", Long.parseLong(reqVO.getOrgId()));
        }
        if (reqVO.getCampusId() != null) {
            params.put("campusId", reqVO.getCampusId());
            params.put("campusIds", reqVO.getCampusId());
        }
        if (reqVO.getStartHeadTime() != null) {
            params.put("startHeadTime", reqVO.getStartHeadTime());
@@ -427,12 +511,18 @@
    private List<ServiceSLTDInhospResDTO> parseResponseData(String result, List<String> types, String cry, String campusId) {
        try {
            List<Map<String, Object>> dataList = getDataList(result);
            List<Map<String, Object>> dataList = getDataList(result, cry.equals("4") ? 2 : 1);
            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.size());
            log.info("【parseResponseData】成功解析resultList:{}条健康记录数据", resultList.size());
            processResultList(resultList, types, cry, campusId);
            return resultList;
@@ -442,11 +532,20 @@
        }
    }
    public List<Map<String, Object>> getDataList(String result) {
    /**
     * 解析接口返回数据
     *
     * @param result 接口返回数据
     * @param flag   1代表,数组在data里,2代表数组在data/records里
     * @return 解析后的数据列表
     */
    public List<Map<String, Object>> getDataList(String result, Integer flag) {
        Gson gson = new Gson();
        Type mapType = new TypeToken<Map<String, Object>>() {
        }.getType();
        Map<String, Object> responseMap = gson.fromJson(result, mapType);
        log.info("【parseResponseData】接口返回数据的responseMap:{}", Objects.isNull(responseMap) ? "空的" : "有值");
        Number codeNum = (Number) responseMap.get("code");
        String code = BigDecimal.valueOf(codeNum.longValue()).toPlainString();
        if (StringUtils.isEmpty(code) || !code.equals("200")) {
@@ -455,15 +554,30 @@
        }
        Object dataObj = responseMap.get("data");
        if (flag == 1) {
            dataObj = responseMap.get("data");
        } else if (flag == 2) {
            //门急诊的分页数据是在records中
            dataObj = ((Map<String, Object>) responseMap.get("data")).get("records");
            log.info("【parseResponseData】接口返回数据的dataObj:{}", Objects.isNull(dataObj) ? "空的" : "有值");
        }
        if (dataObj == null) {
            log.info("【parseResponseData】接口返回数据为空");
            return new ArrayList<>();
        }
        String dataJson = gson.toJson(dataObj);
        Type listType = new TypeToken<List<Map<String, Object>>>() {
        }.getType();
        List<Map<String, Object>> dataList = gson.fromJson(dataJson, listType);
        // 兼容data为数组或对象两种情况:接口正常时data为[{...}]数组,
        // 异常或空数据时可能返回{}对象,直接按List解析会抛JsonSyntaxException
        List<Map<String, Object>> dataList;
        if (dataObj instanceof List) {
            String dataJson = gson.toJson(dataObj);
            Type listType = new TypeToken<List<Map<String, Object>>>() {
            }.getType();
            dataList = gson.fromJson(dataJson, listType);
        } else {
            log.info("【parseResponseData】接口返回data不是数组,而是对象类型,data内容:{}", gson.toJson(dataObj));
            return new ArrayList<>();
        }
        // 使用 Set 去重,确保没有重复的数据
        Set<Map<String, Object>> uniqueDataSet = new HashSet<>(dataList);
@@ -516,34 +630,184 @@
    }
    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) {
@@ -584,11 +848,35 @@
        }
    }
    /**
     * 取就诊日期的 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) {
        int failCount = 0;
        for (ServiceSLTDInhospResDTO dto : resultList) {
            if (Objects.isNull(dto)) continue;
            PatArchive patArchive = processPatientArchive(dto);
            processPatientInhospInfo(dto, patArchive, cry);
            try {
                PatArchive patArchive = processPatientArchive(dto);
                processPatientInhospInfo(dto, patArchive, cry);
            } catch (Exception e) {
                failCount++;
                log.error("【inHospitalDate】单条记录处理失败,已跳过继续采集。state={}, serialnum={}, patname={}, patno={}, orgid={}, campusId={}",
                        cry, dto.getHealthcareRecordNo(), dto.getPatientName(), dto.getMedicalRecordNo(),
                        dto.getOrgId(), dto.getCampusId(), e);
            }
        }
        if (failCount > 0) {
            log.warn("【inHospitalDate】本页共{}条,失败{}条已跳过,其余{}条处理完成,state={}",
                    resultList.size(), failCount, resultList.size() - failCount, cry);
        }
        return true;
    }
@@ -600,7 +888,7 @@
        queryInhosp.setSerialnum(StringUtils.trim(patMedInhosp.getSerialnum()));
        queryInhosp.setOrgid(StringUtils.trim(dto.getOrgId()));
        queryInhosp.setInhospstate(cry);
        log.info("----------------这里的入参为:{},{}", queryInhosp.getPatno(), queryInhosp.getSerialnum());
//        log.info("----------------这里的入参为:{},{}", queryInhosp.getPatno(), queryInhosp.getSerialnum());
        List<PatMedInhosp> existingInhosps = patMedInhospService.selectPatMedInhosp(queryInhosp);
        if (cry.equals("0")) {
@@ -619,6 +907,44 @@
                    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 = inhospVisitFacade.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;
                    }
                }
@@ -643,7 +969,7 @@
            } else {
                // 不存在出院记录,先查预入院(state=3),再查入院(state=0)
                queryInhosp.setInhospstate("3");
                log.info("-----查询预入院记录,入参为:{}", queryInhosp);
//                log.info("-----查询预入院记录,入参为:{}", queryInhosp);
                List<PatMedInhosp> patMedInhospList = patMedInhospService.selectPatMedInhosp(queryInhosp);
                log.info("----预入院查询结果:{}", CollectionUtils.isEmpty(patMedInhospList) ? "空" : patMedInhospList.get(0).getSerialnum());
                if (CollectionUtils.isNotEmpty(patMedInhospList)) {
@@ -695,6 +1021,15 @@
                    List<PatMedInhosp> patMedInhospList3 = patMedInhospService.selectPatMedInhosp(queryInhosp);
                    if (CollectionUtils.isEmpty(patMedInhospList3)) {
                        try {
                            //获取一下入院申请单
                            //根据 encounterSourceId 获取 importanceFlagCode   emergencyLevelCode
                            if (active.equals("sltd")) {
                                Map<String, String> applyInfo = hospitalizedApply(dto.getEncounterSourceId(), dto.getOrgId());
                                if (ObjectUtils.isNotEmpty(applyInfo)) {
                                    patMedInhosp.setImportanceFlagCode(applyInfo.get("importanceFlagCode"));
                                    patMedInhosp.setEmergencyLevelCode(applyInfo.get("emergencyLevelCode"));
                                }
                            }
                            patMedInhospService.insertPatMedInhosp(patMedInhosp);
                            log.debug("成功插入预入院记录:serialnum={}", patMedInhosp.getSerialnum());
                        } catch (Exception e) {
@@ -728,7 +1063,6 @@
        patMedInhosp.setInhospno(dto.getHealthcareRecordNo());
        patMedInhosp.setFuflag("1");
        if (StringUtils.isNotEmpty(patArchive.getNotrequiredFlag()) && patArchive.getNotrequiredFlag().equals("1"))
            patMedInhosp.setFuflag("0");
        if ("0".equals(cry)) {
@@ -745,7 +1079,7 @@
        if ("1".equals(cry)) {
            patMedInhosp.setInhospstate("1");
            ServiceSLTDDiagnosisResDTO mainDischargeDiagnosis = dto.getMainDischargeDiagnosis();
            log.info("-----------mainDischargeDiagnosis的值有了吗?{}", mainDischargeDiagnosis);
//            log.info("-----------mainDischargeDiagnosis的值有了吗?{}", mainDischargeDiagnosis);
            if (!Objects.isNull(mainDischargeDiagnosis)) {
                if (mainDischargeDiagnosis.getIsMainDiagnosis() == 1) {
                    patMedInhosp.setLeavediagname(StringUtils.defaultString(mainDischargeDiagnosis.getDiagnosisDictName(), ""));
@@ -773,6 +1107,15 @@
            setDischargeInfo(patMedInhosp, dto);
        } else {
            setAdmissionInfo(patMedInhosp, dto);
            patMedInhosp.setEncounterSourceId(dto.getEncounterSourceId() == null ? "" : dto.getEncounterSourceId());
            if (StringUtils.isNotEmpty(dto.getEncounterSourceId()) && active.equals("sltd")) {
                //根据 encounterSourceId 获取 importanceFlagCode   emergencyLevelCode
                Map<String, String> applyInfo = hospitalizedApply(dto.getEncounterSourceId(), dto.getOrgId());
                if (!Objects.isNull(applyInfo)) {
                    patMedInhosp.setImportanceFlagCode(applyInfo.get("importanceFlagCode"));
                    patMedInhosp.setEmergencyLevelCode(applyInfo.get("emergencyLevelCode"));
                }
            }
        }
        patMedInhosp.setDrname(dto.getDoctorName());
@@ -783,7 +1126,170 @@
        patMedInhosp.setCampusid(dto.getCampusId());
        if (StringUtils.isNotEmpty(dto.getHealthcareRecordStatus()) && dto.getHealthcareRecordStatus().equals("FH0109.25"))
            patMedInhosp.setRemark("预出院");
        if (active.equals("nhfy") && dto.getAreaId() != null && dto.getAreaId().toString().equals("40003483") && StringUtils.isNotEmpty(patMedInhosp.getPatname())) {
            //南华附一的产科,要求不生成“之子、之女、之婴”的随访任务
            if (patMedInhosp.getPatname().contains("之子") || patMedInhosp.getPatname().contains("之女") || patMedInhosp.getPatname().contains("之婴")) {
                patMedInhosp.setDeptcheckFlag("3");
                patMedInhosp.setWardcheckFlag("3");
                patMedInhosp.setDiagcheckFlag("3");
                patMedInhosp.setRemark(patMedInhosp.getRemark() + ";不生成之子、之女、之婴问卷");
            }
        }
        //目前只有省立同德
        if (active.equals("sltd")) {
            Map<String, String> drugInfoMap = queryMedicalOrders(dto);
            if (drugInfoMap != null && !drugInfoMap.isEmpty()) {
                patMedInhosp.setMedicalName(drugInfoMap.get("drugItemNames"));
                patMedInhosp.setMedicalCode(drugInfoMap.get("drugItemIds"));
                patMedInhosp.setMedicalCompany(drugInfoMap.get("drugManufacturerNames"));
                log.info("获取药品信息成功:名称={}, 编码={}, 厂家={}", drugInfoMap.get("drugItemNames"), drugInfoMap.get("drugItemIds"), drugInfoMap.get("drugManufacturerNames"));
            }
        }
        return patMedInhosp;
    }
    /**
     * 查询医嘱信息并拼接药品信息
     *
     * @param dto 健康记录请求对象
     * @return 返回包含三个字段 Map:
     * - drugItemNames: 药品名称列表(逗号分隔)
     * - drugItemIds: 药品编码列表(逗号分隔)
     * - drugManufacturerNames: 药品厂家列表(逗号分隔)
     * <p>
     * 规则:
     * 1. 三个字段顺序位置保持一致
     * 2. 如果三个字段都为空,则跳过该记录
     * 3. 如果至少有一个字段有值,则其他两个为空用空字符串代替
     */
    private Map<String, String> queryMedicalOrders(ServiceSLTDInhospResDTO dto) {
        Map<String, String> result = new HashMap<>();
        List<String> itemNames = new ArrayList<>();
        List<String> itemIds = new ArrayList<>();
        List<String> manufacturerNames = new ArrayList<>();
        try {
            // 获取患者的医嘱信息
            Map<String, Object> medicineInfoReq = new HashMap<>();
            medicineInfoReq.put("orgId", dto.getOrgId());
            medicineInfoReq.put("patientId", dto.getPatientId());
            medicineInfoReq.put("campusId", dto.getCampusId());
            medicineInfoReq.put("medicalRecordNo", dto.getMedicalRecordNo());
            log.info("【queryMedicalOrders】请求参数:{}", medicineInfoReq);
            Map<String, String> headers = buildRequestHeaders();
            String resultStr = HttpUtils.sendPostByHeader(sltdPubPath + "/osj/hbos-thirdparty-integration/standard/common/doctorOrder/queryDoctorOrderList", new Gson().toJson(medicineInfoReq), headers);
            if (StringUtils.isEmpty(resultStr)) {
                log.warn("【queryMedicalOrders】接口返回为空,patientId={}", dto.getPatientId());
                return result;
            }
            // 解析返回数据
            Gson gson = new Gson();
            Type mapType = new TypeToken<Map<String, Object>>() {
            }.getType();
            Map<String, Object> responseMap = gson.fromJson(resultStr, mapType);
            // 检查返回码
            Object codeObj = responseMap.get("code");
            if (codeObj != null) {
                String code = BigDecimal.valueOf(((Number) codeObj).longValue()).toPlainString();
                if (!"200".equals(code)) {
                    log.error("【queryMedicalOrders】接口返回失败,code={}, message={}", code, responseMap.get("message"));
                    return null;
                }
            }
            // 获取 data 数组
            Object dataObj = responseMap.get("data");
            if (dataObj instanceof List) {
                List<?> dataList = (List<?>) dataObj;
                // 遍历医嘱列表,提取药品信息
                Set<String> processedItemIds = new HashSet<>(); // 用于去重(只根据 itemId 去重)
                for (Object item : dataList) {
                    if (item instanceof Map) {
                        Map<String, Object> itemMap = (Map<String, Object>) item;
                        // 只处理药品相关的医嘱(itemDomain 为 FH0134.01 表示药品)
                        Object itemDomainObj = itemMap.get("itemDomain");
                        if (itemDomainObj == null || !"FH0134.01".equals(itemDomainObj.toString())) {
                            continue;
                        }
                        // 获取三个字段
                        Object itemNameObj = itemMap.get("itemName");
                        Object itemIdObj = itemMap.get("itemId");
                        Object manufacturerNameObj = itemMap.get("manufacturerName");
                        // 判断是否都为空
                        boolean allEmpty = (itemNameObj == null || StringUtils.isEmpty(itemNameObj.toString())) && (itemIdObj == null || StringUtils.isEmpty(itemIdObj.toString())) && (manufacturerNameObj == null || StringUtils.isEmpty(manufacturerNameObj.toString()));
                        // 如果三个都为空,则跳过
                        if (allEmpty) {
                            continue;
                        }
                        // 至少有一个有值,处理每个字段
                        String itemName = (itemNameObj != null && StringUtils.isNotEmpty(itemNameObj.toString())) ? itemNameObj.toString() : "";
                        // itemId 特殊处理:避免科学计数法(Gson 可能将数字解析为 Double)
                        String itemId = "";
                        if (itemIdObj != null && StringUtils.isNotEmpty(itemIdObj.toString())) {
                            if (itemIdObj instanceof Number) {
                                // 如果是数值类型,转换为整数字符串,避免科学计数法
                                itemId = String.valueOf(((Number) itemIdObj).longValue());
                            } else {
                                itemId = itemIdObj.toString();
                            }
                        }
                        String manufacturerName = (manufacturerNameObj != null && StringUtils.isNotEmpty(manufacturerNameObj.toString())) ? manufacturerNameObj.toString() : "";
                        // 只根据 itemId 去重(同一编码的药品只保留一个)
                        if (StringUtils.isNotEmpty(itemId)) {
                            if (processedItemIds.contains(itemId)) {
                                log.info("【queryMedicalOrders】跳过重复药品(相同编码):名称={}, 编码={}", itemName, itemId);
                                continue;
                            }
                            processedItemIds.add(itemId);
                        } else {
                            // itemId 为空时,使用 itemName 作为去重依据
                            if (processedItemIds.contains(itemName)) {
                                log.info("【queryMedicalOrders】跳过重复药品(相同名称):名称={}", itemName);
                                continue;
                            }
                            processedItemIds.add(itemName);
                        }
                        // 添加到列表
                        itemNames.add(itemName);
                        itemIds.add(itemId);
                        manufacturerNames.add(manufacturerName);
                        log.info("【queryMedicalOrders】提取药品信息:名称={}, 编码={}, 厂家={}", itemName, itemId, manufacturerName);
                    }
                }
            } else {
                log.info("【queryMedicalOrders】data 不是数组类型,patientId={}", dto.getPatientId());
            }
            // 将列表拼接成逗号分隔的字符串
            if (!itemNames.isEmpty()) {
                result.put("drugItemNames", String.join(",", itemNames));
                result.put("drugItemIds", String.join(",", itemIds));
                result.put("drugManufacturerNames", String.join(",", manufacturerNames));
            }
            log.info("【queryMedicalOrders】成功提取{}条药品信息,patientId={}", itemNames.size(), dto.getPatientId());
        } catch (Exception e) {
            log.error("【queryMedicalOrders】查询医嘱信息失败,patientId={}", dto.getPatientId(), e);
        }
        return result;
    }
    private void setDischargeInfo(PatMedInhosp patMedInhosp, ServiceSLTDInhospResDTO dto) {
@@ -810,17 +1316,92 @@
    }
    /**
     * 根据入院申请单ID查询入院申请单详情
     * 只提取 importanceFlagCode(importanceFlagCodeList逗号拼接)和 emergencyLevelCode 两个值
     */
    private Map<String, String> hospitalizedApply(String encounterSourceId, String orgid) {
        Map<String, String> applyInfo = new HashMap<>();
        Map<String, String> headers = buildRequestHeaders();
        log.info("院申请单ID查询入院申请单:{},appKey:{}", sltdPubPath + "/sf/hospitalizedApply/query", APP_KEY);
        //设置入参
        Map<String, Object> requestParams = new HashMap<>();
        requestParams.put("applyId", encounterSourceId);
        requestParams.put("orgId", orgid);
        String result = null;
        if (StringUtils.isNotEmpty(encounterSourceId) && StringUtils.isNotEmpty(orgid))
            result = HttpUtils.sendPostByHeader(sltdPubPath + "/sf/hospitalizedApply/query", new Gson().toJson(requestParams), headers);
        if (StringUtils.isEmpty(result)) {
            log.error("【hospitalizedApply】接口返回为空,applyId={}", encounterSourceId);
            return applyInfo;
        }
        try {
            Gson gson = new Gson();
            Type mapType = new TypeToken<Map<String, Object>>() {
            }.getType();
            Map<String, Object> responseMap = gson.fromJson(result, mapType);
            Object dataObj = responseMap == null ? null : responseMap.get("data");
            if (dataObj instanceof Map) {
                Map<String, Object> data = (Map<String, Object>) dataObj;
                //重要性标记编码,接口返回是数组,需要转换为字典名称
                Object importanceFlagCodeListObj = data.get("importanceFlagCodeList");
                if (importanceFlagCodeListObj instanceof List) {
                    List<?> importanceFlagCodeList = (List<?>) importanceFlagCodeListObj;
                    // 过滤掉 null 值
                    List<String> nonNullList = importanceFlagCodeList.stream().filter(Objects::nonNull).map(Object::toString).filter(StringUtils::isNotEmpty).collect(Collectors.toList());
                    if (!nonNullList.isEmpty()) {
                        String importanceFlagLabel;
                        if (nonNullList.size() == 1) {
                            // 只有一个值,直接通过枚举获取描述
                            importanceFlagLabel = ImportFlagEnum.getDescByCode(nonNullList.get(0));
                        } else {
                            // 多个值,用逗号拼接枚举描述
                            StringBuilder labelBuilder = new StringBuilder();
                            for (String code : nonNullList) {
                                String label = ImportFlagEnum.getDescByCode(code);
                                if (StringUtils.isNotEmpty(label)) {
                                    labelBuilder.append(label).append(",");
                                }
                            }
                            importanceFlagLabel = StringUtils.stripEnd(labelBuilder.toString(), ",");
                        }
                        if (StringUtils.isNotEmpty(importanceFlagLabel)) {
                            applyInfo.put("importanceFlagCode", importanceFlagLabel);
                        }
                    }
                }
                //紧急程度编码,通过枚举转换描述
                Object emergencyLevelCodeObj = data.get("emergencyLevelCode");
                if (emergencyLevelCodeObj != null) {
                    String emergencyLevelLabel = EmergencyLevelEnum.getDescByCode(emergencyLevelCodeObj.toString());
                    if (StringUtils.isNotEmpty(emergencyLevelLabel)) {
                        applyInfo.put("emergencyLevelCode", emergencyLevelLabel);
                    }
                }
            } else {
                log.error("【hospitalizedApply】接口返回data不是对象,applyId={},result:{}", encounterSourceId, result);
            }
        } catch (Exception e) {
            log.error("【hospitalizedApply】解析入院申请单详情失败,applyId={}", encounterSourceId, e);
        }
        log.info("【hospitalizedApply】applyId={},importanceFlagCode={},emergencyLevelCode={}", encounterSourceId, applyInfo.get("importanceFlagCode"), applyInfo.get("emergencyLevelCode"));
        return applyInfo;
    }
    /**
     * 处理患者档案(新增或更新)
     * 使用 Redis 分布式锁防止并发重复插入
     */
    private PatArchive processPatientArchive(ServiceSLTDInhospResDTO dto) {
        // 构建锁的 key:基于 patientno 或 idcardno
        String lockKey = "pat_archive_lock:" +
            (StringUtils.isNotEmpty(dto.getMedicalRecordNo()) ? dto.getMedicalRecordNo() : dto.getIdCardNo());
        String lockKey = "pat_archive_lock:" + (StringUtils.isNotEmpty(dto.getMedicalRecordNo()) ? dto.getMedicalRecordNo() : dto.getIdCardNo());
        // 尝试获取分布式锁,最多等待 3 秒,锁定 10 秒自动释放
        Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        if (lockAcquired == null || !lockAcquired) {
            log.warn("【processPatientArchive】获取分布式锁失败,跳过本次处理(其他线程正在处理),patientno={}", dto.getMedicalRecordNo());
            // 等待一段时间后重试查询
@@ -830,7 +1411,7 @@
                Thread.currentThread().interrupt();
            }
        }
        try {
            return doProcessPatientArchive(dto);
        } finally {
@@ -865,9 +1446,8 @@
        }
        PatArchive patArchive = buildPatientArchive(dto);
        log.info("【processPatientArchive】患者档案查重完成,patientno={}, 是否已存在={}",
            patArchive.getPatientno(), CollectionUtils.isEmpty(existingArchives) ? "否" : "是(id=" + existingArchives.get(0).getId() + ")");
        log.info("【processPatientArchive】患者档案查重完成,patientno={}, 是否已存在={}", patArchive.getPatientno(), CollectionUtils.isEmpty(existingArchives) ? "否" : "是(id=" + existingArchives.get(0).getId() + ")");
        if (CollectionUtils.isEmpty(existingArchives)) {
            try {
                // 最终确认查询(防御性编程:防止Redis锁失效等极端情况)
@@ -878,15 +1458,13 @@
                if (StringUtils.isNotEmpty(dto.getMedicalRecordNo())) {
                    finalQuery.setPatientno(dto.getMedicalRecordNo());
                }
                List<PatArchive> finalCheck = patArchiveService.selectPatArchiveList(finalQuery);
                log.info("【processPatientArchive】最终确认查询,patientno={}, idcardno={}, 结果数量={}",
                    dto.getMedicalRecordNo(), dto.getIdCardNo(), finalCheck.size());
                log.info("【processPatientArchive】最终确认查询,patientno={}, idcardno={}, 结果数量={}", dto.getMedicalRecordNo(), dto.getIdCardNo(), finalCheck.size());
                if (CollectionUtils.isEmpty(finalCheck)) {
                    patArchiveService.insertPatArchive(patArchive);
                    log.info("【processPatientArchive】✓ 新增患者档案成功,patientno={}, id={}",
                        patArchive.getPatientno(), patArchive.getId());
                    log.info("【processPatientArchive】✓ 新增患者档案成功,patientno={}, id={}", patArchive.getPatientno(), patArchive.getId());
                } else {
                    // 其他线程已经插入,直接使用已有记录
                    existingArchives = finalCheck;
@@ -896,8 +1474,7 @@
                    log.info("【processPatientArchive】档案已被其他线程创建,使用已有记录,id={}", patArchive.getId());
                }
            } catch (org.springframework.dao.DuplicateKeyException e) {
                log.warn("【processPatientArchive】患者档案已存在(并发插入异常),跳过:patientno={}, idcardno={}",
                    patArchive.getPatientno(), patArchive.getIdcardno());
                log.warn("【processPatientArchive】患者档案已存在(并发插入异常),跳过:patientno={}, idcardno={}", patArchive.getPatientno(), patArchive.getIdcardno());
                // 并发插入场景,重新查询获取已存在的记录
                PatArchive queryRetry = new PatArchive();
                queryRetry.setPatientno(patArchive.getPatientno());
@@ -917,8 +1494,7 @@
            patArchive.setNotrequiredFlag(existingArchives.get(0).getNotrequiredFlag());
            patArchive.setNotrequiredreason(existingArchives.get(0).getNotrequiredreason());
            patArchiveService.updateArchive(patArchive);
            log.info("【processPatientArchive】✓ 更新患者档案,patientno={}, id={}",
                patArchive.getPatientno(), patArchive.getId());
            log.info("【processPatientArchive】✓ 更新患者档案,patientno={}, id={}", patArchive.getPatientno(), patArchive.getId());
        }
        return patArchive;
@@ -1005,6 +1581,7 @@
        dto.setCostCategoryCode(getStringValue(dataItem, "costCategoryCode"));
        dto.setCostNatureName(getStringValue(dataItem, "costNatureName"));
        dto.setCostNatureCode(getStringValue(dataItem, "costNatureCode"));
        dto.setEncounterSourceId(getStringValue(dataItem, "encounterSourceId"));
        dto.setFurtherConsultationStatus(getIntegerValue(dataItem, "furtherConsultationStatus"));
        Object mainDiagObj = dataItem.get("mainDischargeDiagnosis");
        if (mainDiagObj instanceof Map) {
@@ -1029,6 +1606,55 @@
        return dto;
    }
    private MedicationItem convertToMedication(Map<String, Object> dataItem) {
        MedicationItem dto = new MedicationItem();
        dto.setOrgId(getLongValue(dataItem, "orgId"));
        dto.setIdentificationCode(getStringValue(dataItem, "identificationCode"));
        dto.setWbm(getStringValue(dataItem, "wbm"));
        dto.setMedicationCommonName(getStringValue(dataItem, "medicationCommonName"));
        dto.setManufacturerName(getStringValue(dataItem, "manufacturerName"));
        dto.setItemCode(getStringValue(dataItem, "itemCode"));
        dto.setSmallLargePackageRatio(getLongValue(dataItem, "smallLargePackageRatio"));
        dto.setRemark(getStringValue(dataItem, "remark"));
        dto.setItemName(getStringValue(dataItem, "itemName"));
        dto.setMedicationType(getStringValue(dataItem, "medicationType"));
        Object mainDiagObj = dataItem.get("doseInfoList");
        List<MedicationDoseInfo> doseInfoList = new ArrayList<>();
        if (mainDiagObj instanceof List) {
            List<Map<String, Object>> mainDiagList = (List<Map<String, Object>>) mainDiagObj;
            for (Map<String, Object> mainDiagMap : mainDiagList) {
                MedicationDoseInfo medicationDoseInfo = new MedicationDoseInfo();
                medicationDoseInfo.setDoseSmallPackageRation(getBigDecimalValue(mainDiagMap, "doseSmallPackageRation"));
                medicationDoseInfo.setDose(getLongValue(mainDiagMap, "dose"));
                medicationDoseInfo.setDoseSmallPackageNum(getLongValue(mainDiagMap, "doseSmallPackageNum"));
                medicationDoseInfo.setDoseUnit(getStringValue(mainDiagMap, "doseUnit"));
                medicationDoseInfo.setDoseUnitType(getLongValue(mainDiagMap, "doseUnitType"));
                doseInfoList.add(medicationDoseInfo);
            }
        }
        dto.setDoseInfoList(doseInfoList);
        dto.setAntimicrobialGradeCode(getStringValue(dataItem, "antimicrobialGradeCode"));
        dto.setLicenseNumber(getStringValue(dataItem, "licenseNumber"));
        dto.setDefaultUsageName(getStringValue(dataItem, "defaultUsageName"));
        dto.setSpecialDrugsName(getStringValue(dataItem, "specialDrugsName"));
        dto.setAntimicrobialGradeName(getStringValue(dataItem, "antimicrobialGradeName"));
        dto.setCommonPinyin(getStringValue(dataItem, "commonPinyin"));
        dto.setCommonWbm(getStringValue(dataItem, "commonWbm"));
        dto.setManufacturerId(getLongValue(dataItem, "manufacturerId"));
        dto.setDefaultUsageId(getLongValue(dataItem, "defaultUsageId"));
        dto.setSpecification(getStringValue(dataItem, "specification"));
        dto.setSmallPackageUnit(getStringValue(dataItem, "smallPackageUnit"));
        dto.setItemId(getLongValue(dataItem, "itemId"));
        dto.setSpecialDrugsCode(getStringValue(dataItem, "specialDrugsCode"));
        dto.setPinyin(getStringValue(dataItem, "pinyin"));
        dto.setDoseFormName(getStringValue(dataItem, "doseFormName"));
        dto.setLargePackageUnit(getStringValue(dataItem, "largePackageUnit"));
        dto.setRetailPrice(getBigDecimalValue(dataItem, "retailPrice"));
        dto.setStatus(getLongValue(dataItem, "status"));
        return dto;
    }
    private List<ServiceSLTDContactsResDTO> parseContacts(List<?> contactsList) {
        List<ServiceSLTDContactsResDTO> contacts = new ArrayList<>();
@@ -1074,6 +1700,16 @@
        return value instanceof Number ? new BigDecimal(value.toString()).toPlainString() : value.toString();
    }
    private BigDecimal getBigDecimalValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        // 只处理数字类型,避免科学计数法
        return new BigDecimal(value.toString());
    }
    private Long getLongValue(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) return null;