Compare commits

...

9 Commits

Author SHA1 Message Date
关羽
2bb9b0a07f Fix Bug #475: 【住院医生工作站】开立检查申请单报错后仍生成申请记录
根因:saveRequestForm方法的预校验循环和主循环分别独立查询activityOrganizationConfig获取positionId,
存在数据不一致风险——预校验通过但主循环中positionId查找失败时,RequestForm已被保存导致脏数据。

修复:将预校验循环中查到的positionId缓存到Map中,主循环直接使用缓存结果,
避免重复查询导致的数据不一致问题。确保所有校验通过后再执行任何数据库操作。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 11:14:52 +08:00
赵云
a9bd2516e3 Fix Bug #445: 【手术管理-门诊手术安排】临时医嘱生成界面逻辑错误:已生成医嘱的计费项目未从"待生成"列表中剔除
根因:提交成功后,父组件使用 requestId/chargeItemId 匹配已提交项目来过滤
待生成列表,但这些字段在新建医嘱时往往为空,导致匹配失败,已生成的项目
仍保留在"待生成"列表中。

修复:
1. handleTemporaryMedicalSubmit: 改用稳定的字段组合(药品名称+规格+数量)
   匹配已提交项目,从 temporaryBillingMedicines 中移除
2. handleMedicalAdvice: 首次打开弹窗时过滤掉已有 requestId 的项目
3. handleQuoteBilling: 引用计费/刷新时同样过滤掉已有 requestId 的项目

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 11:14:52 +08:00
赵云
4c3adfecaa Fix Bug #435: 门诊手术安排:编辑弹窗中"费用类别"字段数据未回显
根因:OpCreateScheduleDto 缺少 feeType 字段,导致新建手术安排时 BeanUtils.copyProperties 无法复制该字段,
保存到数据库后 fee_type 为空字符串/null,编辑时详情查询返回 null 导致前端不显示。

修复:在 OpCreateScheduleDto 新增 feeType 字段,使创建流程完整传递费用类别数据。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 11:14:52 +08:00
荀彧
34485b45ea Fix Bug #428: 门诊医生站-检查申请:未实现分类联动检查方法及套餐明细展示与勾选逻辑
- 分类对象初始化时增加 methods: [],确保 Vue 响应式追踪分类下检查方法的加载
- handleMethodSelect 创建新项目时复制 cat.methods 全部方法数组(原只放单个方法),允许用户在右侧面板切换其他方法
- handleMethodSelect 新增/更新项目时同步 packageName 字段,确保 toggleItemExpand 能通过名称查找并加载套餐明细

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 11:14:52 +08:00
关羽
83c4cce92c Fix Bug #441: 门诊手术安排:手术室护士角色进入页面提示"无权限"且"获取卫生机构列表失败"
根因:响应拦截器中 skipErrorMsg: true 仅抑制了弹窗提示,但仍返回 Promise.reject,
导致 .catch() 路径仍可能触发错误消息或异常行为。
修复:当 skipErrorMsg 为 true 且返回业务错误码(403/500/601等)时,改为 Promise.resolve(res.data),
让 .then() 分支通过 res.code !== 200 判断实现静默降级,不触发 .catch()。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 11:14:52 +08:00
关羽
2e00caf336 Fix Bug #455: 门诊医生站-医嘱:开立诊疗医嘱时执行科室默认获取逻辑有误且显示为原始ID
根因:setValue() 中通过展开运算符(...JSON.parse(JSON.stringify(row)))将诊疗目录
的 positionId/orgId 带入处方列表,后续条件判断只处理非诊疗类型(advicetype != 3),
导致诊疗类的 catalog ID 未被覆盖,且该 ID 不在机构树中,el-tree-select 显示原始数字。

修复:
1. setValue() 中显式为诊疗类(adviceType=3)设置 orgId/positionId 为患者就诊科室,
   并同步 positionName 为机构树中的名称
