fix: Bug #407 #385 检查申请医嘱分类错误及预结算账户验证修复

主要修复:
  - 检查申请医嘱类型正确设置为诊疗(3),避免被错误归类为药品
  - 检查申请收费项获取真实自费账户,预结算验证accountId必须有效存在
  - 签发时补充诊疗费用项诊断关联信息变更模块:
  - ExamApplyController:使用ItemType枚举,获取真实账户替代占位值0
-DoctorStationAdviceAppService:按枚举标准分类医嘱,验证accountId有效性
  - IChargeBillService:productId=0时从ServiceRequest.contentJson获取项目名称
  - PaymentRecService:预结算自动修复账户不存在的历史数据
  - Mapper:排除衍生执行记录,productId=0时补充查询逻辑
  - ServiceRequest实体:activityId字段添加ALWAYS插入策略
This commit is contained in:
wangjian963
2026-04-23 17:17:04 +08:00
parent 2a8e662b44
commit 95e379e5a5
7 changed files with 293 additions and 47 deletions

View File

@@ -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);

View File

@@ -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<AdviceSaveDto> 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<AdviceSaveDto> 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<AdviceSaveDto> deviceList = adviceSaveList.stream()
.filter(e -> e.getAdviceType() != null && e.getAdviceType() == ItemType.DEVICE.getValue())
.collect(Collectors.toList());
// 诊疗分类adviceType == 3
List<AdviceSaveDto> 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<EncounterDiagnosis> 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<ChargeItem> 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

View File

@@ -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=0chargeItemDefinition 会为 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()));

View File

@@ -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<Long> 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<Account> accountList = iAccountService.list(new LambdaQueryWrapper<Account>()
.in(Account::getId, distinctAccountIdList));
// 🔧 Bug Fix: 处理账户不存在的情况(历史数据修复)
if (accountList.size() != distinctAccountIdList.size()) {
// 部分账户查不到时,记录警告日志,并校验是否每个收费项都能找到对应账户
Set<Long> foundAccountIds = accountList.stream().map(Account::getId).collect(Collectors.toSet());
List<Long> 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<ChargeItem> affectedChargeItems = chargeItemList.stream()
.filter(ci -> ci.getAccountId() != null && ci.getAccountId().equals(missingAccountId))
.collect(Collectors.toList());
for (ChargeItem ci : affectedChargeItems) {
LambdaUpdateWrapper<ChargeItem> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(ChargeItem::getId, ci.getId())
.set(ChargeItem::getAccountId, selfAccount.getId());
iChargeItemService.update(updateWrapper);
logger.info("预结算修复收费项accountIdchargeItemId={}oldAccountId={}newAccountId={}",
ci.getId(), missingAccountId, selfAccount.getId());
// 更新本地对象的 accountId以便后续处理使用正确的值
ci.setAccountId(selfAccount.getId());
}
}
// 重新查询账户列表
accountList = iAccountService.list(new LambdaQueryWrapper<Account>()
.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对应的账单列表

View File

@@ -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

View File

@@ -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)
<if test="historyFlag == '0'.toString()">
AND T1.encounter_id = #{encounterId}
</if>
@@ -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'))
<if test="historyFlag == '0'.toString()">
AND T1.encounter_id = #{encounterId}
</if>