Compare commits
9 Commits
zhaoyun
...
2bb9b0a07f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bb9b0a07f | ||
|
|
a9bd2516e3 | ||
|
|
4c3adfecaa | ||
|
|
34485b45ea | ||
|
|
83c4cce92c | ||
|
|
2e00caf336 | ||
|
|
31fec4fbe6 | ||
|
|
cd038fc793 | ||
|
|
e770741c8b |
@@ -261,6 +261,11 @@ public class OpCreateScheduleDto {
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 费用类别
|
||||
*/
|
||||
private String feeType;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
|
||||
@@ -87,8 +87,11 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
throw new ServiceException("请先配置当前时间段的执行科室");
|
||||
}
|
||||
|
||||
// 逐个校验activityList中的项目是否都配置了执行科室,避免部分通过后在循环中抛异常导致事务复杂化
|
||||
// 逐个校验activityList中的项目是否都配置了执行科室,并收集positionId供后续使用
|
||||
// 必须在任何数据库操作之前完成全部校验,避免部分保存后异常导致脏数据
|
||||
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
|
||||
// 缓存校验结果,避免主循环中重复查询和可能出现的数据不一致
|
||||
java.util.Map<Long, Long> activityIdToPositionIdMap = new java.util.HashMap<>();
|
||||
if (activityList != null && !activityList.isEmpty()) {
|
||||
for (ActivitySaveDto activitySaveDto : activityList) {
|
||||
Long positionId = activityOrganizationConfig.stream()
|
||||
@@ -97,6 +100,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
if (positionId == null) {
|
||||
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
|
||||
}
|
||||
activityIdToPositionIdMap.put(activitySaveDto.getAdviceDefinitionId(), positionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,9 +183,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
||||
serviceRequest.setEncounterId(encounterId); // 就诊id
|
||||
serviceRequest.setAuthoredTime(curDate); // 请求签发时间
|
||||
|
||||
Long positionId = activityOrganizationConfig.stream()
|
||||
.filter(dto -> activitySaveDto.getAdviceDefinitionId().equals(dto.getActivityDefinitionId()))
|
||||
.map(ActivityOrganizationConfigDto::getOrganizationId).findFirst().orElse(null);
|
||||
Long positionId = activityIdToPositionIdMap.get(activitySaveDto.getAdviceDefinitionId());
|
||||
if (positionId == null) {
|
||||
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
|
||||
}
|
||||
|
||||
@@ -178,22 +178,25 @@ service.interceptors.request.use(config => {
|
||||
}
|
||||
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
|
||||
} else if (code === 500) {
|
||||
// 检查是否需要跳过错误提示
|
||||
if (!res.config?.skipErrorMsg) {
|
||||
ElMessage({ message: msg, type: 'error' })
|
||||
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
|
||||
if (res.config?.skipErrorMsg) {
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
ElMessage({ message: msg, type: 'error' })
|
||||
return Promise.reject(new Error(msg))
|
||||
} else if (code === 601) {
|
||||
// 检查是否需要跳过错误提示
|
||||
if (!res.config?.skipErrorMsg) {
|
||||
ElMessage({ message: msg, type: 'warning' })
|
||||
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
|
||||
if (res.config?.skipErrorMsg) {
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
ElMessage({ message: msg, type: 'warning' })
|
||||
return Promise.reject(new Error(msg))
|
||||
} else if (code !== 200) {
|
||||
// 检查是否需要跳过错误提示
|
||||
if (!res.config?.skipErrorMsg) {
|
||||
ElNotification.error({ title: msg })
|
||||
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
|
||||
if (res.config?.skipErrorMsg) {
|
||||
return Promise.resolve(res.data)
|
||||
}
|
||||
ElNotification.error({ title: msg })
|
||||
return Promise.reject('error')
|
||||
} else {
|
||||
return Promise.resolve(res.data)
|
||||
|
||||
@@ -413,29 +413,33 @@
|
||||
:key="idx"
|
||||
class="selected-item-card"
|
||||
>
|
||||
<!-- Bug #384修复: 项目卡片头部,可展开/收起 -->
|
||||
<!-- Bug #384修复 + #426修复: 项目卡片头部,可展开/收起 -->
|
||||
<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-price">¥{{ item.price }}</span>
|
||||
<!-- 展开图标 -->
|
||||
<el-icon :class="['expand-icon', { expanded: item.expanded }]">
|
||||
<ArrowDown v-if="!item.expanded" />
|
||||
<ArrowUp v-if="item.expanded" />
|
||||
<!-- 展开/收起图标 -->
|
||||
<el-icon class="expand-icon" :class="{ expanded: item.expanded }">
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
<!-- 删除按钮 -->
|
||||
<el-button link type="danger" size="small" @click.stop="handleRemoveItem(idx, item)">
|
||||
<el-icon><Close /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- Bug #428修复: 展开后显示套餐明细或检查方法 -->
|
||||
<div v-if="item.expanded">
|
||||
<!-- Bug #428修复 + #426修复: 展开后显示套餐明细或检查方法 -->
|
||||
<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">
|
||||
<span class="detail-name">{{ detail.name }}</span>
|
||||
<span class="detail-info">数量: {{ detail.quantity }} 单价: ¥{{ detail.price }}</span>
|
||||
</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-for="method in item.methods" :key="method.id" class="method-option">
|
||||
@@ -473,7 +477,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue';
|
||||
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 request from '@/utils/request';
|
||||
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) {
|
||||
if (!item.isPackage || (!item.packageId && !item.packageName)) {
|
||||
// 只要有 packageName 就认为是套餐,不强制要求 isPackage 或 packageId
|
||||
if (!item.packageName && !item.packageId) {
|
||||
return;
|
||||
}
|
||||
item.packageDetailsLoading = true;
|
||||
try {
|
||||
let packageId = item.packageId;
|
||||
if (!packageId && item.packageName) {
|
||||
@@ -612,6 +618,10 @@ async function loadPackageDetailsForItem(item) {
|
||||
}
|
||||
packageId = packages[0].id;
|
||||
}
|
||||
if (!packageId) {
|
||||
item.packageDetails = [];
|
||||
return;
|
||||
}
|
||||
const res = await request({
|
||||
url: `/system/package/${packageId}/details`,
|
||||
method: 'get'
|
||||
@@ -630,6 +640,8 @@ async function loadPackageDetailsForItem(item) {
|
||||
} catch (err) {
|
||||
console.error('加载套餐明细失败:', err);
|
||||
item.packageDetails = [];
|
||||
} finally {
|
||||
item.packageDetailsLoading = false;
|
||||
}
|
||||
}
|
||||
const detailTableRef = ref(null);
|
||||
@@ -912,7 +924,8 @@ async function loadCategoryList() {
|
||||
categoryName: t.name,
|
||||
// “检查类型管理”里配置的执行科室(图三)
|
||||
performDeptName: t.department || '',
|
||||
items: []
|
||||
items: [],
|
||||
methods: [] // #428修复: 初始化 methods 数组,确保 Vue 响应式追踪
|
||||
});
|
||||
}
|
||||
const unclassified = [];
|
||||
@@ -1255,6 +1268,7 @@ async function handleMethodSelect(checked, method, cat) {
|
||||
if (method.packageId) {
|
||||
existingItem.isPackage = true;
|
||||
existingItem.packageId = method.packageId;
|
||||
existingItem.packageName = method.packageName || existingItem.packageName; // #428修复: 确保 packageName 同步
|
||||
// 预加载套餐明细
|
||||
loadPackageDetailsForItem(existingItem);
|
||||
}
|
||||
@@ -1282,12 +1296,13 @@ async function handleMethodSelect(checked, method, cat) {
|
||||
checkType: cat.typeName,
|
||||
nationalCode: targetItem.nationalCode || '',
|
||||
checked: true,
|
||||
methods: [method],
|
||||
methods: cat.methods || [method], // #428修复: 复制分类下全部方法,允许用户切换
|
||||
selectedMethod: method,
|
||||
expanded: false,
|
||||
// 从方法中获取套餐信息(优先级高于项目本身的 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);
|
||||
|
||||
@@ -1396,11 +1411,11 @@ async function handleItemSelect(checked, item, cat) {
|
||||
// Bug #382 修复:移除自动切换页签逻辑,保持当前页签状态
|
||||
}
|
||||
|
||||
// Bug #384修复: 展开/收起项目卡片
|
||||
// Bug #384修复 + #426修复: 展开/收起项目卡片
|
||||
async function toggleItemExpand(item) {
|
||||
item.expanded = !item.expanded;
|
||||
// 如果是展开且该项目是套餐,加载套餐明细
|
||||
if (item.expanded && item.isPackage && (!item.packageDetails || item.packageDetails.length === 0)) {
|
||||
// 如果是展开且该项目是套餐(通过 isPackage 或 packageName 判断),加载套餐明细
|
||||
if (item.expanded && (item.isPackage || item.packageName) && (!item.packageDetails || item.packageDetails.length === 0) && !item.packageDetailsLoading) {
|
||||
await loadPackageDetailsForItem(item);
|
||||
}
|
||||
}
|
||||
@@ -1812,10 +1827,24 @@ defineExpose({ getList });
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
transition: transform 0.2s;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.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修复: 套餐明细列表样式 */
|
||||
|
||||
@@ -3418,7 +3418,12 @@ async function setValue(row) {
|
||||
console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText);
|
||||
// 🔧 Bug #455: 诊疗医嘱(adviceType=3)的执行科室默认使用患者就诊科室,
|
||||
// 不使用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].dose = row.dose || row.doseQuantity;
|
||||
@@ -3652,7 +3657,10 @@ function handleSaveGroup(orderGroupList) {
|
||||
unitCode_dictText: item.unitCodeName || '',
|
||||
statusEnum: 1,
|
||||
// 🔧 修复执行科室逻辑:优先使用 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',
|
||||
conditionId: conditionId.value,
|
||||
conditionDefinitionId: conditionDefinitionId.value,
|
||||
|
||||
@@ -1009,8 +1009,6 @@ function handleLocationClick(item, row, index) {
|
||||
getCount({
|
||||
itemId: form.purchaseinventoryList[index].itemId,
|
||||
orgLocationId: form.purchaseinventoryList[index].sourceLocationId,
|
||||
// objLocationId:purposeLocationId,
|
||||
lotNumber: form.purchaseinventoryList[index].lotNumber,
|
||||
}).then((res) => {
|
||||
if (res.data && res.data.length > 0) {
|
||||
form.purchaseinventoryList[index].itemTable = res.data[0].itemTable || '';
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</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 - 不可操作 -->
|
||||
<el-table-column label="申请日期" align="center" prop="createTime" width="160">
|
||||
<template #default="scope">
|
||||
@@ -1405,6 +1405,12 @@ function getRowClassName({ row }) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// 当前选中行(高亮)
|
||||
const currentRow = ref(null)
|
||||
function handleCurrentChange(row) {
|
||||
currentRow.value = row
|
||||
}
|
||||
|
||||
// 时间格式化函数
|
||||
function parseTime(time, pattern) {
|
||||
if (!time) return ''
|
||||
|
||||
@@ -1552,6 +1552,8 @@ function handleMedicalAdvice(row) {
|
||||
// 过滤掉名称为空的项目
|
||||
const medicineName = item.adviceName || item.advice_name;
|
||||
if (!medicineName || medicineName.trim() === '') return false;
|
||||
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目(已有 requestId 的不应出现在"待生成"列表)
|
||||
if (item.requestId) return false;
|
||||
// 根据药品请求ID去重,避免重复显示
|
||||
const itemId = item.requestId || item.id;
|
||||
if (itemId && seenIds.has(itemId)) return false;
|
||||
@@ -1739,15 +1741,27 @@ function handleTemporaryMedicalSubmit(data) {
|
||||
}
|
||||
})
|
||||
|
||||
// 同步更新计费药品列表:移除已生成医嘱的项目,避免数据重复显示
|
||||
const submittedIds = new Set(
|
||||
(data.temporaryAdvices || []).map(a => a.originalMedicine?.requestId || a.originalMedicine?.chargeItemId).filter(Boolean)
|
||||
// 🔧 修复 Bug #445: 使用稳定的字段组合匹配已提交项目,而不是依赖可能为空的 requestId/chargeItemId
|
||||
// 构建已提交项目的匹配键集合(药品名称 + 规格 + 数量)
|
||||
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(
|
||||
m => !submittedIds.has(m.requestId || m.chargeItemId)
|
||||
)
|
||||
|
||||
if (submittedKeys.size > 0) {
|
||||
temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => {
|
||||
const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`
|
||||
return !submittedKeys.has(key)
|
||||
})
|
||||
} else {
|
||||
// 如果没有任何匹配键,清空待生成列表(所有项目都已提交)
|
||||
temporaryBillingMedicines.value = []
|
||||
}
|
||||
|
||||
@@ -1807,7 +1821,8 @@ function handleQuoteBilling() {
|
||||
temporaryBillingMedicines.value = []
|
||||
temporaryAdvices.value = []
|
||||
|
||||
// 只保留药品类型(adviceType=1),过滤掉耗材(2)和诊疗项目(3)
|
||||
// 🔧 修复 Bug #445: 只保留药品类型(adviceType=1),过滤掉耗材(2)和诊疗项目(3)
|
||||
// 同时过滤掉已有 requestId 的项目(已生成医嘱的不需要再次显示在"待生成"列表中)
|
||||
const filteredItems = res.data.filter(item => {
|
||||
// 匹配 encounterId
|
||||
if (item.encounterId !== temporaryPatientInfo.value.visitId) return false;
|
||||
@@ -1815,7 +1830,10 @@ function handleQuoteBilling() {
|
||||
if (item.adviceType !== 1) return false;
|
||||
// 过滤掉名称为空的项目
|
||||
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条,避免数据过多导致页面卡死
|
||||
const maxItems = 100
|
||||
|
||||
Reference in New Issue
Block a user