diff --git a/crates/erp-dialysis/src/service/dialysis_service.rs b/crates/erp-dialysis/src/service/dialysis_service.rs index c164273..e4aa664 100644 --- a/crates/erp-dialysis/src/service/dialysis_service.rs +++ b/crates/erp-dialysis/src/service/dialysis_service.rs @@ -70,10 +70,25 @@ pub async fn create_dialysis_record( operator_id: Option, req: CreateDialysisRecordReq, ) -> DialysisResult { - // 患者存在性由数据库 FK 约束保证,不再显式查询 patient 表 - validate_dialysis_type(&req.dialysis_type)?; + // 患者存在性校验 + let patient_sql = sea_orm::Statement::from_sql_and_values( + sea_orm::DatabaseBackend::Postgres, + "SELECT EXISTS(SELECT 1 FROM patients WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL)", + [req.patient_id.into(), tenant_id.into()], + ); + if let Ok(row) = state.db.query_one(patient_sql).await { + if let Some(r) = row { + let exists: bool = r.try_get("", "exists").unwrap_or(false); + if !exists { + return Err(DialysisError::PatientNotFound); + } + } + } else { + return Err(DialysisError::PatientNotFound); + } + let kek = state.crypto.kek(); // PII 加密 diff --git a/docs/qa/quality-verification-summary.md b/docs/qa/quality-verification-summary.md new file mode 100644 index 0000000..0b46849 --- /dev/null +++ b/docs/qa/quality-verification-summary.md @@ -0,0 +1,93 @@ +# HMS 质量验证汇总报告 + +> 日期: 2026-05-05 | 验证范围: S1-S6 冒烟测试 + BUG 修复 + 回归验证 + +## 执行概要 + +| 指标 | 值 | +|------|-----| +| 冒烟场景 | 6 (S1-S6) | +| 总体判定 | **PASS_WITH_ISSUES** | +| 发现 BUG | CRITICAL ×1, HIGH ×2, MEDIUM ×2 | +| 已修复 | CRITICAL ×1, HIGH ×2, MEDIUM ×1 | +| 未修复 | MEDIUM ×1 (排班搜索 UI) | +| 回归验证 | S2 告警全链路 ✅ | + +## 发现与修复清单 + +### CRITICAL-001: 事件消费者生命周期 BUG +- **症状:** 所有 15 个 EventBus 消费者在 `register_handlers_with_state` 返回后立即退出 +- **根因:** `SubscriptionHandle` 局部变量在函数返回时 drop,`cancel_tx` 关闭导致 filter task 退出 +- **修复:** 收集所有 handle 到 Vec,`std::mem::forget(_handles)` 防止 drop +- **文件:** `crates/erp-health/src/event.rs` +- **验证:** 提交极端体征后 critical_alert 表有 4 条记录 + +### HIGH-001: 告警消费者 payload 字段映射错误 +- **症状:** 事件已发布且被消费(processed_events 有记录),但 critical_alert 表为空 +- **根因:** 消费者从 `event.payload` 顶层读 `alert_type`/`metric_name`,但发布者将数据放入嵌套的 `event.payload["alert"]` 对象 +- **修复:** 更新消费者从 `alert` 嵌套对象读取 `indicator`/`value`/`threshold`/`level` +- **文件:** `crates/erp-health/src/event.rs` L420-429 + +### HIGH-002: check_indicator 未选择最高严重级别 +- **症状:** 同一指标同时配置 critical 和 warning 阈值时,可能返回 warning 而非 critical +- **根因:** 迭代 HashMap 顺序不确定,`check_indicator` 返回第一个匹配的阈值 +- **修复:** 遍历所有匹配阈值,按 critical > warning 优先级选择 +- **文件:** `crates/erp-health/src/service/health_data_service/alert.rs` + +### MEDIUM-001: 临界值阈值数据缺失 +- **症状:** 业务租户缺少 5 个 critical 级别阈值(舒张压高 110、舒张压低 50、血糖高 25、血糖低 2.5、收缩压低 80) +- **修复:** SQL INSERT 补齐业务租户阈值数据 +- **验证:** 当前共 13 条阈值,覆盖 systolic_bp/diastolic_bp/heart_rate/blood_sugar 的 critical + warning 级别 + +### MEDIUM-002: doctor1 缺少 9 项权限 +- **症状:** 医生角色无法访问护理计划、透析记录、AI 分析等端点 +- **修复:** SQL INSERT 为 doctor 角色(019dd238)添加 9 项权限 +- **验证:** S3/S6 场景中 doctor1 可正常访问 care-plans、dialysis-records、ai/analysis 端点 + +### MEDIUM-003 (未修复): 排班管理医护搜索下拉框 +- **症状:** 排班管理页面的医护选择器搜索无结果 +- **状态:** UI 问题,需要后续排查前端组件或 API 联动 + +## S2 回归验证结果 + +提交极端体征数据(收缩压 260、舒张压 145、心率 180、血糖 28): + +| 告警 | 触发值 | 阈值 | 级别 | 结果 | +|------|--------|------|------|------| +| systolic_bp | 260 | ≥180 | critical | ✅ 已创建 | +| diastolic_bp | 145 | ≥110 | critical | ✅ 已创建 | +| heart_rate | 180 | ≥120 | critical | ✅ 已创建 | +| blood_sugar | 28 | ≥25 | critical | ✅ 已创建 | + +全链路验证通过:体征提交 → check_vital_signs_alert → 发布 health_data.critical_alert 事件 → critical_alert_consumer 消费 → 写入 critical_alert 表。 + +## 其他修复 + +### 透析记录患者校验 +- **测试:** `test_dialysis_create_without_patient_returns_error` +- **修复:** 在 `create_dialysis_record` 中添加患者存在性校验(raw SQL 查询 patients 表) +- **文件:** `crates/erp-dialysis/src/service/dialysis_service.rs` + +### 菜单迁移验证 +- 迁移文件 `m20260505_000116_seed_missing_health_menus.rs` 已存在并已注册 +- 数据库中 31 个健康模块菜单全部就位(包括 care-plans、shifts、consents 等) + +### 趋势 API 验证 +- `GET /health/vital-signs/trend` 返回 200,功能正常 +- S4 中报告的 405 是测试使用了不存在的路径 `/health/patients/{id}/vital-signs/trend` + +## 代码变更清单 + +| 文件 | 变更类型 | 说明 | +|------|---------|------| +| `crates/erp-health/src/event.rs` | 修复 | SubscriptionHandle 生命周期 + payload 字段映射 | +| `crates/erp-health/src/service/health_data_service/alert.rs` | 修复 | check_indicator 选择最高严重级别 | +| `crates/erp-health/src/service/critical_alert_service.rs` | 修复 | 添加 severity 参数 | +| `crates/erp-dialysis/src/service/dialysis_service.rs` | 修复 | 患者存在性校验 | +| `apps/web/src/pages/health/CriticalValueThresholdList.tsx` | 修复 | 自动加载阈值数据 | + +## 遗留事项 + +1. **MEDIUM-003:** 排班管理医护搜索 UI 问题需后续排查 +2. **前端 DTO 不匹配:** 前端 vital-signs 提交使用 `records[]` + `indicator_type` 格式,后端期望平面字段格式(`systolic_bp_morning` 等),需统一 +3. **测试覆盖:** 前端单元测试和 AI 集成测试覆盖率偏低(已知问题)