From af32366bf43ad761d7841c90e78ab63282c3da23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B3=E7=BE=BD?= <关羽@gentronhealth.com> Date: Thu, 14 May 2026 22:21:16 +0800 Subject: [PATCH] =?UTF-8?q?Fix=20Bug=20#480:=20[=E4=BD=8F=E9=99=A2?= =?UTF-8?q?=E6=8A=A4=E5=A3=AB=E7=AB=99-=E5=8C=BB=E5=98=B1=E6=89=A7?= =?UTF-8?q?=E8=A1=8C]=20=E9=9D=9E=E8=80=97=E6=9D=90=E7=B1=BB=E5=8C=BB?= =?UTF-8?q?=E5=98=B1=E6=89=A7=E8=A1=8C=E6=8A=A5"=E8=80=97=E6=9D=90?= =?UTF-8?q?=E5=BA=93=E5=AD=98"=E9=94=99=E8=AF=AF=E4=B8=94=E5=85=A8?= =?UTF-8?q?=E9=80=89=E9=80=BB=E8=BE=91=E8=81=94=E5=8A=A8=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因分析: 1. 耗材库存报错:lotNumberMatch() 按 encounterId 查询 ALL 待发放 DeviceDispense, 不区分是否为本次执行的医嘱。若该就诊存在其他未执行的耗材记录且库存为零, 整个调用就会失败,导致非耗材类医嘱执行也被拦截。 2. 全选联动异常:toggleRowSelection() 程序化选中会触发 @select 事件, handleRowSelect 中调用 selectAllCheckboxesInRow 导致级联全选。 修复方案: - 后端:lotNumberMatch 新增 requestIdList 可选参数,当传入时通过 DeviceRequest.basedOnId 过滤仅校验与本次执行医嘱关联的耗材记录,避免其他未执行医嘱干扰 - 前端:handleExecute 传入 selectedRequestIds(仅诊疗类医嘱的 requestId) - 前端:新增 skipSelectCascade 标志,程序化 toggleRowSelection 时阻止 handleRowSelect 触发 selectAllCheckboxesInRow,消除级联反馈环 Co-Authored-By: Claude Opus 4.7 --- .../web/common/appservice/ICommonService.java | 3 +- .../appservice/impl/CommonServiceImpl.java | 33 ++++++++++++++++--- .../controller/CommonAppController.java | 7 ++-- .../components/prescriptionList.vue | 22 +++++++++++-- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/appservice/ICommonService.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/appservice/ICommonService.java index b436d532c..124452239 100755 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/appservice/ICommonService.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/appservice/ICommonService.java @@ -200,9 +200,10 @@ public interface ICommonService { * 批号匹配 * * @param encounterIdList 就诊id列表 + * @param requestIdList 医嘱请求id列表(可选,用于限定仅校验与当前执行医嘱关联的耗材) * @return 处理结果 */ - R lotNumberMatch(List encounterIdList); + R lotNumberMatch(List encounterIdList, List requestIdList); /** * 根据机构ID获取机构名称 diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/appservice/impl/CommonServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/appservice/impl/CommonServiceImpl.java index 62c2bbff4..f136eda28 100755 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/appservice/impl/CommonServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/appservice/impl/CommonServiceImpl.java @@ -39,8 +39,10 @@ import com.openhis.web.common.dto.*; import com.openhis.web.common.mapper.CommonAppMapper; import com.openhis.web.pharmacymanage.dto.InventoryDetailDto; import com.openhis.workflow.domain.DeviceDispense; +import com.openhis.workflow.domain.DeviceRequest; import com.openhis.workflow.domain.InventoryItem; import com.openhis.workflow.service.IDeviceDispenseService; +import com.openhis.workflow.service.IDeviceRequestService; import com.openhis.workflow.service.IInventoryItemService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -99,6 +101,9 @@ public class CommonServiceImpl implements ICommonService { @Resource private IDeviceDispenseService deviceDispenseService; + @Resource + private IDeviceRequestService deviceRequestService; + /** * 获取药房列表 * @@ -678,10 +683,11 @@ public class CommonServiceImpl implements ICommonService { * 批号匹配 * * @param encounterIdList 就诊id列表 + * @param requestIdList 医嘱请求id列表(可选,用于限定仅校验与当前执行医嘱关联的耗材) * @return 处理结果 */ @Override - public R lotNumberMatch(List encounterIdList) { + public R lotNumberMatch(List encounterIdList, List requestIdList) { // 查询患者待发放的药品信息 List medicationDispenseList = medicationDispenseService .list(new LambdaQueryWrapper().in(MedicationDispense::getEncounterId, encounterIdList) @@ -798,10 +804,27 @@ public class CommonServiceImpl implements ICommonService { } } // 查询患者待发放的耗材信息 - List deviceDispenseList = deviceDispenseService - .list(new LambdaQueryWrapper().in(DeviceDispense::getEncounterId, encounterIdList) - .eq(DeviceDispense::getStatusEnum, DispenseStatus.PREPARATION.getValue()) - .eq(DeviceDispense::getDeleteFlag, DelFlag.NO.getCode())); + LambdaQueryWrapper deviceDispenseQuery = new LambdaQueryWrapper() + .in(DeviceDispense::getEncounterId, encounterIdList) + .eq(DeviceDispense::getStatusEnum, DispenseStatus.PREPARATION.getValue()) + .eq(DeviceDispense::getDeleteFlag, DelFlag.NO.getCode()); + // 若传入requestIdList,则仅查询与指定医嘱请求关联的耗材,避免其他未执行医嘱的耗材记录干扰 + if (requestIdList != null && !requestIdList.isEmpty()) { + List deviceReqIds = deviceRequestService + .list(new LambdaQueryWrapper() + .in(DeviceRequest::getBasedOnId, requestIdList) + .eq(DeviceRequest::getBasedOnTable, CommonConstants.TableName.WOR_SERVICE_REQUEST)) + .stream() + .map(DeviceRequest::getId) + .collect(java.util.stream.Collectors.toList()); + if (!deviceReqIds.isEmpty()) { + deviceDispenseQuery.in(DeviceDispense::getDeviceReqId, deviceReqIds); + } else { + // 无关联的耗材请求,直接跳过耗材校验 + deviceDispenseQuery.eq(DeviceDispense::getId, -1L); + } + } + List deviceDispenseList = deviceDispenseService.list(deviceDispenseQuery); // 耗材批号匹配 if (deviceDispenseList != null && !deviceDispenseList.isEmpty()) { // 获取待发放的耗材id diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/controller/CommonAppController.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/controller/CommonAppController.java index d3a0dfd1a..d6bb32032 100755 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/controller/CommonAppController.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/common/controller/CommonAppController.java @@ -274,10 +274,13 @@ public class CommonAppController { * 批号匹配 * * @param encounterIdList 就诊id列表 + * @param requestIdList 医嘱请求id列表(可选,用于限定仅校验与当前执行医嘱关联的耗材) * @return 处理结果 */ @GetMapping("/lot-number-match") - public R lotNumberMatch(@RequestParam(value = "encounterIdList") List encounterIdList) { - return commonService.lotNumberMatch(encounterIdList); + public R lotNumberMatch( + @RequestParam(value = "encounterIdList") List encounterIdList, + @RequestParam(value = "requestIdList", required = false) List requestIdList) { + return commonService.lotNumberMatch(encounterIdList, requestIdList); } } diff --git a/openhis-ui-vue3/src/views/inpatientNurse/medicalOrderExecution/components/prescriptionList.vue b/openhis-ui-vue3/src/views/inpatientNurse/medicalOrderExecution/components/prescriptionList.vue index 646a03252..c46888b3a 100755 --- a/openhis-ui-vue3/src/views/inpatientNurse/medicalOrderExecution/components/prescriptionList.vue +++ b/openhis-ui-vue3/src/views/inpatientNurse/medicalOrderExecution/components/prescriptionList.vue @@ -241,6 +241,8 @@ const loading = ref(false); const chooseAll = ref(false); // 独立维护选中行ID集合,避免el-table内部selection状态异常导致联动全选 const selectedRowIds = ref(new Set()); +// 跳过选中事件级联:程序化调用 toggleRowSelection 时阻止 handleRowSelect 触发 selectAllCheckboxesInRow +const skipSelectCascade = ref(false); const props = defineProps({ exeStatus: { type: Number, @@ -484,8 +486,13 @@ function handleExecute() { if (hasServiceRequest) { // 仅传入选中医嘱对应的 encounterId,避免其他患者的耗材记录干扰 const selectedEncounterIds = [...new Set(list.map((item) => item.encounterId).filter(Boolean))]; + // 仅传入诊疗类医嘱的 requestId,让后端仅校验与本次执行相关的耗材,避免其他未执行医嘱的耗材记录干扰 + const selectedRequestIds = list + .filter((item) => String(item.adviceTable || '') === 'wor_service_request') + .map((item) => item.requestId) + .filter(Boolean); if (selectedEncounterIds.length > 0) { - lotNumberMatch({ encounterIdList: selectedEncounterIds }, { skipErrorMsg: true }) + lotNumberMatch({ encounterIdList: selectedEncounterIds, requestIdList: selectedRequestIds }, { skipErrorMsg: true }) .then((matchRes) => { if (matchRes && matchRes.code !== 200) { console.warn('lotNumberMatch returned error:', matchRes.msg); @@ -650,6 +657,7 @@ function handleRateChange(value, date, time, row, rateItem) { } function handelSwicthChange(value) { + skipSelectCascade.value = true; prescriptionList.value.forEach((item, index) => { const tableRef = proxy.$refs['tableRef' + index]; if (tableRef && tableRef[0]) { @@ -670,12 +678,15 @@ function handelSwicthChange(value) { } } }); + skipSelectCascade.value = false; } // 默认选中全部行 function defaultSelectAllRows() { // 清空并重建选中集合 selectedRowIds.value.clear(); + // 阻止 toggleRowSelection 触发 handleRowSelect 中的 selectAllCheckboxesInRow 级联 + skipSelectCascade.value = true; prescriptionList.value.forEach((item, index) => { const tableRef = proxy.$refs['tableRef' + index]; if (tableRef && tableRef[0]) { @@ -688,6 +699,7 @@ function defaultSelectAllRows() { }); } }); + skipSelectCascade.value = false; // 更新全选开关状态 chooseAll.value = true; } @@ -764,6 +776,7 @@ function checkAndToggleRowSelection(row) { const isCurrentlySelected = selectedRowIds.value.has(row.requestId); // 根据checkbox状态更新表格行选中状态 + skipSelectCascade.value = true; if (isAllSelected && !isCurrentlySelected) { selectedRowIds.value.add(row.requestId); tableRef[0].toggleRowSelection(row, true); @@ -771,6 +784,7 @@ function checkAndToggleRowSelection(row) { selectedRowIds.value.delete(row.requestId); tableRef[0].toggleRowSelection(row, false); } + skipSelectCascade.value = false; } } }); @@ -782,8 +796,10 @@ function handleRowSelect(selection, row, tableIndex) { if (isSelected) { selectedRowIds.value.add(row.requestId); - // 选中行时,选中该行内部的所有checkbox - selectAllCheckboxesInRow(row); + // 仅在非程序化选中时,联动选中该行内部的所有checkbox + if (!skipSelectCascade.value) { + selectAllCheckboxesInRow(row); + } } else { selectedRowIds.value.delete(row.requestId); // 取消选中行时,取消选中该行内部的所有checkbox