diff --git a/Cargo.lock b/Cargo.lock index fe2b206..1d32208 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1656,6 +1656,7 @@ dependencies = [ "erp-auth", "erp-config", "erp-core", + "erp-dialysis", "erp-health", "erp-message", "erp-plugin", diff --git a/Cargo.toml b/Cargo.toml index 42a9eb5..77584f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,6 +107,7 @@ erp-plugin = { path = "crates/erp-plugin" } erp-health = { path = "crates/erp-health" } erp-ai = { path = "crates/erp-ai" } erp-points = { path = "crates/erp-points" } +erp-dialysis = { path = "crates/erp-dialysis" } # Async streaming futures = "0.3" diff --git a/crates/erp-dialysis/src/handler/dialysis_handler.rs b/crates/erp-dialysis/src/handler/dialysis_handler.rs index 3f30a12..203f1f9 100644 --- a/crates/erp-dialysis/src/handler/dialysis_handler.rs +++ b/crates/erp-dialysis/src/handler/dialysis_handler.rs @@ -41,7 +41,7 @@ where DialysisState: FromRef, S: Clone + Send + Sync + 'static, { - require_permission(&ctx, "health.health-data.list")?; + require_permission(&ctx, "health.dialysis.list")?; let page = params.page.unwrap_or(1); let page_size = params.page_size.unwrap_or(20); let result = dialysis_service::list_dialysis_records( @@ -60,7 +60,7 @@ where DialysisState: FromRef, S: Clone + Send + Sync + 'static, { - require_permission(&ctx, "health.health-data.list")?; + require_permission(&ctx, "health.dialysis.list")?; let result = dialysis_service::get_dialysis_record( &state, ctx.tenant_id, record_id, ) @@ -77,7 +77,7 @@ where DialysisState: FromRef, S: Clone + Send + Sync + 'static, { - require_permission(&ctx, "health.health-data.manage")?; + require_permission(&ctx, "health.dialysis.manage")?; let mut req = req; req.sanitize(); let result = dialysis_service::create_dialysis_record( @@ -97,7 +97,7 @@ where DialysisState: FromRef, S: Clone + Send + Sync + 'static, { - require_permission(&ctx, "health.health-data.manage")?; + require_permission(&ctx, "health.dialysis.manage")?; let mut data = req.data; data.sanitize(); let result = dialysis_service::update_dialysis_record( @@ -117,7 +117,7 @@ where DialysisState: FromRef, S: Clone + Send + Sync + 'static, { - require_permission(&ctx, "health.health-data.manage")?; + require_permission(&ctx, "health.dialysis.manage")?; let result = dialysis_service::review_dialysis_record( &state, ctx.tenant_id, record_id, ctx.user_id, req.version, ) @@ -135,7 +135,7 @@ where DialysisState: FromRef, S: Clone + Send + Sync + 'static, { - require_permission(&ctx, "health.health-data.manage")?; + require_permission(&ctx, "health.dialysis.manage")?; dialysis_service::delete_dialysis_record( &state, ctx.tenant_id, record_id, Some(ctx.user_id), req.version, ) diff --git a/crates/erp-dialysis/src/module.rs b/crates/erp-dialysis/src/module.rs index c18e774..7fdea60 100644 --- a/crates/erp-dialysis/src/module.rs +++ b/crates/erp-dialysis/src/module.rs @@ -78,13 +78,13 @@ impl ErpModule for DialysisModule { fn permissions(&self) -> Vec { vec![ PermissionDescriptor { - code: "health.health-data.list".into(), + code: "health.dialysis.list".into(), name: "查看透析记录".into(), description: "查看透析记录列表和详情".into(), module: "erp-dialysis".into(), }, PermissionDescriptor { - code: "health.health-data.manage".into(), + code: "health.dialysis.manage".into(), name: "管理透析记录".into(), description: "创建、编辑、审阅、删除透析记录".into(), module: "erp-dialysis".into(), diff --git a/crates/erp-server/Cargo.toml b/crates/erp-server/Cargo.toml index 083d7af..5a1eb3f 100644 --- a/crates/erp-server/Cargo.toml +++ b/crates/erp-server/Cargo.toml @@ -30,6 +30,7 @@ erp-message.workspace = true erp-plugin.workspace = true erp-health.workspace = true erp-ai.workspace = true +erp-dialysis.workspace = true erp-points.workspace = true anyhow.workspace = true uuid.workspace = true @@ -41,3 +42,4 @@ erp-auth = { workspace = true } erp-plugin = { workspace = true } erp-workflow = { workspace = true } erp-core = { workspace = true } +erp-dialysis = { workspace = true } diff --git a/crates/erp-server/src/main.rs b/crates/erp-server/src/main.rs index 8271ca0..c2912f8 100644 --- a/crates/erp-server/src/main.rs +++ b/crates/erp-server/src/main.rs @@ -357,6 +357,14 @@ async fn main() -> anyhow::Result<()> { "Points module initialized" ); + // Initialize dialysis module + let dialysis_module = erp_dialysis::DialysisModule; + tracing::info!( + module = dialysis_module.name(), + version = dialysis_module.version(), + "Dialysis module initialized" + ); + // Initialize module registry and register modules let registry = ModuleRegistry::new() .register(auth_module) @@ -365,7 +373,8 @@ async fn main() -> anyhow::Result<()> { .register(message_module) .register(health_module) .register(ai_module) - .register(points_module); + .register(points_module) + .register(dialysis_module); tracing::info!( module_count = registry.modules().len(), "Modules registered" @@ -545,6 +554,7 @@ async fn main() -> anyhow::Result<()> { .merge(erp_health::HealthModule::protected_routes()) .merge(erp_ai::AiModule::protected_routes()) .merge(erp_points::PointsModule::protected_routes()) + .merge(erp_dialysis::DialysisModule::protected_routes()) .merge(handlers::audit_log::audit_log_router()) .route( "/upload", diff --git a/crates/erp-server/src/state.rs b/crates/erp-server/src/state.rs index 6ae857b..aedd4b7 100644 --- a/crates/erp-server/src/state.rs +++ b/crates/erp-server/src/state.rs @@ -132,3 +132,14 @@ impl FromRef for erp_points::PointsState { } } } + +/// Allow erp-dialysis handlers to extract their required state. +impl FromRef for erp_dialysis::DialysisState { + fn from_ref(state: &AppState) -> Self { + Self { + db: state.db.clone(), + event_bus: state.event_bus.clone(), + crypto: state.pii_crypto.clone(), + } + } +} diff --git a/crates/erp-server/tests/integration/health_data_tests.rs b/crates/erp-server/tests/integration/health_data_tests.rs index cc76605..8c8e26c 100644 --- a/crates/erp-server/tests/integration/health_data_tests.rs +++ b/crates/erp-server/tests/integration/health_data_tests.rs @@ -3,7 +3,7 @@ //! 验证体征 CRUD、化验报告 CRUD + 审阅、租户隔离、乐观锁。 use erp_health::dto::health_data_dto::*; -use erp_health::dto::dialysis_dto::ReviewLabReportReq; +use erp_dialysis::dto::dialysis_dto::ReviewLabReportReq; use erp_health::service::health_data_service; use super::test_fixture::TestApp; diff --git a/crates/erp-server/tests/integration/health_dialysis_prescription_tests.rs b/crates/erp-server/tests/integration/health_dialysis_prescription_tests.rs index 828f25e..73ae93c 100644 --- a/crates/erp-server/tests/integration/health_dialysis_prescription_tests.rs +++ b/crates/erp-server/tests/integration/health_dialysis_prescription_tests.rs @@ -1,9 +1,9 @@ -//! erp-health 透析方案管理集成测试 +//! erp-dialysis 透析方案管理集成测试 //! //! 验证透析方案 CRUD、列表按患者过滤、租户隔离、乐观锁。 -use erp_health::dto::dialysis_prescription_dto::*; -use erp_health::service::dialysis_prescription_service; +use erp_dialysis::dto::dialysis_prescription_dto::*; +use erp_dialysis::service::dialysis_prescription_service; use super::test_fixture::TestApp; @@ -33,7 +33,7 @@ fn default_create_req(patient_id: uuid::Uuid) -> CreateDialysisPrescriptionReq { async fn seed_prescription(app: &TestApp, patient_id: uuid::Uuid) -> DialysisPrescriptionResp { dialysis_prescription_service::create_prescription( - app.health_state(), app.tenant_id(), Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), default_create_req(patient_id), ) .await @@ -67,7 +67,7 @@ async fn test_dialysis_prescription_get() { let rx = seed_prescription(&app, patient_id).await; let fetched = dialysis_prescription_service::get_prescription( - app.health_state(), app.tenant_id(), rx.id, + app.dialysis_state(), app.tenant_id(), rx.id, ) .await .expect("查询应成功"); @@ -84,7 +84,7 @@ async fn test_dialysis_prescription_update() { let rx = seed_prescription(&app, patient_id).await; let updated = dialysis_prescription_service::update_prescription( - app.health_state(), app.tenant_id(), rx.id, + app.dialysis_state(), app.tenant_id(), rx.id, Some(app.operator_id()), UpdateDialysisPrescriptionReq { blood_flow_rate: Some(350), @@ -122,14 +122,14 @@ async fn test_dialysis_prescription_list_by_patient() { seed_prescription(&app, patient_b).await; let list_a = dialysis_prescription_service::list_prescriptions( - app.health_state(), app.tenant_id(), 1, 20, Some(patient_a), None, + app.dialysis_state(), app.tenant_id(), 1, 20, Some(patient_a), None, ) .await .unwrap(); assert_eq!(list_a.total, 1); let list_b = dialysis_prescription_service::list_prescriptions( - app.health_state(), app.tenant_id(), 1, 20, Some(patient_b), None, + app.dialysis_state(), app.tenant_id(), 1, 20, Some(patient_b), None, ) .await .unwrap(); @@ -146,14 +146,14 @@ async fn test_dialysis_prescription_soft_delete() { let rx = seed_prescription(&app, patient_id).await; dialysis_prescription_service::delete_prescription( - app.health_state(), app.tenant_id(), rx.id, + app.dialysis_state(), app.tenant_id(), rx.id, Some(app.operator_id()), rx.version, ) .await .expect("删除应成功"); let result = dialysis_prescription_service::get_prescription( - app.health_state(), app.tenant_id(), rx.id, + app.dialysis_state(), app.tenant_id(), rx.id, ) .await; assert!(result.is_err(), "软删除后查询应失败"); @@ -170,7 +170,7 @@ async fn test_dialysis_prescription_tenant_isolation() { let other_tenant = uuid::Uuid::new_v4(); let list = dialysis_prescription_service::list_prescriptions( - app.health_state(), other_tenant, 1, 20, None, None, + app.dialysis_state(), other_tenant, 1, 20, None, None, ) .await .unwrap(); @@ -188,7 +188,7 @@ async fn test_dialysis_prescription_version_conflict() { // 先更新一次 dialysis_prescription_service::update_prescription( - app.health_state(), app.tenant_id(), rx.id, + app.dialysis_state(), app.tenant_id(), rx.id, Some(app.operator_id()), UpdateDialysisPrescriptionReq { blood_flow_rate: Some(350), @@ -208,7 +208,7 @@ async fn test_dialysis_prescription_version_conflict() { // 用旧 version 再更新应失败 let result = dialysis_prescription_service::update_prescription( - app.health_state(), app.tenant_id(), rx.id, + app.dialysis_state(), app.tenant_id(), rx.id, Some(app.operator_id()), UpdateDialysisPrescriptionReq { blood_flow_rate: Some(400), diff --git a/crates/erp-server/tests/integration/health_dialysis_tests.rs b/crates/erp-server/tests/integration/health_dialysis_tests.rs index bf2d72b..fc88103 100644 --- a/crates/erp-server/tests/integration/health_dialysis_tests.rs +++ b/crates/erp-server/tests/integration/health_dialysis_tests.rs @@ -1,10 +1,10 @@ -//! erp-health 透析记录集成测试 +//! erp-dialysis 透析记录集成测试 //! //! 验证透析 CRUD、状态流转、PII 加密、乐观锁、租户隔离等核心行为。 use chrono::{NaiveDate, NaiveTime}; -use erp_health::dto::dialysis_dto::*; -use erp_health::service::dialysis_service; +use erp_dialysis::dto::dialysis_dto::*; +use erp_dialysis::service::dialysis_service; use super::test_fixture::TestApp; @@ -42,7 +42,7 @@ async fn test_dialysis_create_basic() { let req = default_create_req(patient_id); let record = dialysis_service::create_dialysis_record( - app.health_state(), app.tenant_id(), Some(app.operator_id()), req, + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), req, ) .await .expect("创建透析记录应成功"); @@ -54,7 +54,7 @@ async fn test_dialysis_create_basic() { // 读取 let fetched = dialysis_service::get_dialysis_record( - app.health_state(), app.tenant_id(), record.id, + app.dialysis_state(), app.tenant_id(), record.id, ) .await .expect("查询应成功"); @@ -74,7 +74,7 @@ async fn test_dialysis_create_pii_encrypted() { req.complication_notes = Some("低血压发作".to_string()); let record = dialysis_service::create_dialysis_record( - app.health_state(), app.tenant_id(), Some(app.operator_id()), req, + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), req, ) .await .expect("创建应成功"); @@ -93,7 +93,7 @@ async fn test_dialysis_update_status_flow() { let patient_id = app.create_patient("状态流转患者").await; let record = dialysis_service::create_dialysis_record( - app.health_state(), app.tenant_id(), Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), default_create_req(patient_id), ) .await @@ -102,7 +102,7 @@ async fn test_dialysis_update_status_flow() { // 先将状态推进到 completed(draft → completed → reviewed) use sea_orm::{EntityTrait, ColumnTrait, QueryFilter}; - use erp_health::entity::dialysis_record; + use erp_dialysis::entity::dialysis_record; let _ = dialysis_record::Entity::update_many() .col_expr(dialysis_record::Column::Status, sea_orm::sea_query::Expr::val("completed").into()) .col_expr(dialysis_record::Column::Version, sea_orm::sea_query::Expr::val(record.version + 1).into()) @@ -113,7 +113,7 @@ async fn test_dialysis_update_status_flow() { // 审核: completed → reviewed let reviewed = dialysis_service::review_dialysis_record( - app.health_state(), app.tenant_id(), record.id, app.operator_id(), record.version + 1, + app.dialysis_state(), app.tenant_id(), record.id, app.operator_id(), record.version + 1, ) .await .expect("审核应成功"); @@ -131,35 +131,35 @@ async fn test_dialysis_list_by_patient() { let patient_b = app.create_patient("列表患者B").await; dialysis_service::create_dialysis_record( - app.health_state(), app.tenant_id(), Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), default_create_req(patient_a), ) .await .unwrap(); dialysis_service::create_dialysis_record( - app.health_state(), app.tenant_id(), Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), default_create_req(patient_a), ) .await .unwrap(); dialysis_service::create_dialysis_record( - app.health_state(), app.tenant_id(), Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), default_create_req(patient_b), ) .await .unwrap(); let list_a = dialysis_service::list_dialysis_records( - app.health_state(), app.tenant_id(), patient_a, 1, 20, + app.dialysis_state(), app.tenant_id(), patient_a, 1, 20, ) .await .unwrap(); assert_eq!(list_a.total, 2); let list_b = dialysis_service::list_dialysis_records( - app.health_state(), app.tenant_id(), patient_b, 1, 20, + app.dialysis_state(), app.tenant_id(), patient_b, 1, 20, ) .await .unwrap(); @@ -175,7 +175,7 @@ async fn test_dialysis_tenant_isolation() { let patient_id = app.create_patient("隔离患者").await; let record = dialysis_service::create_dialysis_record( - app.health_state(), app.tenant_id(), Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), default_create_req(patient_id), ) .await @@ -184,7 +184,7 @@ async fn test_dialysis_tenant_isolation() { // 用不同 tenant_id 查询应失败 let other_tenant = uuid::Uuid::new_v4(); let result = dialysis_service::get_dialysis_record( - app.health_state(), other_tenant, record.id, + app.dialysis_state(), other_tenant, record.id, ) .await; assert!(result.is_err(), "不同租户不应看到此记录"); @@ -199,7 +199,7 @@ async fn test_dialysis_version_conflict() { let patient_id = app.create_patient("乐观锁患者").await; let record = dialysis_service::create_dialysis_record( - app.health_state(), app.tenant_id(), Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), default_create_req(patient_id), ) .await @@ -207,7 +207,7 @@ async fn test_dialysis_version_conflict() { // 用正确版本更新 let updated = dialysis_service::update_dialysis_record( - app.health_state(), app.tenant_id(), record.id, Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), record.id, Some(app.operator_id()), UpdateDialysisRecordReq { dialysis_date: None, start_time: None, end_time: None, dry_weight: None, pre_weight: None, post_weight: None, @@ -227,7 +227,7 @@ async fn test_dialysis_version_conflict() { // 用旧版本更新应失败 let result = dialysis_service::update_dialysis_record( - app.health_state(), app.tenant_id(), record.id, Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), record.id, Some(app.operator_id()), UpdateDialysisRecordReq { dialysis_date: None, start_time: None, end_time: None, dry_weight: None, pre_weight: None, post_weight: None, @@ -254,7 +254,7 @@ async fn test_dialysis_soft_delete() { let patient_id = app.create_patient("软删除患者").await; let record = dialysis_service::create_dialysis_record( - app.health_state(), app.tenant_id(), Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), default_create_req(patient_id), ) .await @@ -262,21 +262,21 @@ async fn test_dialysis_soft_delete() { // 删除 dialysis_service::delete_dialysis_record( - app.health_state(), app.tenant_id(), record.id, Some(app.operator_id()), record.version, + app.dialysis_state(), app.tenant_id(), record.id, Some(app.operator_id()), record.version, ) .await .expect("删除应成功"); // 查询应失败 let result = dialysis_service::get_dialysis_record( - app.health_state(), app.tenant_id(), record.id, + app.dialysis_state(), app.tenant_id(), record.id, ) .await; assert!(result.is_err(), "软删除后应不可见"); // 列表中不应出现 let list = dialysis_service::list_dialysis_records( - app.health_state(), app.tenant_id(), patient_id, 1, 20, + app.dialysis_state(), app.tenant_id(), patient_id, 1, 20, ) .await .unwrap(); @@ -292,7 +292,7 @@ async fn test_dialysis_create_without_patient_returns_error() { let fake_patient = uuid::Uuid::new_v4(); let result = dialysis_service::create_dialysis_record( - app.health_state(), app.tenant_id(), Some(app.operator_id()), + app.dialysis_state(), app.tenant_id(), Some(app.operator_id()), default_create_req(fake_patient), ) .await; diff --git a/crates/erp-server/tests/integration/test_fixture.rs b/crates/erp-server/tests/integration/test_fixture.rs index f624d7c..545bbe6 100644 --- a/crates/erp-server/tests/integration/test_fixture.rs +++ b/crates/erp-server/tests/integration/test_fixture.rs @@ -6,6 +6,7 @@ use erp_health::dto::doctor_dto::CreateDoctorReq; use erp_health::dto::patient_dto::CreatePatientReq; use erp_health::service::{appointment_service, doctor_service, patient_service}; use erp_health::state::HealthState; +use erp_dialysis::state::DialysisState; use super::test_db::TestDb; @@ -41,6 +42,14 @@ impl TestApp { &self.health_state } + pub fn dialysis_state(&self) -> DialysisState { + DialysisState { + db: self.test_db.db().clone(), + event_bus: self.health_state.event_bus.clone(), + crypto: self.health_state.crypto.clone(), + } + } + pub fn tenant_id(&self) -> uuid::Uuid { self.tenant_id }