Files
his/MD/bugs/BUG_716_ANALYSIS.md

215 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bug #716 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-13 02:33:10
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 716
- **标题**: 【住院医生站-临床医嘱】住院医嘱录入界面:医嘱类型/耗材仓库显示不符合业务逻辑,且开嘱医生列无回写值
- **模块**: 住院医生工作站
- **提出人**: 陈显精
---
I have enough evidence now. Let me provide the comprehensive analysis.
---
## Bug #716 分析报告
### 一、Bug 理解
**原文引用 — 禅道 Bug 标题:**
> 【住院医生站-临床医嘱】住院医嘱录入界面:医嘱类型/耗材仓库显示不符合业务逻辑,且开嘱医生列无回写值
**重现步骤:**
1. 登录账号 `doctor1`,在"住院医生工作站"选中患者,点击【新增】医嘱
2. 点击【医嘱类型】下拉框,观察显示内容
3. 在添加含耗材的医嘱时,观察【耗材仓库】字段选择列表的显示内容
4. 保存并签发该医嘱,查看医嘱列表明细中【开嘱医生】列的显示
**结果:**
1. 医嘱类型显示为数字(如"2"
2. 耗材仓库选择框显示的是批号(如"4444444"
3. 已保存/签发的医嘱,【开嘱医生】列显示为"-"
**期望:**
1. 医嘱类型下拉应展示业务名称(如"耗材"),而非数字
2. 耗材仓库选择列表应展示"仓库名称",而非批号
3. 【开嘱医生】应显示当前操作医生的姓名
**附图关键信息:**
- 图2506红色箭头标注"显示数字",指向医嘱类型区域显示"2"
- 图2507红色箭头标注"应该显示耗材仓库 不是批号",耗材仓库下拉选中值为"4444444"
- 图2508红色箭头标注"未回写",新保存的耗材医嘱【开嘱医生】列显示"-"
**综合总结:** 用户在住院医生工作站新增耗材类医嘱时,遇到三个显示/数据问题:(1) 医嘱类型下拉框的 el-select 显示原始数字编码而非业务名称;(2) 耗材仓库下拉框显示的是 inventoryId/批号而非仓库名称标签;(3) 新保存的耗材医嘱在列表中开嘱医生列显示"-",说明 requesterId 的字典翻译或数据回写链路断裂。
---
### 二、根因分析
#### 问题1医嘱类型显示数字
**目标文件:**
- `healthlink-his-ui/src/views/inpatientDoctor/home/components/order/index.vue` (行 312-357)
**根因:** 内联编辑行的医嘱类型 el-select 使用 `:model-value="getRowSelectValue(scope.row)"` 单向绑定。`@change` 处理器直接修改 `filterPrescriptionList[scope.rowIndex].adviceType`,但 `filterPrescriptionList` 是 computed 属性,其返回的数组是 `prescriptionList.value.filter(...)` 的结果。虽然修改的是同一个对象引用,但 Vue 的 computed 缓存机制可能导致 el-select 未及时重新评估 `model-value`,从而显示原始值而非匹配的 label。
此外,`getRowSelectValue` 返回 `row.adviceType`(数字),而选项的 `value` 也是数字 `2`。如果 `adviceType` 在某处被转为字符串 `"2"`(例如从 `contentJson` 反序列化时),则 `model-value="2"` (string) 与 `value: 2` (number) 不匹配el-select 会显示原始值。
**关键代码:**
```javascript
// index.vue:1202
function getRowSelectValue(row) {
if (row.adviceType == 1 && row.categoryCode) {
return '1-' + row.categoryCode;
}
if (row.adviceType == 7) {
return 7;
}
return row.adviceType; // ← 可能是 string "2" 而非 number 2
}
```
**影响范围:** `index.vue:316` 的 el-select `:model-value` 绑定
---
#### 问题2耗材仓库显示批号
**目标文件:**
- `healthlink-his-ui/src/views/inpatientDoctor/home/components/order/OrderForm.vue` (行 479-498)
**根因:** 耗材(adviceType==2)的药房/仓库下拉框使用 `v-model="row.inventoryId"`el-option 的 `:value="item.inventoryId"`。当 `row.inventoryId` 与 stockList 中的 `item.inventoryId` 类型不一致时(一个是 string一个是 numberel-select 无法匹配选项,回退显示原始 `inventoryId` 值。
后端 `AdviceInventoryDto.inventoryId` 使用了 `@JsonSerialize(using = ToStringSerializer.class)`Jackson 会将其序列化为字符串。但在 `setValue` 函数中,`inventoryId``selectedStock?.inventoryId` 赋值,而 `selectedStock` 来自 `mergedStockList`(合并了 `inventoryList``priceList`)。如果 `priceList` 中也有 `inventoryId` 字段且为 number 类型,`{ ...item, ...priceList[index] }` 的 spread 会覆盖为 number导致类型不一致。
**关键代码:**
```javascript
// index.vue:2206
mergedStockList = row.inventoryList.map((item, index) => {
return { ...item, ...priceList[index] }; // priceList 可能覆盖 inventoryId 类型
});
// OrderForm.vue:480
<el-select v-model="row.inventoryId" ...>
<el-option :value="item.inventoryId" :label="`${item.locationName} 批次号: ...`" />
</el-select>
```
---
#### 问题3开嘱医生显示"-"
**目标文件:**
- 后端:`AdviceManageAppServiceImpl.java` (行 817-910, `handDevice` 方法)
- 前端:`index.vue` (行 1030-1033, 数据转换)
**根因:** 后端 `AdviceSaveDto` 构造函数设置了 `this.practitionerId = SecurityUtils.getLoginUser().getPractitionerId()`。但 `handDevice` 方法中,`requesterId` 只在 `if (is_save)` 分支内设置:
```java
if (is_save) {
deviceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId());
}
```
当用户直接签发(`adviceOpType=SIGN_ADVICE`)时,`is_save=false``requesterId` 不会被设置到 `DeviceRequest` 对象上。虽然 MyBatis-Plus 的 `saveOrUpdate` 默认跳过 null 字段(不会覆盖已有的 `requester_id`),但如果这是**首次签发**(未先保存),则 `requester_id` 在数据库中为 NULL。
更重要的是SQL 查询的列别名 `requester_id_dict_text`(下划线命名)与 DTO 字段 `requesterId_dictText`混合命名不匹配。MyBatis 的 `mapUnderscoreToCamelCase` 会将 `requester_id_dict_text` 映射为 `requesterIdDictText`,而非 `requesterId_dictText`。虽然 `@Dict` 注解理论上会补填,但如果 `requesterId` 本身为 NULL则字典查询也返回 NULL最终前端回退到 `'-'`
---
### 三、修复方案
#### 修复1医嘱类型 el-select 显示数字
**文件:** `healthlink-his-ui/src/views/inpatientDoctor/home/components/order/index.vue`
**修改内容:**
1.`getRowSelectValue` 函数中,确保返回值类型与选项 `value` 类型一致(统一为 number
2.`@change` 处理器中,使用 `prescriptionList.value[index]` 直接操作源数据,而非通过 computed 属性
```javascript
// 修复 getRowSelectValue确保返回 number
function getRowSelectValue(row) {
const adviceType = Number(row.adviceType);
if (adviceType === 1 && row.categoryCode) {
return '1-' + row.categoryCode;
}
if (adviceType === 7) {
return 7;
}
return isNaN(adviceType) ? undefined : adviceType;
}
```
#### 修复2耗材仓库显示批号
**文件:** `healthlink-his-ui/src/views/inpatientDoctor/home/components/order/OrderForm.vue`
**修改内容:**
1. 在耗材(adviceType==2)的 el-select 中,使用 `:value="String(item.inventoryId)"``v-model` 统一为 string 类型
2. 或者在 `setValue` 函数中确保 `inventoryId` 类型一致
```javascript
// OrderForm.vue 中耗材部分,统一 inventoryId 为 string
<el-option
v-for="item in row.stockList"
:key="item.inventoryId"
:value="String(item.inventoryId)"
:label="`${item.locationName} 批次号: ${item.lotNumber ?? '-'} 库存:${...}`"
/>
```
同时在 `index.vue``setValue` 中:
```javascript
inventoryId: selectedStock?.inventoryId ? String(selectedStock.inventoryId) : undefined,
```
#### 修复3开嘱医生显示"-"
**文件:** `healthlink-his-server/.../AdviceManageAppServiceImpl.java`
**修改内容:**
`handDevice` 方法中,将 `requesterId` 的设置移到 `if (is_save)` 块外面,确保签发操作也设置 `requesterId`
```java
// handDevice 方法中longInsertOrUpdateList 和 tempInsertOrUpdateList 的循环内
// 将 requesterId 设置移到 if (is_save) 块外
deviceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId()); // ← 移到这里
if (is_save) {
deviceRequest.setBusNo(...);
deviceRequest.setGenerateSourceEnum(...);
// ... 其他仅保存时设置的字段
}
```
同样在 `handMedication``handService` 方法中检查是否存在相同问题。
**附加修复(保险措施):** 在前端数据转换中,确保 `requesterId_dictText` 的回退逻辑更健壮:
```javascript
requesterId_dictText: item.requesterId_dictText
|| (item.requesterId ? (String(item.requesterId) === String(userStore.practitionerId) ? userStore.name : '') : '')
|| '-',
```
---
### 四、路由决策
**FIXER:** `zhaoyun`(前端开发)+ `guanyu`(后端开发)
**REASON:** 问题1和问题2是纯前端 el-select 绑定/类型问题由赵云修复问题3涉及后端 `handDevice` 方法中 `requesterId` 设置位置不当,需要关羽修改后端 Java 代码。两个角色各负责独立的修复范围,无冲突,可并行工作。
---
## 路由决策
- **FIXER_ID**: guanyu
- **修复 Agent**: guanyu后端
- **原因**: ** 问题1和问题2是纯前端 el-select 绑定/类型问题由赵云修复问题3涉及后端 `handDevice` 方法中 `requesterId` 设置位置不当,需要关羽修改后端 Java 代码。两个角色各负责独立的修复范围,无冲突,可并行工作。
> ⚠️ 修复人员请先验证以上分析是否正确,再执行修复。