Compare commits

...

4 Commits

Author SHA1 Message Date
关羽
0efde56f70 Fix Bug #481: [住院护士站-医嘱执行] 药品"注射用头孢哌酮钠舒巴坦钠"库存充足,但执行医嘱时提示库存不足
在 checkExeMedInventory 方法中,原代码使用 findFirst() 只取第一个批次的库存
进行校验,导致同一库房多个批次的库存总量未被聚合计算。改为 collect(Collectors.toList())
收集所有匹配批次,然后用 Stream reduce 聚合总可用库存后再与需求量比较。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 00:22:33 +08:00
赵云
cae70bd742 Fix Bug #478: 【住院医生工作站-检验申请】点击"详情"查看检验单时,"发往科室"字段回显异常(显示为"-")
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 00:22:33 +08:00
赵云
d2a89b9f31 Fix Bug #495: 【医嘱闭环】已校对医嘱无法流转至"医嘱执行"界面,导致费用无法提交执行
医嘱执行模块 prescriptionList.vue 中 try-catch 被注释掉,导致数据处理
异常时静默失败且 loading 状态无法重置,页面显示空数据无报错。
- 恢复 try-catch 错误处理,捕获 res.data.records 空值及数据处理异常
- 添加 .catch() 处理 API 接口级别失败,重置 loading 并清空列表
- 修复无患者时 loading 状态未重置的问题

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 00:22:33 +08:00
赵云
04ac6b0ec2 Fix Bug #411: 智能分诊排队:底部操作控制区"过滤栏"功能实现与PRD需求不符(误设为科室过滤)
将底部过滤栏从"就诊科室快速过滤栏"改为"诊室快速过滤栏":
- UI文案:过滤栏标题、下拉框placeholder均改为诊室相关
- 数据源:移除 getLocationTree() 科室树API调用,改为从队列/候选池数据中动态提取诊室列表
- 过滤逻辑:改为按诊室名称(room字段)过滤,支持本科室下不同诊室快速切换
- 后端API调用不再依赖过滤栏选择,改用队列数据自身的organizationId

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 00:22:33 +08:00
4 changed files with 84 additions and 95 deletions

View File

