Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b6b00b6c2 | |||
| 1ddf8a2ccd | |||
| 0a37b05aab | |||
| 20817d6dc4 |
44
bug444_analysis.md
Normal file
44
bug444_analysis.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Bug #444 分析报告
|
||||||
|
|
||||||
|
## Bug 描述
|
||||||
|
【手术管理-门诊手术安排】生成临时医嘱界面,"已引用计费药品"列表未正常显示药品详细名称信息,且错误地带出了非药品类的计费信息(如手术诊疗项目"小腿烧伤扩创交腿皮瓣修复术"、检查项目"心脏彩色多普勒超声")。
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
### 数据流
|
||||||
|
1. 用户点击"医嘱"按钮 → `handleMedicalAdvice()` → 调用 `getPrescriptionList()` 获取计费数据
|
||||||
|
2. 用户对数据进行过滤后展示在"已引用计费药品"列表
|
||||||
|
3. 用户点击"引用计费"按钮 → `handleQuoteBilling()` → 再次调用 `getPrescriptionList()` 获取最新计费数据
|
||||||
|
|
||||||
|
### 根因定位
|
||||||
|
**`handleQuoteBilling()` 方法(index.vue:1866-1877)缺少非药品关键词过滤逻辑。**
|
||||||
|
|
||||||
|
`handleMedicalAdvice()` 中有两层过滤:
|
||||||
|
1. `adviceType !== 1` 过滤(只保留药品类型)
|
||||||
|
2. **关键词排除过滤**(排除名称中包含"术"、"超声"、"检查"等非药品关键词的项目)
|
||||||
|
|
||||||
|
但 `handleQuoteBilling()` 中只有第一层过滤(`adviceType !== 1`),**缺少关键词排除过滤**。
|
||||||
|
|
||||||
|
当后端返回的计费数据中某些非药品项目被错误标注为 `adviceType=1` 时:
|
||||||
|
- `handleMedicalAdvice()` 能通过关键词过滤排除这些项目
|
||||||
|
- `handleQuoteBilling()` 无法排除,导致非药品项目出现在"已引用计费药品"列表中
|
||||||
|
|
||||||
|
### 代码对比
|
||||||
|
|
||||||
|
| 过滤条件 | handleMedicalAdvice (L1576-1597) | handleQuoteBilling (L1866-1877) |
|
||||||
|
|---------|:---:|:---:|
|
||||||
|
| encounterId 匹配 | ✓ | ✓ |
|
||||||
|
| adviceType === 1 | ✓ | ✓ |
|
||||||
|
| 名称非空 | ✓ | ✓ |
|
||||||
|
| **关键词排除** | ✓ **有** | ✗ **缺失** |
|
||||||
|
| requestId 过滤 | ✓ | ✓ |
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
在 `handleQuoteBilling()` 方法的过滤逻辑中,添加与 `handleMedicalAdvice()` 一致的关键词排除逻辑:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 🔧 修复 Bug #444: 排除名称中包含手术/检查/诊疗关键词的非药品项目
|
||||||
|
const excludedKeywords = ['术', '超声', '多普勒', '检查', '检验', '彩超', 'X线', 'CT', 'MRI', '扫描', '造影'];
|
||||||
|
if (excludedKeywords.some(kw => medicineName.includes(kw))) return false;
|
||||||
|
```
|
||||||
@@ -1854,21 +1854,6 @@ function handleTemporaryMedicalRefresh() {
|
|||||||
function handleQuoteBilling() {
|
function handleQuoteBilling() {
|
||||||
// 重新拉取计费药品数据
|
// 重新拉取计费药品数据
|
||||||
if (temporaryPatientInfo.value.visitId) {
|
if (temporaryPatientInfo.value.visitId) {
|
||||||
// 🔧 修复 Bug #445: 在清空之前提取已提交项目的复合匹配键
|
|
||||||
// 原因:后续的 ID 匹配过滤依赖 temporaryAdvices,但 temporaryAdvices 会被先清空
|
|
||||||
// 新医嘱没有 requestId/chargeItemId,需用名称+规格+数量的复合键匹配
|
|
||||||
const submittedKeys = new Set(
|
|
||||||
(temporaryAdvices.value || [])
|
|
||||||
.map(a => {
|
|
||||||
const om = a.originalMedicine || {}
|
|
||||||
const name = om.medicineName || om.adviceName || a.adviceName || ''
|
|
||||||
const spec = om.specification || om.volume || ''
|
|
||||||
const qty = om.quantity ?? 0
|
|
||||||
return `${name}|||${spec}|||${qty}`
|
|
||||||
})
|
|
||||||
.filter(k => k !== '|||0')
|
|
||||||
)
|
|
||||||
|
|
||||||
temporaryMedicalLoading.value = true // 🔧 新增:开始加载
|
temporaryMedicalLoading.value = true // 🔧 新增:开始加载
|
||||||
getPrescriptionList(temporaryPatientInfo.value.visitId, 6, temporaryPatientInfo.value.operCode).then((res) => {
|
getPrescriptionList(temporaryPatientInfo.value.visitId, 6, temporaryPatientInfo.value.operCode).then((res) => {
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
@@ -1876,13 +1861,14 @@ function handleQuoteBilling() {
|
|||||||
temporaryBillingMedicines.value = []
|
temporaryBillingMedicines.value = []
|
||||||
temporaryAdvices.value = []
|
temporaryAdvices.value = []
|
||||||
|
|
||||||
// 🔧 修复 Bug #445: 只保留药品类型(adviceType=1),过滤掉耗材(2)和诊疗项目(3/6)
|
// 🔧 修复 Bug #444: 统一过滤逻辑,与 handleMedicalAdvice 保持一致
|
||||||
// 同时过滤掉已有 requestId 的项目(已生成医嘱的不需要再次显示在"待生成"列表中)
|
// 1. 使用 Number() + snake_case 回退,避免类型转换导致过滤失效
|
||||||
|
// 2. 增加关键词二次过滤,排除手术/检查/诊疗等非药品项目
|
||||||
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;
|
||||||
// 只保留药品类型(adviceType=1),过滤掉耗材(2)和诊疗项目(3/6)
|
// 只保留药品类型(adviceType=1),过滤掉耗材(2)和诊疗项目(3/6)
|
||||||
// 🔧 修复 Bug #444: 使用 Number() 显式转换 + snake_case 回退,避免字符串 "1" 匹配失败
|
// 🔧 修复 Bug #444: 使用 Number() 显式转换,增加 snake_case 回退
|
||||||
const at = Number(item.adviceType ?? item.advice_type);
|
const at = Number(item.adviceType ?? item.advice_type);
|
||||||
if (at !== 1) return false;
|
if (at !== 1) return false;
|
||||||
// 过滤掉名称为空的项目
|
// 过滤掉名称为空的项目
|
||||||
@@ -2007,58 +1993,28 @@ function handleQuoteBilling() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目,避免"引用计费"后已提交项目重新出现在"待生成"列表
|
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目,避免"引用计费"后已提交项目重新出现在"待生成"列表
|
||||||
// 使用清空前提取的 submittedKeys(名称|||规格|||数量复合键)进行匹配
|
// 原因:后端返回的计费数据中,已生成医嘱的项目可能没有 requestId 字段
|
||||||
if (submittedKeys.size > 0) {
|
// 方案:用 chargeItemId/requestId/id 与已有的 temporaryAdvices 做匹配,排除已生成项目
|
||||||
|
if (temporaryAdvices.value.length > 0) {
|
||||||
|
const existingAdviceIds = new Set()
|
||||||
|
temporaryAdvices.value.forEach(a => {
|
||||||
|
const om = a.originalMedicine || {}
|
||||||
|
if (om.requestId) existingAdviceIds.add(String(om.requestId))
|
||||||
|
if (om.chargeItemId) existingAdviceIds.add(String(om.chargeItemId))
|
||||||
|
if (om.id) existingAdviceIds.add(String(om.id))
|
||||||
|
})
|
||||||
|
if (existingAdviceIds.size > 0) {
|
||||||
temporaryBillingMedicines.value = temporaryBillingMedicines.value.filter(m => {
|
temporaryBillingMedicines.value = temporaryBillingMedicines.value.filter(m => {
|
||||||
const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity ?? 0}`
|
const mRequestId = m.requestId != null ? String(m.requestId) : null
|
||||||
return !submittedKeys.has(key)
|
const mChargeItemId = m.chargeItemId != null ? String(m.chargeItemId) : null
|
||||||
|
const mId = m.id != null ? String(m.id) : null
|
||||||
|
if (mRequestId && existingAdviceIds.has(mRequestId)) return false
|
||||||
|
if (mChargeItemId && existingAdviceIds.has(mChargeItemId)) return false
|
||||||
|
if (mId && existingAdviceIds.has(mId)) return false
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
// 同步更新 temporaryAdvices,保持两份数据一致
|
|
||||||
temporaryAdvices.value = temporaryBillingMedicines.value.map((medicine, index) => {
|
|
||||||
const specMatch = medicine.specification ? medicine.specification.match(/(\d+)(\D+)/) : null
|
|
||||||
const specValue = specMatch ? parseInt(specMatch[1]) : 1
|
|
||||||
const specUnit = specMatch ? specMatch[2] : 'ml'
|
|
||||||
const dosage = specValue * (medicine.quantity || 1)
|
|
||||||
let usageCode = 'iv'
|
|
||||||
let usageLabel = '静脉注射'
|
|
||||||
try {
|
|
||||||
const jsonContent = medicine.contentJson || medicine.content_json;
|
|
||||||
if (jsonContent) {
|
|
||||||
const contentData = JSON.parse(jsonContent);
|
|
||||||
if (contentData.methodCode) {
|
|
||||||
usageCode = contentData.methodCode;
|
|
||||||
usageLabel = getUsageLabel(contentData.methodCode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
|
||||||
if (!usageCode || usageCode === 'iv') {
|
|
||||||
if (medicine.medicineName && medicine.medicineName.includes('注射液')) {
|
|
||||||
usageCode = 'iv'; usageLabel = '静脉注射';
|
|
||||||
} else if (medicine.medicineName && medicine.medicineName.includes('片')) {
|
|
||||||
usageCode = 'po'; usageLabel = '口服';
|
|
||||||
} else if (medicine.medicineName && medicine.medicineName.includes('胶囊')) {
|
|
||||||
usageCode = 'po'; usageLabel = '口服';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id: index + 1,
|
|
||||||
adviceName: medicine.medicineName || '',
|
|
||||||
dosage,
|
|
||||||
unit: specUnit,
|
|
||||||
usage: usageCode,
|
|
||||||
usageLabel,
|
|
||||||
frequency: '临时',
|
|
||||||
executeTime: new Date().toLocaleString('zh-CN'),
|
|
||||||
originalMedicine: {
|
|
||||||
...medicine,
|
|
||||||
medicineName: medicine.medicineName,
|
|
||||||
specification: medicine.specification,
|
|
||||||
quantity: medicine.quantity,
|
|
||||||
encounterId: temporaryPatientInfo.value.visitId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
temporaryMedicalLoading.value = false // 🔧 新增:加载完成
|
temporaryMedicalLoading.value = false // 🔧 新增:加载完成
|
||||||
ElMessage.success('已成功引用最新计费药品信息!')
|
ElMessage.success('已成功引用最新计费药品信息!')
|
||||||
|
|||||||
Reference in New Issue
Block a user