From 95e379e5a5961ee45c7c1b5625281fa42bed113e Mon Sep 17 00:00:00 2001 From: wangjian963 <15215920+aprilry@user.noreply.gitee.com> Date: Thu, 23 Apr 2026 17:17:04 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Bug=20#407=20#385=20=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E5=8C=BB=E5=98=B1=E5=88=86=E7=B1=BB=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=8F=8A=E9=A2=84=E7=BB=93=E7=AE=97=E8=B4=A6=E6=88=B7?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E4=BF=AE=E5=A4=8D=20=20=20=E4=B8=BB=E8=A6=81?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=20=20=20-=20=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E5=8C=BB=E5=98=B1=E7=B1=BB=E5=9E=8B=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E8=AE=BE=E7=BD=AE=E4=B8=BA=E8=AF=8A=E7=96=97(3)?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E8=A2=AB=E9=94=99=E8=AF=AF=E5=BD=92?= =?UTF-8?q?=E7=B1=BB=E4=B8=BA=E8=8D=AF=E5=93=81=20=20=20-=20=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E7=94=B3=E8=AF=B7=E6=94=B6=E8=B4=B9=E9=A1=B9=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=9C=9F=E5=AE=9E=E8=87=AA=E8=B4=B9=E8=B4=A6=E6=88=B7?= =?UTF-8?q?=EF=BC=8C=E9=A2=84=E7=BB=93=E7=AE=97=E9=AA=8C=E8=AF=81accountId?= =?UTF-8?q?=E5=BF=85=E9=A1=BB=E6=9C=89=E6=95=88=E5=AD=98=E5=9C=A8=20=20=20?= =?UTF-8?q?-=20=E7=AD=BE=E5=8F=91=E6=97=B6=E8=A1=A5=E5=85=85=E8=AF=8A?= =?UTF-8?q?=E7=96=97=E8=B4=B9=E7=94=A8=E9=A1=B9=E8=AF=8A=E6=96=AD=E5=85=B3?= =?UTF-8?q?=E8=81=94=E4=BF=A1=E6=81=AF=E5=8F=98=E6=9B=B4=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=EF=BC=9A=20=20=20-=20ExamApplyController=EF=BC=9A=E4=BD=BF?= =?UTF-8?q?=E7=94=A8ItemType=E6=9E=9A=E4=B8=BE=EF=BC=8C=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E8=B4=A6=E6=88=B7=E6=9B=BF=E4=BB=A3=E5=8D=A0?= =?UTF-8?q?=E4=BD=8D=E5=80=BC0=20-DoctorStationAdviceAppService=EF=BC=9A?= =?UTF-8?q?=E6=8C=89=E6=9E=9A=E4=B8=BE=E6=A0=87=E5=87=86=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E5=8C=BB=E5=98=B1=EF=BC=8C=E9=AA=8C=E8=AF=81accountId=E6=9C=89?= =?UTF-8?q?=E6=95=88=E6=80=A7=20=20=20-=20IChargeBillService=EF=BC=9Aprodu?= =?UTF-8?q?ctId=3D0=E6=97=B6=E4=BB=8EServiceRequest.contentJson=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=A1=B9=E7=9B=AE=E5=90=8D=E7=A7=B0=20=20=20-=20Payme?= =?UTF-8?q?ntRecService=EF=BC=9A=E9=A2=84=E7=BB=93=E7=AE=97=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E4=BF=AE=E5=A4=8D=E8=B4=A6=E6=88=B7=E4=B8=8D=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=E7=9A=84=E5=8E=86=E5=8F=B2=E6=95=B0=E6=8D=AE=20=20=20?= =?UTF-8?q?-=20Mapper=EF=BC=9A=E6=8E=92=E9=99=A4=E8=A1=8D=E7=94=9F?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E8=AE=B0=E5=BD=95=EF=BC=8CproductId=3D0?= =?UTF-8?q?=E6=97=B6=E8=A1=A5=E5=85=85=E6=9F=A5=E8=AF=A2=E9=80=BB=E8=BE=91?= =?UTF-8?q?=20=20=20-=20ServiceRequest=E5=AE=9E=E4=BD=93=EF=BC=9AactivityI?= =?UTF-8?q?d=E5=AD=97=E6=AE=B5=E6=B7=BB=E5=8A=A0ALWAYS=E6=8F=92=E5=85=A5?= =?UTF-8?q?=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../check/controller/ExamApplyController.java | 47 +++-- .../DoctorStationAdviceAppServiceImpl.java | 162 +++++++++++++++--- .../impl/IChargeBillServiceImpl.java | 47 ++++- .../impl/PaymentRecServiceImpl.java | 69 +++++++- .../OutpatientChargeAppMapper.xml | 6 + .../DoctorStationAdviceAppMapper.xml | 6 + .../workflow/domain/ServiceRequest.java | 3 + 7 files changed, 293 insertions(+), 47 deletions(-) diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/ExamApplyController.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/ExamApplyController.java index a58f9bfd..9dee9f4e 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/ExamApplyController.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/check/controller/ExamApplyController.java @@ -5,9 +5,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.core.common.core.controller.BaseController; import com.core.common.core.domain.AjaxResult; import com.core.common.core.page.TableDataInfo; +import com.core.common.exception.ServiceException; import com.core.common.utils.AssignSeqUtil; import com.core.common.utils.SecurityUtils; +import com.openhis.administration.domain.Account; import com.openhis.administration.domain.ChargeItem; +import com.openhis.administration.service.IAccountService; import com.openhis.administration.service.IChargeItemService; import com.openhis.check.domain.ExamApply; import com.openhis.check.domain.ExamApplyItem; @@ -17,8 +20,8 @@ import com.openhis.common.constant.CommonConstants; import com.openhis.common.enums.AssignSeqEnum; import com.openhis.common.enums.ChargeItemStatus; import com.openhis.common.enums.GenerateSource; +import com.openhis.common.enums.ItemType; import com.openhis.common.enums.RequestStatus; -import com.openhis.common.enums.EncounterClass; import com.openhis.web.check.dto.ExamApplyDto; import com.openhis.web.check.dto.ExamApplyItemDto; import com.openhis.workflow.domain.ServiceRequest; @@ -58,6 +61,9 @@ public class ExamApplyController extends BaseController { @Autowired private IChargeItemService chargeItemService; + @Autowired + private IAccountService accountService; + @Autowired private AssignSeqUtil assignSeqUtil; @@ -213,8 +219,9 @@ public class ExamApplyController extends BaseController { // 检查申请不走诊疗定义,设置为0占位(数据库有NOT NULL约束) serviceRequest.setActivityId(0L); - // 🔧 Bug #407修复:设置医嘱类型为诊疗(3),避免被错误识别为中成药 - serviceRequest.setCategoryEnum(3); + // 🔧 Bug Fix: 设置医嘱类型为诊疗(3),与检验申请保持一致 + // categoryEnum=3 → SQL查询返回 adviceType=3(诊疗),避免被错误归类为药品 + serviceRequest.setCategoryEnum(ItemType.ACTIVITY.getValue()); // 患者和就诊信息 —— 使用前端传递的数字型ID if (dto.getPatientIdNum() != null) { @@ -227,7 +234,8 @@ public class ExamApplyController extends BaseController { serviceRequest.setRequesterId(currentUserId); // 开单医生 serviceRequest.setOrgId(currentOrgId); // 执行科室 serviceRequest.setAuthoredTime(now); // 签发时间 - serviceRequest.setCategoryEnum(EncounterClass.AMB.getValue()); // 请求类型:门诊 + // 🔧 Bug Fix: 不设置门诊类型,保留上面已设置的 categoryEnum=3(诊疗类型) + // EncounterClass.AMB.getValue()=2 表示门诊类型,会覆盖诊疗类型导致医嘱被错误归类 serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 来源=医生开立 // 将项目名称存入 contentJson,使医嘱列表能通过 JSON 字段回显 adviceName @@ -268,10 +276,17 @@ public class ExamApplyController extends BaseController { chargeItem.setRequestingOrgId(currentOrgId); // 开立科室 chargeItem.setEnteredDate(now); // 开立时间 - // 以下字段均有 NOT NULL 约束,检查申请不走定价/账户体系,用0占位 + // 以下字段均有 NOT NULL 约束,检查申请不走定价体系,用0占位 chargeItem.setDefinitionId(0L); // 费用定价ID - chargeItem.setAccountId(0L); // 关联账户ID - chargeItem.setContextEnum(2); // 类型:2=诊疗 + // 🔧 BugFix#385: 获取患者真实的自费账户,预结算验证要求accountId必须真实存在 + Account selfAccount = accountService.getSelfAccount(dto.getEncounterId()); + if (selfAccount == null) { + throw new ServiceException("患者自费账户不存在,无法创建检查收费项,encounterId=" + dto.getEncounterId()); + } + chargeItem.setAccountId(selfAccount.getId()); + // 🔧 BugFix#385: 使用 ItemType.ACTIVITY.getValue()=3 表示诊疗,而不是硬编码的2 + // ItemType 枚举定义:MEDICINE=1, DEVICE=2(耗材), ACTIVITY=3(诊疗) + chargeItem.setContextEnum(ItemType.ACTIVITY.getValue()); // 类型:3=诊疗 chargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION); // 产品来源表 chargeItem.setProductId(0L); // 产品ID @@ -393,8 +408,9 @@ public class ExamApplyController extends BaseController { serviceRequest.setBasedOnTable("exam_apply"); serviceRequest.setBasedOnId(examApply.getId()); serviceRequest.setActivityId(0L); - // 🔧 Bug #407修复:设置医嘱类型为诊疗(3),避免被错误识别为中成药 - serviceRequest.setCategoryEnum(3); + // 🔧 Bug Fix: 设置医嘱类型为诊疗(3),与检验申请保持一致 + // categoryEnum=3 → SQL查询返回 adviceType=3(诊疗),避免被错误归类为药品 + serviceRequest.setCategoryEnum(ItemType.ACTIVITY.getValue()); if (dto.getPatientIdNum() != null) { serviceRequest.setPatientId(dto.getPatientIdNum()); @@ -405,7 +421,8 @@ public class ExamApplyController extends BaseController { serviceRequest.setRequesterId(currentUserId); serviceRequest.setOrgId(currentOrgId); serviceRequest.setAuthoredTime(now); - serviceRequest.setCategoryEnum(EncounterClass.AMB.getValue()); // 请求类型:门诊 + // 🔧 Bug Fix: 不设置门诊类型,保留上面已设置的 categoryEnum=3(诊疗类型) + // EncounterClass.AMB.getValue()=2 表示门诊类型,会覆盖诊疗类型导致医嘱被错误归类 serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 将项目名称存入 contentJson,使医嘱列表能通过 JSON 字段回显 adviceName @@ -440,8 +457,14 @@ public class ExamApplyController extends BaseController { chargeItem.setRequestingOrgId(currentOrgId); chargeItem.setEnteredDate(now); chargeItem.setDefinitionId(0L); - chargeItem.setAccountId(0L); - chargeItem.setContextEnum(2); + // 🔧 BugFix#385: 获取患者真实的自费账户,预结算验证要求accountId必须真实存在 + Account selfAccount = accountService.getSelfAccount(dto.getEncounterId()); + if (selfAccount == null) { + throw new ServiceException("患者自费账户不存在,无法创建检查收费项,encounterId=" + dto.getEncounterId()); + } + chargeItem.setAccountId(selfAccount.getId()); + // 🔧 BugFix#385: 使用 ItemType.ACTIVITY.getValue()=3 表示诊疗 + chargeItem.setContextEnum(ItemType.ACTIVITY.getValue()); chargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION); chargeItem.setProductId(0L); diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java index 79e6ea93..aa3a4c6d 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java @@ -22,6 +22,8 @@ import com.openhis.administration.domain.Encounter; import com.openhis.administration.service.IAccountService; import com.openhis.administration.service.IChargeItemService; import com.openhis.administration.service.IEncounterService; +import com.openhis.administration.service.IEncounterDiagnosisService; +import com.openhis.administration.domain.EncounterDiagnosis; import com.openhis.common.constant.CommonConstants; import com.openhis.common.constant.PromptMsgConstant; import com.openhis.common.enums.*; @@ -115,6 +117,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp @Resource IEncounterService iEncounterService; + @Resource + IEncounterDiagnosisService iEncounterDiagnosisService; + @Resource IInventoryItemService inventoryItemService; @@ -606,27 +611,24 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp } } - // 药品(前端adviceType=1=西药, 2=中成药 → 都属于药品后端分类) + // 按后端 ItemType 枚举标准分类: + // MEDICINE=1(药品)、DEVICE=2(耗材)、ACTIVITY=3(诊疗)、SURGERY=6(手术) + + // 药品分类:adviceType == 1 List medicineList = adviceSaveList.stream() - .filter(e -> ItemType.MEDICINE.getValue().equals(e.getAdviceType()) - || e.getAdviceType() == 1 - || e.getAdviceType() == 2) // 前端中成药类型值为2 → 也属于药品分类 - .collect(Collectors.toList()); - // 耗材(前端adviceType=4,后端ItemType.DEVICE=2) - List deviceList = adviceSaveList.stream() - .filter(e -> ItemType.DEVICE.getValue().equals(e.getAdviceType()) - || e.getAdviceType() == 4) // 前端耗材类型值为4 + .filter(e -> e.getAdviceType() != null && e.getAdviceType() == ItemType.MEDICINE.getValue()) .collect(Collectors.toList()); - // 诊疗活动(前端adviceType=3诊疗、adviceType=5会诊、adviceType=6手术、adviceType=23检查、adviceType=26护理 → 都属于诊疗后端分类) + // 耗材分类:adviceType == 2 + List deviceList = adviceSaveList.stream() + .filter(e -> e.getAdviceType() != null && e.getAdviceType() == ItemType.DEVICE.getValue()) + .collect(Collectors.toList()); + + // 诊疗分类:adviceType == 3 List activityList = adviceSaveList.stream() - .filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType()) - || e.getAdviceType() == 3 // 前端诊疗类型值为3 - || e.getAdviceType() == 5 // 前端会诊类型值为5 - || e.getAdviceType() == 6 // 前端手术类型值为6 - || e.getAdviceType() == 23 // 前端检查类型值为23 - || e.getAdviceType() == 26 // 前端护理类型值为26 - || ItemType.SURGERY.getValue().equals(e.getAdviceType())) // 后端手术类型值为6 + .filter(e -> e.getAdviceType() != null + && (e.getAdviceType() == ItemType.ACTIVITY.getValue() + || e.getAdviceType() == ItemType.SURGERY.getValue())) // 手术(6)也走诊疗流程 .collect(Collectors.toList()); // 🔍 Debug日志日志: 记录分类结果 @@ -803,12 +805,71 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp // 就诊id Long encounterId = adviceSaveList.get(0).getEncounterId(); - // 使用安全的更新方法,避免并发冲突 - 更新费用项状态 + // 🔧 BugFix#385: 签发时更新诊疗医嘱关联的费用项诊断信息 + // 检查申请创建的费用项没有诊断关联,需要在签发时补充 + if (!activityList.isEmpty()) { + // 先从就诊中获取主诊断,用于补充没有诊断关联的费用项 + List encounterDiagList = iEncounterDiagnosisService.getDiagnosisList(encounterId); + EncounterDiagnosis mainDiagnosis = iEncounterDiagnosisService.getMainDiagnosis(encounterDiagList); + Long mainConditionId = mainDiagnosis != null ? mainDiagnosis.getConditionId() : null; + Long mainEncounterDiagId = mainDiagnosis != null ? mainDiagnosis.getId() : null; + + log.info("BugFix#385: 签发时获取就诊主诊断, encounterId={}, mainConditionId={}, mainEncounterDiagId={}", + encounterId, mainConditionId, mainEncounterDiagId); + + for (AdviceSaveDto adviceDto : activityList) { + if (adviceDto.getRequestId() != null) { + // 查询诊疗医嘱关联的费用项 + List chargeItems = iChargeItemService.getChargeItemInfoByReqId( + Arrays.asList(adviceDto.getRequestId())); + if (chargeItems != null && !chargeItems.isEmpty()) { + // 过滤只保留诊疗类型的费用项 + ChargeItem chargeItem = chargeItems.stream() + .filter(ci -> CommonConstants.TableName.WOR_SERVICE_REQUEST.equals(ci.getServiceTable())) + .findFirst().orElse(null); + if (chargeItem != null) { + // 🔧 BugFix#385: 如果费用项没有诊断关联,使用主诊断补充 + Long conditionId = adviceDto.getConditionId(); + Long encounterDiagId = adviceDto.getEncounterDiagnosisId(); + + // 如果传入的诊断为空,使用主诊断 + if (conditionId == null) { + conditionId = mainConditionId; + } + if (encounterDiagId == null) { + encounterDiagId = mainEncounterDiagId; + } + + // 更新诊断关联 + if (conditionId != null || encounterDiagId != null) { + chargeItem.setConditionId(conditionId); + chargeItem.setEncounterDiagnosisId(encounterDiagId); + iChargeItemService.updateById(chargeItem); + log.info("BugFix#385: 签发时更新诊疗费用项诊断关联, chargeItemId={}, conditionId={}, encounterDiagnosisId={}", + chargeItem.getId(), conditionId, encounterDiagId); + } + } + } + } + } + } + + // 🔧 BugFix#385: 使用安全的更新方法,避免并发冲突 - 更新费用项状态 + // 需要处理两种情况: + // 1. 从 DRAFT (0) → PLANNED (1):新创建的收费项目 + // 2. 从 BILLABLE (2) → PLANNED (1):保存时已设为待结算的项目 iChargeItemService.updateChargeStatusByConditionSafe( encounterId, ChargeItemStatus.DRAFT.getValue(), ChargeItemStatus.PLANNED.getValue(), requestIds); + + // 🔧 BugFix#385: 同时处理 BILLABLE 状态的收费项目 + iChargeItemService.updateChargeStatusByConditionSafe( + encounterId, + ChargeItemStatus.BILLABLE.getValue(), + ChargeItemStatus.PLANNED.getValue(), + requestIds); } // 数据变更后清理相关缓存 @@ -942,12 +1003,26 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp insertOrUpdateList = uniqueInsertOrUpdateList; for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) { - // 🔧 Bug Fix: 确保accountId不为null,与handleBoundDevices保持一致 + // 🔧 Bug Fix: 确保accountId有效(不为null且账户存在) + boolean needNewAccount = false; if (adviceSaveDto.getAccountId() == null) { + needNewAccount = true; + } else { + // 验证账户是否存在且有效(未被删除,租户匹配) + Account existingAccount = iAccountService.getById(adviceSaveDto.getAccountId()); + if (existingAccount == null) { + log.warn("handMedication - 前端传入的accountId无效(账户不存在),accountId={},将重新获取或创建账户", + adviceSaveDto.getAccountId()); + needNewAccount = true; + } + } + + if (needNewAccount) { // 尝试从患者就诊中获取默认账户ID(自费账户) Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId()); if (selfAccount != null) { adviceSaveDto.setAccountId(selfAccount.getId()); + log.info("handMedication - 使用现有自费账户,accountId={}", selfAccount.getId()); } else { // 自动创建自费账户 Account newAccount = new Account(); @@ -961,6 +1036,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo()); Long newAccountId = iAccountService.saveAccountByRegister(newAccount); adviceSaveDto.setAccountId(newAccountId); + log.info("handMedication - 自动创建自费账户,newAccountId={}", newAccountId); } } @@ -1351,12 +1427,26 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp log.info("BugFix#219: ========== handDevice END =========="); for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) { - // 🔧 Bug Fix: 确保accountId不为null + // 🔧 Bug Fix: 确保accountId有效(不为null且账户存在) + boolean needNewAccount = false; if (adviceSaveDto.getAccountId() == null) { + needNewAccount = true; + } else { + // 验证账户是否存在且有效(未被删除,租户匹配) + Account existingAccount = iAccountService.getById(adviceSaveDto.getAccountId()); + if (existingAccount == null) { + log.warn("handDevice - 前端传入的accountId无效(账户不存在),accountId={},将重新获取或创建账户", + adviceSaveDto.getAccountId()); + needNewAccount = true; + } + } + + if (needNewAccount) { // 尝试从患者就诊中获取默认账户ID(自费账户) Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId()); if (selfAccount != null) { adviceSaveDto.setAccountId(selfAccount.getId()); + log.info("handDevice - 使用现有自费账户,accountId={}", selfAccount.getId()); } else { // 自动创建自费账户 Account newAccount = new Account(); @@ -1370,9 +1460,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo()); Long newAccountId = iAccountService.saveAccountByRegister(newAccount); adviceSaveDto.setAccountId(newAccountId); + log.info("handDevice - 自动创建自费账户,newAccountId={}", newAccountId); } } - + // 🔧 Bug Fix: 确保practitionerId不为null if (adviceSaveDto.getPractitionerId() == null) { adviceSaveDto.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId()); @@ -1607,12 +1698,26 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp } for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) { - // 🔧 Bug Fix: 确保accountId不为null + // 🔧 Bug Fix: 确保accountId有效(不为null且账户存在) + boolean needNewAccount = false; if (adviceSaveDto.getAccountId() == null) { + needNewAccount = true; + } else { + // 验证账户是否存在且有效(未被删除,租户匹配) + Account existingAccount = iAccountService.getById(adviceSaveDto.getAccountId()); + if (existingAccount == null) { + log.warn("handService - 前端传入的accountId无效(账户不存在),accountId={},将重新获取或创建账户", + adviceSaveDto.getAccountId()); + needNewAccount = true; + } + } + + if (needNewAccount) { // 尝试从患者就诊中获取默认账户ID(自费账户) Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId()); if (selfAccount != null) { adviceSaveDto.setAccountId(selfAccount.getId()); + log.info("handService - 使用现有自费账户,accountId={}", selfAccount.getId()); } else { // 自动创建自费账户 Account newAccount = new Account(); @@ -1626,9 +1731,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo()); Long newAccountId = iAccountService.saveAccountByRegister(newAccount); adviceSaveDto.setAccountId(newAccountId); + log.info("handService - 自动创建自费账户,newAccountId={}", newAccountId); } } - + // 🔧 Bug Fix: 确保practitionerId不为null if (adviceSaveDto.getPractitionerId() == null) { adviceSaveDto.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId()); @@ -1677,8 +1783,14 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp // 普通诊疗医嘱 serviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum()); } - - serviceRequest.setActivityId(adviceSaveDto.getAdviceDefinitionId());// 诊疗定义id + + // 🔧 BugFix#385: 检查类型(adviceType=2)不走定价体系,activityId设置为0L占位 + // 与ExamApplyController保持一致,数据库有NOT NULL约束 + if (adviceSaveDto.getAdviceType() != null && adviceSaveDto.getAdviceType() == 2) { + serviceRequest.setActivityId(0L); + } else { + serviceRequest.setActivityId(adviceSaveDto.getAdviceDefinitionId());// 诊疗定义id + } serviceRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者 serviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生 serviceRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/IChargeBillServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/IChargeBillServiceImpl.java index cac8a2bb..74646c78 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/IChargeBillServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/IChargeBillServiceImpl.java @@ -41,7 +41,9 @@ import com.openhis.web.paymentmanage.appservice.IChargeBillService; import com.openhis.web.paymentmanage.dto.*; import com.openhis.web.paymentmanage.mapper.ChargeBillMapper; import com.openhis.workflow.domain.ActivityDefinition; +import com.openhis.workflow.domain.ServiceRequest; import com.openhis.workflow.service.IActivityDefinitionService; +import com.openhis.workflow.service.IServiceRequestService; import com.openhis.yb.domain.ClinicSettle; import com.openhis.yb.domain.ClinicUnSettle; import com.openhis.yb.domain.InfoPerson; @@ -111,6 +113,8 @@ public class IChargeBillServiceImpl implements IChargeBillService { @Autowired private IActivityDefinitionService iActivityDefinitionService; @Autowired + private IServiceRequestService iServiceRequestService; + @Autowired private IPractitionerService iPractitionerService; @Autowired private IHealthcareServiceService iHealthcareServiceService; @@ -265,10 +269,31 @@ public class IChargeBillServiceImpl implements IChargeBillService { .setTotalPrice(chargeItem.getTotalPrice()).setQuantityUnit(chargeItem.getQuantityUnit()) .setTotalVolume(device.getSize()).setQuantityValue(chargeItem.getQuantityValue()); } else if (CommonConstants.TableName.WOR_ACTIVITY_DEFINITION.equals(chargeItem.getProductTable())) { - ActivityDefinition activity = iActivityDefinitionService.getById(chargeItem.getProductId()); - chargeItemDetailVO.setDirClass(activity.getChrgitmLv() + "").setChargeItemName(activity.getName()) - .setTotalPrice(chargeItem.getTotalPrice()).setQuantityUnit(chargeItem.getQuantityUnit()) - .setTotalVolume("").setQuantityValue(chargeItem.getQuantityValue()); + // 🔧 BugFix#385: 检查申请创建的收费项 productId=0,需从 ServiceRequest.contentJson 获取项目名称 + if (chargeItem.getProductId() != null && chargeItem.getProductId() > 0) { + ActivityDefinition activity = iActivityDefinitionService.getById(chargeItem.getProductId()); + chargeItemDetailVO.setDirClass(activity.getChrgitmLv() + "").setChargeItemName(activity.getName()) + .setTotalPrice(chargeItem.getTotalPrice()).setQuantityUnit(chargeItem.getQuantityUnit()) + .setTotalVolume("").setQuantityValue(chargeItem.getQuantityValue()); + } else { + // productId=0 时,从关联的 ServiceRequest 获取项目名称 + ServiceRequest serviceRequest = iServiceRequestService.getById(chargeItem.getServiceId()); + String itemName = "未知项目"; + String dirClass = "3"; // 默认诊疗类 + if (serviceRequest != null && serviceRequest.getContentJson() != null) { + try { + JSONObject json = JSON.parseObject(serviceRequest.getContentJson()); + if (json.containsKey("adviceName")) { + itemName = json.getString("adviceName"); + } + } catch (Exception e) { + log.warn("解析ServiceRequest.contentJson失败: {}", e.getMessage()); + } + } + chargeItemDetailVO.setDirClass(dirClass).setChargeItemName(itemName) + .setTotalPrice(chargeItem.getTotalPrice()).setQuantityUnit(chargeItem.getQuantityUnit()) + .setTotalVolume("").setQuantityValue(chargeItem.getQuantityValue()); + } } else { HealthcareService healthcareService = iHealthcareServiceService.getById(chargeItem.getServiceId()); chargeItemDetailVO.setDirClass("3").setChargeItemName(healthcareService.getName()) @@ -347,7 +372,19 @@ public class IChargeBillServiceImpl implements IChargeBillService { Long definitionId = chargeItem.getDefinitionId(); - ChargeItemDefinition chargeItemDefinition = iChargeItemDefinitionService.getById(definitionId); + // 🔧 BugFix#385: 检查申请创建的收费项 definition_id=0,chargeItemDefinition 会为 null + ChargeItemDefinition chargeItemDefinition = null; + if (definitionId != null && definitionId > 0) { + chargeItemDefinition = iChargeItemDefinitionService.getById(definitionId); + } + + // 当 definitionId=0 或 chargeItemDefinition 为 null 时,跳过医保分类统计 + // 检查类项目默认归类为"检查费" + if (chargeItemDefinition == null) { + // 检查申请的收费项,归类为检查费(03) + sum03 = sum03.add(chargeItem.getTotalPrice()); + continue; + } YbMedChrgItmType medChrgItmType = YbMedChrgItmType.getByCode(Integer.parseInt(chargeItemDefinition.getYbType())); diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/PaymentRecServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/PaymentRecServiceImpl.java index 8c6ebe39..70f33ac4 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/PaymentRecServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/PaymentRecServiceImpl.java @@ -6,6 +6,7 @@ package com.openhis.web.paymentmanage.appservice.impl; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.core.common.core.domain.R; @@ -241,14 +242,21 @@ public class PaymentRecServiceImpl implements IPaymentRecService { .collect(Collectors.toList()); // account去重 List distinctAccountIdList = accountIdList.stream().distinct().collect(Collectors.toList()); - // 检查是否存在accountId为null的收费项 + // 检查是否存在accountId为null或0的收费项 long nullAccountIdCount = chargeItemList.stream() .map(ChargeItem::getAccountId) .filter(Objects::isNull) .count(); + long zeroAccountIdCount = chargeItemList.stream() + .map(ChargeItem::getAccountId) + .filter(id -> id != null && id == 0L) + .count(); if (nullAccountIdCount > 0) { throw new ServiceException("部分收费项缺少账户信息,请检查收费项数据"); } + if (zeroAccountIdCount > 0) { + throw new ServiceException("部分收费项账户ID为0(无效),请检查收费项数据或重新创建检查申请"); + } if (distinctAccountIdList.isEmpty()) { throw new ServiceException("未找到有效的账户信息"); } @@ -257,15 +265,66 @@ public class PaymentRecServiceImpl implements IPaymentRecService { // 在挂号费等场景下可能因数据不一致导致查不到,引发误报 List accountList = iAccountService.list(new LambdaQueryWrapper() .in(Account::getId, distinctAccountIdList)); + + // 🔧 Bug Fix: 处理账户不存在的情况(历史数据修复) if (accountList.size() != distinctAccountIdList.size()) { - // 部分账户查不到时,记录警告日志,并校验是否每个收费项都能找到对应账户 Set foundAccountIds = accountList.stream().map(Account::getId).collect(Collectors.toSet()); List missingAccountIds = distinctAccountIdList.stream() .filter(id -> !foundAccountIds.contains(id)).collect(Collectors.toList()); - if (accountList.isEmpty()) { - throw new ServiceException("未查询到任何账户信息,encounterId:" + prePaymentDto.getEncounterId() - + ",期望accountId列表:" + distinctAccountIdList); + + logger.warn("预结算发现部分账户不存在,missingAccountIds={},将自动修复收费项的accountId", + missingAccountIds); + + // 获取或创建有效的自费账户 + Account selfAccount = iAccountService.getSelfAccount(prePaymentDto.getEncounterId()); + if (selfAccount == null) { + // 自动创建自费账户 + Account newAccount = new Account(); + newAccount.setPatientId(chargeItemList.get(0).getPatientId()); + newAccount.setEncounterId(prePaymentDto.getEncounterId()); + newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO); + newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode()); + newAccount.setBalanceAmount(BigDecimal.ZERO); + newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue()); + newAccount.setEncounterFlag(Whether.YES.getValue()); + newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo()); + Long newAccountId = iAccountService.saveAccountByRegister(newAccount); + selfAccount = iAccountService.getById(newAccountId); + logger.info("预结算自动创建自费账户,newAccountId={}", newAccountId); } + + // 修复收费项的 accountId + for (Long missingAccountId : missingAccountIds) { + // 找到使用该无效 accountId 的收费项 + List affectedChargeItems = chargeItemList.stream() + .filter(ci -> ci.getAccountId() != null && ci.getAccountId().equals(missingAccountId)) + .collect(Collectors.toList()); + + for (ChargeItem ci : affectedChargeItems) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(ChargeItem::getId, ci.getId()) + .set(ChargeItem::getAccountId, selfAccount.getId()); + iChargeItemService.update(updateWrapper); + logger.info("预结算修复收费项accountId,chargeItemId={},oldAccountId={},newAccountId={}", + ci.getId(), missingAccountId, selfAccount.getId()); + + // 更新本地对象的 accountId,以便后续处理使用正确的值 + ci.setAccountId(selfAccount.getId()); + } + } + + // 重新查询账户列表 + accountList = iAccountService.list(new LambdaQueryWrapper() + .eq(Account::getId, selfAccount.getId())); + + // 重新构建 accountIdList(已修复) + distinctAccountIdList = chargeItemList.stream() + .map(ChargeItem::getAccountId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + + logger.info("预结算账户修复完成,最终使用accountId={}", selfAccount.getId()); } // 账户id,对应的账单列表 diff --git a/openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientChargeAppMapper.xml b/openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientChargeAppMapper.xml index b2ac4cbc..0bf7f396 100644 --- a/openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientChargeAppMapper.xml +++ b/openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientChargeAppMapper.xml @@ -94,18 +94,21 @@ T8.contract_name, CASE WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name + WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name") WHEN T1.context_enum = #{activity} THEN T2."name" WHEN T1.context_enum = #{medication} THEN T3."name" WHEN T1.context_enum = #{device} THEN T4."name" END AS item_name, CASE WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL + WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL WHEN T1.context_enum = #{activity} THEN T2.yb_no WHEN T1.context_enum = #{medication} THEN T3.yb_no WHEN T1.context_enum = #{device} THEN T4.yb_no END AS yb_no, CASE WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id + WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0 WHEN T1.context_enum = #{activity} THEN T2.id WHEN T1.context_enum = #{medication} THEN T3.id WHEN T1.context_enum = #{device} THEN T4.id @@ -205,18 +208,21 @@ T8.contract_name, CASE WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name + WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name") WHEN T1.context_enum = #{activity} THEN T2."name" WHEN T1.context_enum = #{medication} THEN T3."name" WHEN T1.context_enum = #{device} THEN T4."name" END AS item_name, CASE WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL + WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN NULL WHEN T1.context_enum = #{activity} THEN T2.yb_no WHEN T1.context_enum = #{medication} THEN T3.yb_no WHEN T1.context_enum = #{device} THEN T4.yb_no END AS yb_no, CASE WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id + WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN 0 WHEN T1.context_enum = #{activity} THEN T2.id WHEN T1.context_enum = #{medication} THEN T3.id WHEN T1.context_enum = #{device} THEN T4.id diff --git a/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml b/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml index 046bf10e..aa87ce69 100644 --- a/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml +++ b/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml @@ -630,6 +630,8 @@ T3.service_table = #{WOR_DEVICE_REQUEST} LEFT JOIN adm_location AS al ON al.ID = T1.perform_location AND al.delete_flag = '0' WHERE T1.delete_flag = '0' AND T1.generate_source_enum = #{generateSourceEnum} + -- 🔧 Bug Fix: 排除基于其他医嘱生成的执行记录 + AND (T1.based_on_id IS NULL OR T1.based_on_table IS NULL) AND T1.encounter_id = #{encounterId} @@ -686,6 +688,10 @@ WHERE T1.delete_flag = '0' AND T1.generate_source_enum = #{generateSourceEnum} AND T1.parent_id IS NULL AND T1.refund_service_id IS NULL + -- 🔧 Bug Fix: 排除基于药品请求生成的执行记录(输液、皮试),但保留检查/检验申请单创建的原始医嘱 + -- based_on_table='med_medication_request' → 输液/皮试执行记录,应排除 + -- based_on_table='exam_apply'/'lab_apply' → 申请单原始医嘱,应保留 + AND (T1.based_on_id IS NULL OR T1.based_on_table IS NULL OR T1.based_on_table NOT IN ('med_medication_request', 'med_medication_dispense')) AND T1.encounter_id = #{encounterId} diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/domain/ServiceRequest.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/domain/ServiceRequest.java index 7c05ff56..e1283867 100644 --- a/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/domain/ServiceRequest.java +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/domain/ServiceRequest.java @@ -1,6 +1,8 @@ package com.openhis.workflow.domain; +import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.core.common.core.domain.HisBaseEntity; @@ -64,6 +66,7 @@ public class ServiceRequest extends HisBaseEntity { private Integer performFlag; /** 诊疗定义id */ + @TableField(insertStrategy = FieldStrategy.ALWAYS) private Long activityId; /** 数量 */