@@ -178,22 +178,26 @@ public class AdviceUtils {
// 生命提示信息集合
List<String> tipsList = new ArrayList<>();
for (MedicationRequestUseExe medicationRequestUseExe : medUseExeList) {
Optional<AdviceInventoryDto> matchedInventory = adviceInventory.stream()
// 聚合同一位置所有批次的库存总量
List<AdviceInventoryDto> matchedInventories = adviceInventory.stream()
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
&& CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable())
&& medicationRequestUseExe.getPerformLocation().equals(inventoryDto.getLocationId())
// 如果选择了具体的批次号,校验库存时需要加上批次号的匹配条件
&& (StringUtils.isEmpty(medicationRequestUseExe.getLotNumber())
|| medicationRequestUseExe.getLotNumber().equals(inventoryDto.getLotNumber())))
.findFirst();
.collect(Collectors.toList());
// 匹配到库存信息
if (matchedInventory.isPresent()) {
AdviceInventoryDto inventoryDto = matchedInventory.get();
if ((medicationRequestUseExe.getExecuteTimesNum()
.multiply(medicationRequestUseExe.getMinUnitQuantity()))
.compareTo(inventoryDto.getQuantity()) > 0) {
if (!matchedInventories.isEmpty()) {
// 聚合所有批次的可用库存
BigDecimal totalQuantity = matchedInventories.stream()
.map(dto -> dto.getQuantity() != null ? dto.getQuantity() : BigDecimal.ZERO)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal requestQuantity = medicationRequestUseExe.getExecuteTimesNum()
.multiply(medicationRequestUseExe.getMinUnitQuantity());
if (requestQuantity.compareTo(totalQuantity) > 0) {
tipsList
.add("" + medicationRequestUseExe.getBusNo() + "】在" + inventoryDto.getLocationName() + "库存不足");
.add("" + medicationRequestUseExe.getBusNo() + "】在" + matchedInventories.get(0).getLocationName() + "库存不足");
}
} else {
tipsList.add("" + medicationRequestUseExe.getBusNo() + "】未匹配到库存信息");

View File

@@ -173,7 +173,7 @@ import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js';
import {getInspection} from './api';
import {getOrgList} from '@/views/doctorstation/components/api.js';
import {getDepartmentList} from '@/api/public.js';
const { proxy } = getCurrentInstance();
@@ -332,8 +332,8 @@ const hasMatchedFields = computed(() => {
/** 查询科室 */
const getLocationInfo = () => {
getOrgList().then((res) => {
orgOptions.value = res.data.records;
getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
});
};
@@ -345,10 +345,12 @@ const recursionFun = (targetDepartment) => {
name = obj.name;
}
const subObjArray = obj['children'];
for (let index = 0; index < subObjArray.length; index++) {
const item = subObjArray[index];
if (item.id == targetDepartment) {
name = item.name;
if (subObjArray && subObjArray.length > 0) {
for (let i = 0; i < subObjArray.length; i++) {
const item = subObjArray[i];
if (item.id == targetDepartment) {
name = item.name;
}
}
}
}

View File

@@ -283,9 +283,10 @@ function handleGetPrescription() {
exeStatus: props.exeStatus,
requestStatus: props.requestStatus,
}).then((res) => {
// try {
try {
const records = res?.data?.records || [];
// 根据encounterId分组
const groupedPrescriptions = res.data.records.reduce((groups, prescription) => {
const groupedPrescriptions = records.reduce((groups, prescription) => {
// 获取当前医嘱全部执行频次
let rate;
let times;
@@ -430,19 +431,26 @@ function handleGetPrescription() {
// 将分组结果转换为数组形式
prescriptionList.value = Object.values(groupedPrescriptions);
loading.value = false;
// 默认选中全部行
nextTick(() => {
defaultSelectAllRows();
});
// } catch {
// loading.value = false;
// }
} catch (error) {
console.error('医嘱执行-获取处方列表数据处理失败:', error);
prescriptionList.value = [];
} finally {
loading.value = false;
}
}).catch((error) => {
console.error('医嘱执行-获取处方列表接口失败:', error);
prescriptionList.value = [];
loading.value = false;
});
chooseAll.value = false;
} else {
prescriptionList.value = [];
selectedRowIds.value.clear();
loading.value = false;
// proxy.$message.warning('请选择患者');
}
}

View File

@@ -220,15 +220,15 @@
<!-- 底部控制面板 -->
<div class="footer-section">
<!-- 就诊科室快速过滤栏 -->
<!-- 室快速过滤栏 -->
<div class="filter-section">
<div class="filter-label">
就诊科室快速过滤栏
室快速过滤栏
</div>
<div class="filter-select-wrapper">
<el-select
v-model="selectedDept"
placeholder="请选择就诊科室"
v-model="selectedClinicRoom"
placeholder="请选择室"
clearable
filterable
style="width: 100%"
@@ -239,10 +239,10 @@
value="all"
/>
<el-option
v-for="dept in departmentList"
:key="dept.id"
:label="dept.name"
:value="dept.id"
v-for="room in clinicRoomList"
:key="room"
:label="room"
:value="room"
/>
</el-select>
</div>
@@ -648,7 +648,6 @@ import { Refresh } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import {
getCandidatePool,
getLocationTree,
getTriageQueueList,
addToQueue,
removeFromQueue,
@@ -681,10 +680,10 @@ const selectedCandidates = ref([])
// 显示选项
const showOnlyWaiting = ref(false)
// 室过滤(改为使用就诊科室
const selectedDept = ref('all')
// 就诊科室列表
const departmentList = ref([])
// 室过滤(按诊室维度筛选
const selectedClinicRoom = ref('all')
// 诊室列表(从数据中动态提取)
const clinicRoomList = ref([])
// 修复【#397】动态获取当前科室名称
const currentDeptName = computed(() => {
@@ -907,13 +906,11 @@ const mapFrontendStatusToBackend = (status) => {
// 从数据库加载队列
const loadQueueFromDb = async () => {
try {
// 如果选择了具体科室,就按科室加载;否则加载当前登录人科室(后端默认)
const organizationId = selectedDept.value !== 'all' ? selectedDept.value : undefined
// 只查询今天的患者
// 使用当前登录人科室
const today = new Date()
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
console.log('【心内科】loadQueueFromDb 开始:organizationId=', organizationId, 'date=', todayStr, 'selectedDept=', selectedDept.value)
const res = await getTriageQueueList({ organizationId, date: todayStr }).catch((err) => {
console.log('【心内科】loadQueueFromDb 开始:date=', todayStr)
const res = await getTriageQueueList({ date: todayStr }).catch((err) => {
console.error('【心内科】loadQueueFromDb 请求异常:', err)
return { code: 500, msg: err?.message || '请求失败', data: null }
})
@@ -1141,6 +1138,8 @@ const loadDataFromApi = async () => {
// 同步当前呼叫(队列从 DB 加载后已同步;这里再兜底一次)
syncCurrentCallFromQueue()
// 提取诊室列表供过滤栏使用
extractClinicRooms()
console.log('【心内科】数据加载完成:候选池', originalCandidatePoolList.value.length, '条,队列', originalQueueList.value.length, '条')
ElMessage.success('【心内科】已从门诊挂号接口加载数据')
} catch (e) {
@@ -1152,56 +1151,37 @@ const loadDataFromApi = async () => {
totalSignedIn.value = originalCandidatePoolList.value.length
totalInQueue.value = originalQueueList.value.length
syncCurrentCallFromQueue()
extractClinicRooms()
}
}
// 原始数据存储(用于过滤)
const originalCandidatePoolList = ref(getInitialCandidatePoolList())
// 辅助函数:扁平化科室树形结构
const flattenDepartmentTree = (tree, result = []) => {
if (!Array.isArray(tree)) return result
tree.forEach(node => {
if (node.id && node.name) {
result.push({ id: node.id, name: node.name })
}
if (node.children && Array.isArray(node.children)) {
flattenDepartmentTree(node.children, result)
// 提取诊室列表(从队列和候选池数据中动态获取)
const extractClinicRooms = () => {
const roomSet = new Set()
// 从队列中提取
originalQueueList.value.forEach(item => {
if (item.room && item.room !== '-') {
roomSet.add(item.room)
}
})
return result
}
// 加载就诊科室列表
const loadDepartmentList = async () => {
try {
const response = await getLocationTree()
if (response && response.data) {
// 扁平化树形结构
departmentList.value = flattenDepartmentTree(response.data)
console.log('【心内科】已加载就诊科室列表:', departmentList.value.length, '个科室')
// 从候选池中提取
originalCandidatePoolList.value.forEach(item => {
if (item.room && item.room !== '-') {
roomSet.add(item.room)
}
} catch (error) {
console.error('【心内科】加载就诊科室列表失败:', error)
ElMessage.warning('加载就诊科室列表失败,使用默认数据')
}
}
// 获取选中科室的名称
const getSelectedDeptName = () => {
if (selectedDept.value === 'all') return null
const dept = departmentList.value.find(d => d.id === selectedDept.value)
return dept ? dept.name : null
})
clinicRoomList.value = Array.from(roomSet).sort()
}
// 过滤后的智能候选池数据
const filteredCandidatePoolList = computed(() => {
if (selectedDept.value === 'all') {
if (selectedClinicRoom.value === 'all') {
return originalCandidatePoolList.value
}
const deptName = getSelectedDeptName()
if (!deptName) return originalCandidatePoolList.value
return originalCandidatePoolList.value.filter(item => item.room === deptName)
return originalCandidatePoolList.value.filter(item => item.room === selectedClinicRoom.value)
})
// 原始队列数据存储(用于过滤)
@@ -1223,19 +1203,16 @@ const formatSecondsToMmSs = (totalSeconds) => {
return `${mm}:${ss}`
}
// 过滤后的智能队列数据(同时考虑室过滤和状态过滤)
// 过滤后的智能队列数据(同时考虑室过滤和状态过滤)
const filteredQueueList = computed(() => {
let filtered = originalQueueList.value
// 先过滤掉"已完成"状态的患者(无论什么情况都不显示)
filtered = filtered.filter(item => item.status !== '已完成')
// 再按室过滤
if (selectedDept.value !== 'all') {
const deptName = getSelectedDeptName()
if (deptName) {
filtered = filtered.filter(item => item.room === deptName)
}
// 再按室过滤
if (selectedClinicRoom.value !== 'all') {
filtered = filtered.filter(item => item.room === selectedClinicRoom.value)
}
// 再按状态过滤(只显示等待)
@@ -1747,9 +1724,9 @@ const handleNextPatient = async () => {
reqData.organizationId = selectedQueueRow.value.organizationId
} else {
// 如果没有选中患者,使用查询条件(兼容旧逻辑)
let orgId = selectedDept.value !== 'all' ? selectedDept.value : undefined
// "全科"模式:优先用"当前叫号中/第一个等待"所在科室
if (orgId == null) {
// 全科模式:优先用"当前叫号中/第一个等待"所在科室
let orgId = null
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
const waiting = originalQueueList.value.find((i) => i.status === '等待')
console.log('【心内科】handleNextPatient 查找:叫号中=', calling?.patientName, '等待=', waiting?.patientName)
@@ -1785,9 +1762,9 @@ const handleSkip = async () => {
reqData.organizationId = selectedQueueRow.value.organizationId
} else {
// 如果没有选中患者,使用查询条件(兼容旧逻辑)
let orgId = selectedDept.value !== 'all' ? selectedDept.value : undefined
// “全科”模式:优先用“当前叫号中”所在科室
if (orgId == null) {
// 全科模式:优先用”当前叫号中”所在科室
let orgId = null
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
orgId = calling?.organizationId
}
@@ -1819,9 +1796,9 @@ const handleComplete = async () => {
reqData.organizationId = selectedQueueRow.value.organizationId
} else {
// 如果没有选中患者,使用查询条件(兼容旧逻辑)
let orgId = selectedDept.value !== 'all' ? selectedDept.value : undefined
// “全科”模式:优先用“当前叫号中”所在科室
if (orgId == null) {
// 全科模式:优先用”当前叫号中”所在科室
let orgId = null
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
orgId = calling?.organizationId
}
@@ -1853,9 +1830,9 @@ const handleRequeue = async () => {
reqData.organizationId = selectedQueueRow.value.organizationId
} else {
// 如果没有选中患者,使用查询条件(兼容旧逻辑)
let orgId = selectedDept.value !== 'all' ? selectedDept.value : undefined
// “全科”模式:优先用“当前叫号中”所在科室
if (orgId == null) {
// 全科模式:优先用”当前叫号中”所在科室
let orgId = null
{
const calling = originalQueueList.value.find((i) => i.status === '叫号中')
orgId = calling?.organizationId
}
@@ -2121,8 +2098,6 @@ const handleTestRule = () => {
// 组件挂载
onMounted(() => {
// 加载就诊科室列表
loadDepartmentList()
// 初始化:优先从后端加载,失败则回退本地假数据
loadDataFromApi()
startWaitingTimer()