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:
111
ANALYSIS_434.md
111
ANALYSIS_434.md
@@ -1,87 +1,66 @@
|
|||||||
# Bug #434 分析报告
|
# Bug #434 修复分析报告(追加)
|
||||||
|
|
||||||
## 问题:编辑弹窗中"切口类型"字段未正确回显数据
|
## 问题:编辑弹窗中"切口类型"字段未正确回显数据
|
||||||
|
|
||||||
## 数据流追踪
|
## 根因分析(数据流追踪)
|
||||||
|
|
||||||
### 1. 数据库现状
|
### 1. SQL 查询 `getSurgeryScheduleDetail`(Mapper XML 第77-114行)
|
||||||
- `op_schedule` 表:**没有** `incision_level` 字段(58列,已验证)
|
|
||||||
- `cli_surgery` 表:**有** `incision_level` 字段,且有数据
|
|
||||||
- DDL 脚本 `sql/bug_434_add_incision_level_to_op_schedule.sql` 已写好但**未执行**
|
|
||||||
|
|
||||||
### 2. 后端 SQL 查询(getSurgeryScheduleDetail)
|
|
||||||
```sql
|
```sql
|
||||||
SELECT os.*, cs.incision_level AS "incisionLevel", ...
|
SELECT os.*, cs.incision_level AS "incisionLevel", ...
|
||||||
FROM op_schedule os
|
FROM op_schedule os
|
||||||
INNER JOIN cli_surgery cs ON os.oper_code = cs.surgery_no
|
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 实体
|
### 2. 关键矛盾:`os.*` 包含了 `os.incision_level`
|
||||||
- **缺失** `incisionLevel` 字段 → `BeanUtils.copyProperties` 无法将 `incisionLevel` 复制到 `OpSchedule` 实体
|
- `os.*` 展开所有 `op_schedule` 表列,包括 `os.incision_level`(NULL)
|
||||||
- 结果:`updateSurgerySchedule` 更新时,`incisionLevel` 不会写入 `op_schedule` 表
|
- 后面又显式选了 `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
|
```javascript
|
||||||
Object.assign(form, data) // 后端返回 data.incisionLevel
|
|
||||||
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
|
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
|
||||||
```
|
```
|
||||||
- 前端使用 `form.incisionType` 绑定 el-select
|
当 `data.incisionLevel` 为 NULL 时,`form.incisionType` 保持 `undefined`,el-select 无法匹配到任何选项,显示"请选择切口类型"。
|
||||||
- 后端返回 `incisionLevel`,前端转换为 `incisionType`
|
|
||||||
|
|
||||||
### 5. 前端 submitForm
|
### 5. 数据库验证
|
||||||
```javascript
|
```sql
|
||||||
const submitData = { ...form, orgId: ..., incisionLevel: form.incisionType }
|
SELECT os.incision_level, cs.incision_level FROM op_schedule os
|
||||||
delete submitData.incisionType
|
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` 中不存储
|
|
||||||
|
|
||||||
## 根因(双重问题)
|
|
||||||
|
|
||||||
### 根因 A(SQL 别名 - 已修复)
|
|
||||||
SQL 中 `cs.incision_level AS "incisionLevel"` 已加双引号,PostgreSQL 保持大小写,MyBatis 可正确映射。
|
|
||||||
**状态:已修复,无需改动**
|
|
||||||
|
|
||||||
### 根因 B(实体缺失字段 - 待修复)
|
|
||||||
`OpSchedule` 实体类中**没有** `incisionLevel` 字段。导致:
|
|
||||||
1. 编辑时即使后端正确返回数据,保存到 `op_schedule` 时该字段被忽略
|
|
||||||
2. 数据仅存储在 `cli_surgery.incision_level`,`op_schedule` 中没有备份
|
|
||||||
|
|
||||||
## 修复方案
|
## 修复方案
|
||||||
|
|
||||||
### 1. 执行 DDL
|
### 将 `os.*` 替换为显式列选择(排除 `os.incision_level`)
|
||||||
```sql
|
|
||||||
ALTER TABLE op_schedule ADD COLUMN IF NOT EXISTS incision_level INT2;
|
```diff
|
||||||
COMMENT ON COLUMN op_schedule.incision_level IS '手术切口等级 1-I级切口 2-II级切口 3-III级切口 4-IV级切口';
|
- 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 实体中添加字段
|
这样 `incisionLevel` 只被 `cs.incision_level AS "incisionLevel"` 映射一次,保证正确获取值。
|
||||||
```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. 验证保存后数据正确
|
|
||||||
|
|||||||
@@ -76,7 +76,64 @@
|
|||||||
<!-- 根据ID查询手术安排详情-->
|
<!-- 根据ID查询手术安排详情-->
|
||||||
<select id="getSurgeryScheduleDetail" resultType="com.openhis.web.clinicalmanage.dto.OpScheduleDto">
|
<select id="getSurgeryScheduleDetail" resultType="com.openhis.web.clinicalmanage.dto.OpScheduleDto">
|
||||||
SELECT
|
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,
|
os.oper_code AS surgeryNo,
|
||||||
ap.name AS patient_name,
|
ap.name AS patient_name,
|
||||||
CASE ap.gender_enum WHEN 0 THEN '男' WHEN 1 THEN '女' ELSE '未知' END AS gender,
|
CASE ap.gender_enum WHEN 0 THEN '男' WHEN 1 THEN '女' ELSE '未知' END AS gender,
|
||||||
|
|||||||
Reference in New Issue
Block a user