2. handleSaveGroup() 组套应用时同样对诊疗类使用患者就诊科室,不使用目录配置的ID

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 10:08:00 +08:00
赵云
31fec4fbe6 Fix Bug #426: 门诊医生站-检查开立:已选择列表应支持树形展开,显示套餐明细(项目/数量/单价)
- 已选择面板的套餐项增加"套餐"标签,便于用户识别
- 展开/收起图标改为 ArrowRight 旋转样式,符合标准交互习惯
- toggleItemExpand 函数增加 packageName 兜底判断,不强制依赖 isPackage 标记
- loadPackageDetailsForItem 添加 loading 状态和更健壮的 packageId 解析逻辑
- 新增 expanded-content 和 package-loading-hint CSS 样式

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 10:08:00 +08:00
荀彧
cd038fc793 feat: 手术管理列表点击行高亮 (highlight-current-row) 2026-05-14 10:08:00 +08:00
关羽
e770741c8b Fix Bug #452: 领用出库模块选择药品时提示"仓库数量为0,无法调用",与实际库存数据不符
根因:药品目录列表中返回的lotNumber是任意仓库中的批号,但getCount查询时用该lotNumber过滤用户选择的仓库库存。若该批号在目标仓库不存在(但其他批号存在),则返回0条记录导致误报"仓库数量为0"。
修复:在领用出库的handleLocationClick中移除getCount的lotNumber参数,改为查询该药品在所选仓库的所有批号库存。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 09:24:36 +08:00
8 changed files with 115 additions and 46 deletions

View File

@@ -261,6 +261,11 @@ public class OpCreateScheduleDto {
*/ */
private String remark; private String remark;
/**
* 费用类别
*/
private String feeType;
/** /**
* 创建时间 * 创建时间
*/ */

View File

@@ -87,8 +87,11 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
throw new ServiceException("请先配置当前时间段的执行科室"); throw new ServiceException("请先配置当前时间段的执行科室");
} }
// 逐个校验activityList中的项目是否都配置了执行科室避免部分通过后在循环中抛异常导致事务复杂化 // 逐个校验activityList中的项目是否都配置了执行科室并收集positionId供后续使用
// 必须在任何数据库操作之前完成全部校验,避免部分保存后异常导致脏数据
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList(); List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
// 缓存校验结果,避免主循环中重复查询和可能出现的数据不一致
java.util.Map<Long, Long> activityIdToPositionIdMap = new java.util.HashMap<>();
if (activityList != null && !activityList.isEmpty()) { if (activityList != null && !activityList.isEmpty()) {
for (ActivitySaveDto activitySaveDto : activityList) { for (ActivitySaveDto activitySaveDto : activityList) {
Long positionId = activityOrganizationConfig.stream() Long positionId = activityOrganizationConfig.stream()
@@ -97,6 +100,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
if (positionId == null) { if (positionId == null) {
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室"); throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
} }
activityIdToPositionIdMap.put(activitySaveDto.getAdviceDefinitionId(), positionId);
} }
} }
@@ -179,9 +183,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
serviceRequest.setEncounterId(encounterId); // 就诊id serviceRequest.setEncounterId(encounterId); // 就诊id
serviceRequest.setAuthoredTime(curDate); // 请求签发时间 serviceRequest.setAuthoredTime(curDate); // 请求签发时间
Long positionId = activityOrganizationConfig.stream() Long positionId = activityIdToPositionIdMap.get(activitySaveDto.getAdviceDefinitionId());
.filter(dto -> activitySaveDto.getAdviceDefinitionId().equals(dto.getActivityDefinitionId()))
.map(ActivityOrganizationConfigDto::getOrganizationId).findFirst().orElse(null);
if (positionId == null) { if (positionId == null) {
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室"); throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
} }

View File

