Fix Bug #434: 门诊手术安排:编辑弹窗中"切口类型"字段未正确回显数据

根因:getSurgeryScheduleDetail SQL中 os.* 包含 os.incision_level(NULL),
与 cs.incision_level AS "incisionLevel" 产生列名冲突,MyBatis resultType
映射时将NULL值覆盖实际数据,导致前端form.incisionType为undefined。
修复:将 os.* 替换为显式列选择(排除os.incision_level),确保incisionLevel
只被 cs.incision_level 映射一次。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
赵云
2026-05-16 18:05:34 +08:00
parent c1162af5ea
commit 37baa20163
2 changed files with 103 additions and 67 deletions

View File

@@ -1,87 +1,66 @@
# Bug #434 分析报告
# Bug #434 修复分析报告(追加)
## 问题:编辑弹窗中"切口类型"字段未正确回显数据
## 数据流追踪
## 根因分析(数据流追踪
### 1. 数据库现状
- `op_schedule` 表:**没有** `incision_level` 字段58列已验证
- `cli_surgery` 表:**有** `incision_level` 字段,且有数据
- DDL 脚本 `sql/bug_434_add_incision_level_to_op_schedule.sql` 已写好但**未执行**
### 2. 后端 SQL 查询getSurgeryScheduleDetail
### 1. SQL 查询 `getSurgeryScheduleDetail`Mapper XML 第77-114行
```sql
SELECT os.*, cs.incision_level AS "incisionLevel", ...
FROM op_schedule os
INNER JOIN cli_surgery cs ON os.oper_code = cs.surgery_no
```
- `os.*` 不包含 `incision_level`(因为 `op_schedule` 没有该列)
- 通过 JOIN 从 `cli_surgery` 读取,别名 `"incisionLevel"`(已加双引号保持大小写)
- PostgreSQL 返回列名: `incisionLevel`(驼峰)→ MyBatis 可正确映射到 `OpScheduleDto.incisionLevel`
### 3. OpSchedule 实体
- **缺失** `incisionLevel` 字段 → `BeanUtils.copyProperties` 无法将 `incisionLevel` 复制到 `OpSchedule` 实体
- 结果:`updateSurgerySchedule` 更新时,`incisionLevel` 不会写入 `op_schedule`
### 2. 关键矛盾:`os.*` 包含了 `os.incision_level`
- `os.*` 展开所有 `op_schedule` 表列,包括 `os.incision_level`NULL
- 后面又显式选了 `cs.incision_level AS "incisionLevel"`(实际值,如 1
### 4. 前端 handleEdit
### 3. MyBatis `resultType` 映射冲突
MyBatis 使用 `resultType` 时按**列在结果集中的出现顺序**映射到 DTO 属性:
1. 第一列 `os.incision_level`snake_case → `incisionLevel`)→ NULL
2. 后面列 `cs.incision_level AS "incisionLevel"``incisionLevel` → 1
**当两列映射到同一属性时,第一列的值可能覆盖第二列**(取决于 MyBatis 版本和配置),导致 `incisionLevel` 为 NULL。
### 4. 前端影响
```javascript
Object.assign(form, data) // 后端返回 data.incisionLevel
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
```
- 前端使用 `form.incisionType` 绑定 el-select
- 后端返回 `incisionLevel`,前端转换为 `incisionType`
`data.incisionLevel` 为 NULL 时,`form.incisionType` 保持 `undefined`el-select 无法匹配到任何选项,显示"请选择切口类型"。
### 5. 前端 submitForm
```javascript
const submitData = { ...form, orgId: ..., incisionLevel: form.incisionType }
delete submitData.incisionType
### 5. 数据库验证
```sql
SELECT os.incision_level, cs.incision_level FROM op_schedule os
INNER JOIN cli_surgery cs ON os.oper_code = cs.surgery_no;
-- 结果: os.incision_level = NULL, cs.incision_level = 1有数据
```
- 提交时将 `incisionType` 转为 `incisionLevel`
- 后端 `updateSurgerySchedule` 接收 `OpScheduleDto`(有 `incisionLevel` 字段)
- `BeanUtils.copyProperties` 复制到 `OpSchedule` 实体(没有 `incisionLevel`)→ **字段丢失**
- `syncSurgeryIncisionLevel` 仅同步到 `cli_surgery``op_schedule` 中不存储
## 根因(双重问题)
### 根因 ASQL 别名 - 已修复)
SQL 中 `cs.incision_level AS "incisionLevel"` 已加双引号PostgreSQL 保持大小写MyBatis 可正确映射。
**状态:已修复,无需改动**
### 根因 B实体缺失字段 - 待修复)
`OpSchedule` 实体类中**没有** `incisionLevel` 字段。导致:
1. 编辑时即使后端正确返回数据,保存到 `op_schedule` 时该字段被忽略
2. 数据仅存储在 `cli_surgery.incision_level``op_schedule` 中没有备份
## 修复方案
### 1. 执行 DDL
```sql
ALTER TABLE op_schedule ADD COLUMN IF NOT EXISTS incision_level INT2;
COMMENT ON COLUMN op_schedule.incision_level IS '手术切口等级 1-I级切口 2-II级切口 3-III级切口 4-IV级切口';
### 将 `os.*` 替换为显式列选择(排除 `os.incision_level`
```diff
- SELECT os.*,
+ SELECT os.schedule_id, os.tenant_id, os.apply_id, os.patient_id, os.visit_id,
+ os.oper_code, os.oper_name, os.preoperative_diagnosis, os.postoperative_diagnosis,
+ os.schedule_date, os.sequence_no, os.is_first_surgery, os.is_allergy_medication,
+ os.allergy_remark, os.surgery_nature, os.surgery_site,
+ os.admission_time, os.entry_time, os.room_code, os.table_no,
+ os.anes_method, os.anes_doctor1_code, os.anes_doctor2_code, os.anes_doctor3_code,
+ os.scrub_nurse_code, os.circu_nurse1_code, os.circu_nurse2_code,
+ os.scrub_nurse1_code, os.scrub_nurse2_code, os.surgeon_code,
+ os.assistant1_code, os.assistant2_code, os.assistant3_code,
+ os.start_time, os.end_time, os.anes_start, os.anes_end,
+ os.oper_status, os.implant_flag, os.implant_serial,
+ os.blood_loss, os.blood_trans, os.infection_diagnosis, os.isolation_type,
+ os.patient_weight, os.patient_height, os.communication_info,
+ os.is_external_expert, os.external_expert_name, os.fee_type,
+ os.remark, os.create_time, os.creator_id, os.update_time, os.tenant_id,
+ os.delete_flag, os.update_by, os.create_by, os.updater_id,
os.oper_code AS surgeryNo,
...
cs.incision_level AS "incisionLevel",
...
```
### 2. 在 OpSchedule 实体中添加字段
```java
/** 切口类型 */
private Integer incisionLevel;
```
### 3. 修改 mapper XML可选增强
- `getSurgerySchedulePage` 查询中也加入 `cs.incision_level AS "incisionLevel"`(目前列表没有该字段)
- `getSurgeryScheduleDetail` 已有 `"incisionLevel"` 别名,无需改动
### 4. 从同步到直接保存
- `syncSurgeryIncisionLevel` 方法仍然保留(用于同步到 `cli_surgery`
- `updateSurgerySchedule` 中因为实体有了 `incisionLevel``BeanUtils.copyProperties` 会自动复制
- `updateById` 会自动更新 `op_schedule.incision_level`
## 影响范围
- **数据库**: 执行 DDL 添加 `op_schedule.incision_level` 字段
- **后端**: `OpSchedule.java` 添加 `incisionLevel` 字段
- **前端**: 无需修改(数据绑定和转换逻辑已正确)
## 验证计划
1. 执行 DDL 后验证字段存在
2. 修改 Java 实体后验证编译通过
3. 验证编辑弹窗回显
4. 验证保存后数据正确
这样 `incisionLevel` 只被 `cs.incision_level AS "incisionLevel"` 映射一次,保证正确获取值。

View File

@@ -76,7 +76,64 @@
<!-- 根据ID查询手术安排详情-->
<select id="getSurgeryScheduleDetail" resultType="com.openhis.web.clinicalmanage.dto.OpScheduleDto">
SELECT
os.*,
os.schedule_id,
os.tenant_id,
os.apply_id,
os.patient_id,
os.visit_id,
os.oper_code,
os.oper_name,
os.preoperative_diagnosis,
os.postoperative_diagnosis,
os.schedule_date,
os.sequence_no,
os.is_first_surgery,
os.is_allergy_medication,
os.allergy_remark,
os.surgery_nature,
os.surgery_site,
os.admission_time,
os.entry_time,
os.room_code,
os.table_no,
os.anes_method,
os.anes_doctor1_code,
os.anes_doctor2_code,
os.anes_doctor3_code,
os.scrub_nurse_code,
os.circu_nurse1_code,
os.circu_nurse2_code,
os.scrub_nurse1_code,
os.scrub_nurse2_code,
os.surgeon_code,
os.assistant1_code,
os.assistant2_code,
os.assistant3_code,
os.start_time,
os.end_time,
os.anes_start,
os.anes_end,
os.oper_status,
os.implant_flag,
os.implant_serial,
os.blood_loss,
os.blood_trans,
os.infection_diagnosis,
os.isolation_type,
os.patient_weight,
os.patient_height,
os.communication_info,
os.is_external_expert,
os.external_expert_name,
os.fee_type,
os.remark,
os.create_time,
os.creator_id,
os.update_time,
os.delete_flag,
os.update_by,
os.create_by,
os.updater_id,
os.oper_code AS surgeryNo,
ap.name AS patient_name,
CASE ap.gender_enum WHEN 0 THEN '男' WHEN 1 THEN '女' ELSE '未知' END AS gender,