Fix Bug #480: [住院护士站-医嘱执行] 非耗材类医嘱执行报"耗材库存"错误且全选逻辑联动异常
根因分析: 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 <noreply@anthropic.com>
This commit is contained in:
@@ -200,9 +200,10 @@ public interface ICommonService {
|
|||||||
* 批号匹配
|
* 批号匹配
|
||||||
*
|
*
|
||||||
* @param encounterIdList 就诊id列表
|
* @param encounterIdList 就诊id列表
|
||||||
|
* @param requestIdList 医嘱请求id列表(可选,用于限定仅校验与当前执行医嘱关联的耗材)
|
||||||
* @return 处理结果
|
* @return 处理结果
|
||||||
*/
|
*/
|
||||||
R<?> lotNumberMatch(List<Long> encounterIdList);
|
R<?> lotNumberMatch(List<Long> encounterIdList, List<Long> requestIdList);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据机构ID获取机构名称
|
* 根据机构ID获取机构名称
|
||||||
|
|||||||
@@ -39,8 +39,10 @@ import com.openhis.web.common.dto.*;
|
|||||||
import com.openhis.web.common.mapper.CommonAppMapper;
|
import com.openhis.web.common.mapper.CommonAppMapper;
|
||||||
import com.openhis.web.pharmacymanage.dto.InventoryDetailDto;
|
import com.openhis.web.pharmacymanage.dto.InventoryDetailDto;
|
||||||
import com.openhis.workflow.domain.DeviceDispense;
|
import com.openhis.workflow.domain.DeviceDispense;
|
||||||
|
import com.openhis.workflow.domain.DeviceRequest;
|
||||||
import com.openhis.workflow.domain.InventoryItem;
|
import com.openhis.workflow.domain.InventoryItem;
|
||||||
import com.openhis.workflow.service.IDeviceDispenseService;
|
import com.openhis.workflow.service.IDeviceDispenseService;
|
||||||
|
import com.openhis.workflow.service.IDeviceRequestService;
|
||||||
import com.openhis.workflow.service.IInventoryItemService;
|
import com.openhis.workflow.service.IInventoryItemService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -99,6 +101,9 @@ public class CommonServiceImpl implements ICommonService {
|
|||||||
@Resource
|
@Resource
|
||||||
private IDeviceDispenseService deviceDispenseService;
|
private IDeviceDispenseService deviceDispenseService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IDeviceRequestService deviceRequestService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取药房列表
|
* 获取药房列表
|
||||||
*
|
*
|
||||||
@@ -678,10 +683,11 @@ public class CommonServiceImpl implements ICommonService {
|
|||||||
* 批号匹配
|
* 批号匹配
|
||||||
*
|
*
|
||||||
* @param encounterIdList 就诊id列表
|
* @param encounterIdList 就诊id列表
|
||||||
|
* @param requestIdList 医嘱请求id列表(可选,用于限定仅校验与当前执行医嘱关联的耗材)
|
||||||
* @return 处理结果
|
* @return 处理结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> lotNumberMatch(List<Long> encounterIdList) {
|
public R<?> lotNumberMatch(List<Long> encounterIdList, List<Long> requestIdList) {
|
||||||
// 查询患者待发放的药品信息
|
// 查询患者待发放的药品信息
|
||||||
List<MedicationDispense> medicationDispenseList = medicationDispenseService
|
List<MedicationDispense> medicationDispenseList = medicationDispenseService
|
||||||
.list(new LambdaQueryWrapper<MedicationDispense>().in(MedicationDispense::getEncounterId, encounterIdList)
|
.list(new LambdaQueryWrapper<MedicationDispense>().in(MedicationDispense::getEncounterId, encounterIdList)
|
||||||
@@ -798,10 +804,27 @@ public class CommonServiceImpl implements ICommonService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 查询患者待发放的耗材信息
|
// 查询患者待发放的耗材信息
|
||||||
List<DeviceDispense> deviceDispenseList = deviceDispenseService
|
LambdaQueryWrapper<DeviceDispense> deviceDispenseQuery = new LambdaQueryWrapper<DeviceDispense>()
|
||||||
.list(new LambdaQueryWrapper<DeviceDispense>().in(DeviceDispense::getEncounterId, encounterIdList)
|
.in(DeviceDispense::getEncounterId, encounterIdList)
|
||||||
.eq(DeviceDispense::getStatusEnum, DispenseStatus.PREPARATION.getValue())
|
.eq(DeviceDispense::getStatusEnum, DispenseStatus.PREPARATION.getValue())
|
||||||
.eq(DeviceDispense::getDeleteFlag, DelFlag.NO.getCode()));
|
.eq(DeviceDispense::getDeleteFlag, DelFlag.NO.getCode());
|
||||||
|
// 若传入requestIdList,则仅查询与指定医嘱请求关联的耗材,避免其他未执行医嘱的耗材记录干扰
|
||||||
|
if (requestIdList != null && !requestIdList.isEmpty()) {
|
||||||
|
List<Long> deviceReqIds = deviceRequestService
|
||||||
|
.list(new LambdaQueryWrapper<DeviceRequest>()
|
||||||
|
.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<DeviceDispense> deviceDispenseList = deviceDispenseService.list(deviceDispenseQuery);
|
||||||
// 耗材批号匹配
|
// 耗材批号匹配
|
||||||
if (deviceDispenseList != null && !deviceDispenseList.isEmpty()) {
|
if (deviceDispenseList != null && !deviceDispenseList.isEmpty()) {
|
||||||
// 获取待发放的耗材id
|
// 获取待发放的耗材id
|
||||||
|
|||||||
@@ -274,10 +274,13 @@ public class CommonAppController {
|
|||||||
* 批号匹配
|
* 批号匹配
|
||||||
*
|
*
|
||||||
* @param encounterIdList 就诊id列表
|
* @param encounterIdList 就诊id列表
|
||||||
|
* @param requestIdList 医嘱请求id列表(可选,用于限定仅校验与当前执行医嘱关联的耗材)
|
||||||
* @return 处理结果
|
* @return 处理结果
|
||||||
*/
|
*/
|
||||||
@GetMapping("/lot-number-match")
|
@GetMapping("/lot-number-match")
|
||||||
public R<?> lotNumberMatch(@RequestParam(value = "encounterIdList") List<Long> encounterIdList) {
|
public R<?> lotNumberMatch(
|
||||||
return commonService.lotNumberMatch(encounterIdList);
|
@RequestParam(value = "encounterIdList") List<Long> encounterIdList,
|
||||||
|
@RequestParam(value = "requestIdList", required = false) List<Long> requestIdList) {
|
||||||
|
return commonService.lotNumberMatch(encounterIdList, requestIdList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,6 +241,8 @@ const loading = ref(false);
|
|||||||
const chooseAll = ref(false);
|
const chooseAll = ref(false);
|
||||||
// 独立维护选中行ID集合,避免el-table内部selection状态异常导致联动全选
|
// 独立维护选中行ID集合,避免el-table内部selection状态异常导致联动全选
|
||||||
const selectedRowIds = ref(new Set());
|
const selectedRowIds = ref(new Set());
|
||||||
|
// 跳过选中事件级联:程序化调用 toggleRowSelection 时阻止 handleRowSelect 触发 selectAllCheckboxesInRow
|
||||||
|
const skipSelectCascade = ref(false);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
exeStatus: {
|
exeStatus: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -484,8 +486,13 @@ function handleExecute() {
|
|||||||
if (hasServiceRequest) {
|
if (hasServiceRequest) {
|
||||||
// 仅传入选中医嘱对应的 encounterId,避免其他患者的耗材记录干扰
|
// 仅传入选中医嘱对应的 encounterId,避免其他患者的耗材记录干扰
|
||||||
const selectedEncounterIds = [...new Set(list.map((item) => item.encounterId).filter(Boolean))];
|
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) {
|
if (selectedEncounterIds.length > 0) {
|
||||||
lotNumberMatch({ encounterIdList: selectedEncounterIds }, { skipErrorMsg: true })
|
lotNumberMatch({ encounterIdList: selectedEncounterIds, requestIdList: selectedRequestIds }, { skipErrorMsg: true })
|
||||||
.then((matchRes) => {
|
.then((matchRes) => {
|
||||||
if (matchRes && matchRes.code !== 200) {
|
if (matchRes && matchRes.code !== 200) {
|
||||||
console.warn('lotNumberMatch returned error:', matchRes.msg);
|
console.warn('lotNumberMatch returned error:', matchRes.msg);
|
||||||
@@ -650,6 +657,7 @@ function handleRateChange(value, date, time, row, rateItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handelSwicthChange(value) {
|
function handelSwicthChange(value) {
|
||||||
|
skipSelectCascade.value = true;
|
||||||
prescriptionList.value.forEach((item, index) => {
|
prescriptionList.value.forEach((item, index) => {
|
||||||
const tableRef = proxy.$refs['tableRef' + index];
|
const tableRef = proxy.$refs['tableRef' + index];
|
||||||
if (tableRef && tableRef[0]) {
|
if (tableRef && tableRef[0]) {
|
||||||
@@ -670,12 +678,15 @@ function handelSwicthChange(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
skipSelectCascade.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认选中全部行
|
// 默认选中全部行
|
||||||
function defaultSelectAllRows() {
|
function defaultSelectAllRows() {
|
||||||
// 清空并重建选中集合
|
// 清空并重建选中集合
|
||||||
selectedRowIds.value.clear();
|
selectedRowIds.value.clear();
|
||||||
|
// 阻止 toggleRowSelection 触发 handleRowSelect 中的 selectAllCheckboxesInRow 级联
|
||||||
|
skipSelectCascade.value = true;
|
||||||
prescriptionList.value.forEach((item, index) => {
|
prescriptionList.value.forEach((item, index) => {
|
||||||
const tableRef = proxy.$refs['tableRef' + index];
|
const tableRef = proxy.$refs['tableRef' + index];
|
||||||
if (tableRef && tableRef[0]) {
|
if (tableRef && tableRef[0]) {
|
||||||
@@ -688,6 +699,7 @@ function defaultSelectAllRows() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
skipSelectCascade.value = false;
|
||||||
// 更新全选开关状态
|
// 更新全选开关状态
|
||||||
chooseAll.value = true;
|
chooseAll.value = true;
|
||||||
}
|
}
|
||||||
@@ -764,6 +776,7 @@ function checkAndToggleRowSelection(row) {
|
|||||||
const isCurrentlySelected = selectedRowIds.value.has(row.requestId);
|
const isCurrentlySelected = selectedRowIds.value.has(row.requestId);
|
||||||
|
|
||||||
// 根据checkbox状态更新表格行选中状态
|
// 根据checkbox状态更新表格行选中状态
|
||||||
|
skipSelectCascade.value = true;
|
||||||
if (isAllSelected && !isCurrentlySelected) {
|
if (isAllSelected && !isCurrentlySelected) {
|
||||||
selectedRowIds.value.add(row.requestId);
|
selectedRowIds.value.add(row.requestId);
|
||||||
tableRef[0].toggleRowSelection(row, true);
|
tableRef[0].toggleRowSelection(row, true);
|
||||||
@@ -771,6 +784,7 @@ function checkAndToggleRowSelection(row) {
|
|||||||
selectedRowIds.value.delete(row.requestId);
|
selectedRowIds.value.delete(row.requestId);
|
||||||
tableRef[0].toggleRowSelection(row, false);
|
tableRef[0].toggleRowSelection(row, false);
|
||||||
}
|
}
|
||||||
|
skipSelectCascade.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -782,8 +796,10 @@ function handleRowSelect(selection, row, tableIndex) {
|
|||||||
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
selectedRowIds.value.add(row.requestId);
|
selectedRowIds.value.add(row.requestId);
|
||||||
// 选中行时,选中该行内部的所有checkbox
|
// 仅在非程序化选中时,联动选中该行内部的所有checkbox
|
||||||
selectAllCheckboxesInRow(row);
|
if (!skipSelectCascade.value) {
|
||||||
|
selectAllCheckboxesInRow(row);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedRowIds.value.delete(row.requestId);
|
selectedRowIds.value.delete(row.requestId);
|
||||||
// 取消选中行时,取消选中该行内部的所有checkbox
|
// 取消选中行时,取消选中该行内部的所有checkbox
|
||||||
|
|||||||
Reference in New Issue
Block a user