@@ -178,22 +178,25 @@ service.interceptors.request.use(config => {
} }
return Promise.reject('无效的会话,或者会话已过期,请重新登录。') return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) { } else if (code === 500) {
// 检查是否需要跳过错误提示 // 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
if (!res.config?.skipErrorMsg) { if (res.config?.skipErrorMsg) {
ElMessage({ message: msg, type: 'error' }) return Promise.resolve(res.data)
} }
ElMessage({ message: msg, type: 'error' })
return Promise.reject(new Error(msg)) return Promise.reject(new Error(msg))
} else if (code === 601) { } else if (code === 601) {
// 检查是否需要跳过错误提示 // 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
if (!res.config?.skipErrorMsg) { if (res.config?.skipErrorMsg) {
ElMessage({ message: msg, type: 'warning' }) return Promise.resolve(res.data)
} }
ElMessage({ message: msg, type: 'warning' })
return Promise.reject(new Error(msg)) return Promise.reject(new Error(msg))
} else if (code !== 200) { } else if (code !== 200) {
// 检查是否需要跳过错误提示 // 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
if (!res.config?.skipErrorMsg) { if (res.config?.skipErrorMsg) {
ElNotification.error({ title: msg }) return Promise.resolve(res.data)
} }
ElNotification.error({ title: msg })
return Promise.reject('error') return Promise.reject('error')
} else { } else {
return Promise.resolve(res.data) return Promise.resolve(res.data)

View File

@@ -413,29 +413,33 @@
:key="idx" :key="idx"
class="selected-item-card" class="selected-item-card"
> >
<!-- Bug #384修复: 项目卡片头部可展开/收起 --> <!-- Bug #384修复 + #426修复: 项目卡片头部可展开/收起 -->
<div class="card-header" @click="toggleItemExpand(item)"> <div class="card-header" @click="toggleItemExpand(item)">
<el-tag v-if="item.isPackage || item.packageName" size="small" type="warning" style="margin-right: 4px; flex-shrink: 0;">套餐</el-tag>
<span class="card-name">{{ item.name }}</span> <span class="card-name">{{ item.name }}</span>
<span class="card-price">¥{{ item.price }}</span> <span class="card-price">¥{{ item.price }}</span>
<!-- 展开图标 --> <!-- 展开/收起图标 -->
<el-icon :class="['expand-icon', { expanded: item.expanded }]"> <el-icon class="expand-icon" :class="{ expanded: item.expanded }">
<ArrowDown v-if="!item.expanded" /> <ArrowRight />
<ArrowUp v-if="item.expanded" />
</el-icon> </el-icon>
<!-- 删除按钮 --> <!-- 删除按钮 -->
<el-button link type="danger" size="small" @click.stop="handleRemoveItem(idx, item)"> <el-button link type="danger" size="small" @click.stop="handleRemoveItem(idx, item)">
<el-icon><Close /></el-icon> <el-icon><Close /></el-icon>
</el-button> </el-button>
</div> </div>
<!-- Bug #428修复: 展开后显示套餐明细或检查方法 --> <!-- Bug #428修复 + #426修复: 展开后显示套餐明细或检查方法 -->
<div v-if="item.expanded"> <div v-show="item.expanded" class="expanded-content">
<!-- 显示套餐明细 --> <!-- 显示套餐明细 -->
<div v-if="item.packageDetails && item.packageDetails.length > 0" class="package-details-list"> <div v-if="(item.isPackage || item.packageName) && item.packageDetails && item.packageDetails.length > 0" class="package-details-list">
<div class="detail-row" v-for="detail in item.packageDetails" :key="detail.id"> <div class="detail-row" v-for="detail in item.packageDetails" :key="detail.id">
<span class="detail-name">{{ detail.name }}</span> <span class="detail-name">{{ detail.name }}</span>
<span class="detail-info">数量: {{ detail.quantity }} 单价: ¥{{ detail.price }}</span> <span class="detail-info">数量: {{ detail.quantity }} 单价: ¥{{ detail.price }}</span>
</div> </div>
</div> </div>
<!-- 套餐明细加载中 -->
<div v-else-if="(item.isPackage || item.packageName) && item.packageDetailsLoading" class="package-loading-hint">
加载中...
</div>
<!-- 显示检查方法 --> <!-- 显示检查方法 -->
<div v-else-if="item.methods && item.methods.length > 0" class="method-list"> <div v-else-if="item.methods && item.methods.length > 0" class="method-list">
<div v-for="method in item.methods" :key="method.id" class="method-option"> <div v-for="method in item.methods" :key="method.id" class="method-option">
@@ -473,7 +477,7 @@
<script setup> <script setup>
import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue'; import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { Printer, Delete, ArrowDown, ArrowUp, Close } from '@element-plus/icons-vue'; import { Printer, Delete, ArrowDown, ArrowUp, Close, ArrowRight } from '@element-plus/icons-vue';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import request from '@/utils/request'; import request from '@/utils/request';
import { listCheckMethod, searchCheckMethod, listCheckPackage } from '@/api/system/checkType'; import { listCheckMethod, searchCheckMethod, listCheckPackage } from '@/api/system/checkType';
@@ -592,11 +596,13 @@ async function loadPackageDetails(row, treeNode, resolve) {
} }
} }
// #428修复: 为已选择项目加载套餐明细通过packageId或packageName查询 // #428修复 + #426修复: 为已选择项目加载套餐明细通过packageId或packageName查询
async function loadPackageDetailsForItem(item) { async function loadPackageDetailsForItem(item) {
if (!item.isPackage || (!item.packageId && !item.packageName)) { // 只要有 packageName 就认为是套餐,不强制要求 isPackage 或 packageId
if (!item.packageName && !item.packageId) {
return; return;
} }
item.packageDetailsLoading = true;
try { try {
let packageId = item.packageId; let packageId = item.packageId;
if (!packageId && item.packageName) { if (!packageId && item.packageName) {
@@ -612,6 +618,10 @@ async function loadPackageDetailsForItem(item) {
} }
packageId = packages[0].id; packageId = packages[0].id;
} }
if (!packageId) {
item.packageDetails = [];
return;
}
const res = await request({ const res = await request({
url: `/system/package/${packageId}/details`, url: `/system/package/${packageId}/details`,
method: 'get' method: 'get'
@@ -630,6 +640,8 @@ async function loadPackageDetailsForItem(item) {
} catch (err) { } catch (err) {
console.error('加载套餐明细失败:', err); console.error('加载套餐明细失败:', err);
item.packageDetails = []; item.packageDetails = [];
} finally {
item.packageDetailsLoading = false;
} }
} }
const detailTableRef = ref(null); const detailTableRef = ref(null);
@@ -912,7 +924,8 @@ async function loadCategoryList() {
categoryName: t.name, categoryName: t.name,
// “检查类型管理”里配置的执行科室(图三) // “检查类型管理”里配置的执行科室(图三)
performDeptName: t.department || '', performDeptName: t.department || '',
items: [] items: [],
methods: [] // #428修复: 初始化 methods 数组,确保 Vue 响应式追踪
}); });
} }
const unclassified = []; const unclassified = [];
@@ -1255,6 +1268,7 @@ async function handleMethodSelect(checked, method, cat) {
if (method.packageId) { if (method.packageId) {
existingItem.isPackage = true; existingItem.isPackage = true;
existingItem.packageId = method.packageId; existingItem.packageId = method.packageId;
existingItem.packageName = method.packageName || existingItem.packageName; // #428修复: 确保 packageName 同步
// 预加载套餐明细 // 预加载套餐明细
loadPackageDetailsForItem(existingItem); loadPackageDetailsForItem(existingItem);
} }
@@ -1282,12 +1296,13 @@ async function handleMethodSelect(checked, method, cat) {
checkType: cat.typeName, checkType: cat.typeName,
nationalCode: targetItem.nationalCode || '', nationalCode: targetItem.nationalCode || '',
checked: true, checked: true,
methods: [method], methods: cat.methods || [method], // #428修复: 复制分类下全部方法,允许用户切换
selectedMethod: method, selectedMethod: method,
expanded: false, expanded: false,
// 从方法中获取套餐信息(优先级高于项目本身的 packageName // 从方法或项目中获取套餐信息
isPackage: !!method.packageId || !!targetItem.packageName, isPackage: !!method.packageId || !!targetItem.packageName,
packageId: method.packageId || targetItem.packageId || null packageId: method.packageId || targetItem.packageId || null,
packageName: method.packageName || targetItem.packageName || null // #428修复: 复制 packageName确保套餐明细可加载
}; };
selectedItems.value.push(newItem); selectedItems.value.push(newItem);
@@ -1396,11 +1411,11 @@ async function handleItemSelect(checked, item, cat) {
// Bug #382 修复:移除自动切换页签逻辑,保持当前页签状态 // Bug #382 修复:移除自动切换页签逻辑,保持当前页签状态
} }
// Bug #384修复: 展开/收起项目卡片 // Bug #384修复 + #426修复: 展开/收起项目卡片
async function toggleItemExpand(item) { async function toggleItemExpand(item) {
item.expanded = !item.expanded; item.expanded = !item.expanded;
// 如果是展开且该项目是套餐,加载套餐明细 // 如果是展开且该项目是套餐(通过 isPackage 或 packageName 判断),加载套餐明细
if (item.expanded && item.isPackage && (!item.packageDetails || item.packageDetails.length === 0)) { if (item.expanded && (item.isPackage || item.packageName) && (!item.packageDetails || item.packageDetails.length === 0) && !item.packageDetailsLoading) {
await loadPackageDetailsForItem(item); await loadPackageDetailsForItem(item);
} }
} }
@@ -1812,10 +1827,24 @@ defineExpose({ getList });
font-size: 12px; font-size: 12px;
color: #909399; color: #909399;
transition: transform 0.2s; transition: transform 0.2s;
transform: rotate(0deg);
} }
.expand-icon.expanded { .expand-icon.expanded {
transform: rotate(180deg); transform: rotate(90deg);
}
/* Bug #426修复: 展开内容容器 */
.expanded-content {
overflow: hidden;
}
/* Bug #426修复: 套餐明细加载提示 */
.package-loading-hint {
padding: 8px 10px;
font-size: 11px;
color: #c0c4cc;
text-align: center;
} }
/* Bug #428修复: 套餐明细列表样式 */ /* Bug #428修复: 套餐明细列表样式 */

View File

@@ -3418,7 +3418,12 @@ async function setValue(row) {
console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText); console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText);
// 🔧 Bug #455: 诊疗医嘱(adviceType=3)的执行科室默认使用患者就诊科室, // 🔧 Bug #455: 诊疗医嘱(adviceType=3)的执行科室默认使用患者就诊科室,
// 不使用positionId(诊疗目录配置的执行科室)避免配置ID不在机构树中导致显示原始ID // 不使用positionId(诊疗目录配置的执行科室)避免配置ID不在机构树中导致显示原始ID
if (Number(row.adviceType) != 3) { if (Number(row.adviceType) == 3) {
// 覆盖 catalog 传来的 positionId/orgId使用患者就诊科室
prescriptionList.value[rowIndex.value].orgId = props.patientInfo?.orgId;
prescriptionList.value[rowIndex.value].positionId = props.patientInfo?.orgId;
prescriptionList.value[rowIndex.value].positionName = findOrgNameById(props.patientInfo?.orgId) || props.patientInfo?.orgName || '';
} else {
prescriptionList.value[rowIndex.value].orgId = row.positionId || props.patientInfo?.orgId; prescriptionList.value[rowIndex.value].orgId = row.positionId || props.patientInfo?.orgId;
} }
prescriptionList.value[rowIndex.value].dose = row.dose || row.doseQuantity; prescriptionList.value[rowIndex.value].dose = row.dose || row.doseQuantity;
@@ -3652,7 +3657,10 @@ function handleSaveGroup(orderGroupList) {
unitCode_dictText: item.unitCodeName || '', unitCode_dictText: item.unitCodeName || '',
statusEnum: 1, statusEnum: 1,
// 🔧 修复执行科室逻辑:优先使用 orgId(所属科室),其次 positionId // 🔧 修复执行科室逻辑:优先使用 orgId(所属科室),其次 positionId
orgId: item.orderDetailInfos?.orgId || mergedDetail.orgId || item.positionId || item.orderDetailInfos?.positionId || mergedDetail.positionId, // 🔧 Bug #455: 诊疗类(adviceType=3)使用患者就诊科室不使用目录配置的ID
orgId: item.adviceType === 3
? props.patientInfo?.orgId
: (item.orderDetailInfos?.orgId || mergedDetail.orgId || item.positionId || item.orderDetailInfos?.positionId || mergedDetail.positionId),
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1', dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1',
conditionId: conditionId.value, conditionId: conditionId.value,
conditionDefinitionId: conditionDefinitionId.value, conditionDefinitionId: conditionDefinitionId.value,

View File

@@ -1009,8 +1009,6 @@ function handleLocationClick(item, row, index) {
getCount({ getCount({
itemId: form.purchaseinventoryList[index].itemId, itemId: form.purchaseinventoryList[index].itemId,
orgLocationId: form.purchaseinventoryList[index].sourceLocationId, orgLocationId: form.purchaseinventoryList[index].sourceLocationId,
// objLocationId:purposeLocationId,
lotNumber: form.purchaseinventoryList[index].lotNumber,
}).then((res) => { }).then((res) => {
if (res.data && res.data.length > 0) { if (res.data && res.data.length > 0) {
form.purchaseinventoryList[index].itemTable = res.data[0].itemTable || ''; form.purchaseinventoryList[index].itemTable = res.data[0].itemTable || '';

View File

@@ -76,7 +76,7 @@
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
<el-table v-loading="loading" :data="surgeryList" row-key="id" :row-class-name="getRowClassName"> <el-table v-loading="loading" :data="surgeryList" row-key="id" :row-class-name="getRowClassName" highlight-current-row @current-change="handleCurrentChange">
<!-- 申请日期datetime - 2025-09-19 14:15:00 - 不可操作 --> <!-- 申请日期datetime - 2025-09-19 14:15:00 - 不可操作 -->
<el-table-column label="申请日期" align="center" prop="createTime" width="160"> <el-table-column label="申请日期" align="center" prop="createTime" width="160">
<template #default="scope"> <template #default="scope">
@@ -1405,6 +1405,12 @@ function getRowClassName({ row }) {
return '' return ''
} }
// 当前选中行(高亮)
const currentRow = ref(null)
function handleCurrentChange(row) {
currentRow.value = row
}
// 时间格式化函数 // 时间格式化函数
function parseTime(time, pattern) { function parseTime(time, pattern) {
if (!time) return '' if (!time) return ''

View File

@@ -1552,6 +1552,8 @@ function handleMedicalAdvice(row) {
// 过滤掉名称为空的项目 // 过滤掉名称为空的项目
const medicineName = item.adviceName || item.advice_name; const medicineName = item.adviceName || item.advice_name;
if (!medicineName || medicineName.trim() === '') return false; if (!medicineName || medicineName.trim() === '') return false;
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目(已有 requestId 的不应出现在"待生成"列表)
if (item.requestId) return false;
// 根据药品请求ID去重避免重复显示 // 根据药品请求ID去重避免重复显示
const itemId = item.requestId || item.id; const itemId = item.requestId || item.id;
if (itemId && seenIds.has(itemId)) return false; if (itemId && seenIds.has(itemId)) return false;
@@ -1739,15 +1741,27 @@ function handleTemporaryMedicalSubmit(data) {
} }
}) })
// 同步更新计费药品列表:移除已生成医嘱的项目,避免数据重复显示 // 🔧 修复 Bug #445: 使用稳定的字段组合匹配已提交项目,而不是依赖可能为空的 requestId/chargeItemId
const submittedIds = new Set( // 构建已提交项目的匹配键集合(药品名称 + 规格 + 数量)
(data.temporaryAdvices || []).map(a => a.originalMedicine?.requestId || a.originalMedicine?.chargeItemId).filter(Boolean) const submittedKeys = new Set(
(data.temporaryAdvices || [])
.map(a => {
const om = a.originalMedicine || {}
const name = om.medicineName || om.adviceName || om.advice_name || a.adviceName || ''
const spec = om.specification || om.volume || ''
const qty = om.quantity || 0
return `${name}|||${spec}|||${qty}`
})
.filter(k => k !== '|||0') // 过滤掉空项
) )
if (submittedIds.size > 0) {
temporaryBillingMedicines.value = (data.billingMedicines || []).filter( if (submittedKeys.size > 0) {
m => !submittedIds.has(m.requestId || m.chargeItemId) temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => {
) const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`
return !submittedKeys.has(key)
})
} else { } else {
// 如果没有任何匹配键,清空待生成列表(所有项目都已提交)
temporaryBillingMedicines.value = [] temporaryBillingMedicines.value = []
} }
@@ -1807,7 +1821,8 @@ function handleQuoteBilling() {
temporaryBillingMedicines.value = [] temporaryBillingMedicines.value = []
temporaryAdvices.value = [] temporaryAdvices.value = []
// 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3) // 🔧 修复 Bug #445: 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3)
// 同时过滤掉已有 requestId 的项目(已生成医嘱的不需要再次显示在"待生成"列表中)
const filteredItems = res.data.filter(item => { const filteredItems = res.data.filter(item => {
// 匹配 encounterId // 匹配 encounterId
if (item.encounterId !== temporaryPatientInfo.value.visitId) return false; if (item.encounterId !== temporaryPatientInfo.value.visitId) return false;
@@ -1815,7 +1830,10 @@ function handleQuoteBilling() {
if (item.adviceType !== 1) return false; if (item.adviceType !== 1) return false;
// 过滤掉名称为空的项目 // 过滤掉名称为空的项目
const medicineName = item.adviceName || item.advice_name; const medicineName = item.adviceName || item.advice_name;
return medicineName && medicineName.trim() !== ''; if (!medicineName || medicineName.trim() === '') return false;
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目(已有 requestId
if (item.requestId) return false;
return true;
}) })
// 🔧 修复限制返回数量最多显示前100条避免数据过多导致页面卡死 // 🔧 修复限制返回数量最多显示前100条避免数据过多导致页面卡死
const maxItems = 100 const maxItems = 100