feat(health): 添加 erp-health 健康管理模块骨架
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

新建 erp-health 原生 Rust crate,覆盖设计规格中定义的 5 大业务域:

- 16 个 SeaORM Entity(患者/家属/标签/医生/健康档案/体征/化验单/预约/排班/随访/咨询等)
- 16 表数据库迁移(含索引、外键、默认值、可回滚)
- 40+ API 路由骨架(患者管理/健康数据/预约排班/随访/咨询/医生管理)
- 12 个权限声明(health.patient/health-data/appointment/follow-up/consultation/doctor 各 .list/.manage)
- DTO / Service / Handler / Event 四层架构,Service 使用 todo!() 占位
- erp-server 集成:模块注册 + AppState FromRef 桥接 + 路由挂载

同步更新 CLAUDE.md 项目进度、wiki 知识库、设计规格文档。
This commit is contained in:
iven
2026-04-23 19:59:22 +08:00
parent 5ac8e18d74
commit ca50d32f6e
61 changed files with 6853 additions and 1208 deletions

View File

@@ -0,0 +1,322 @@
use axum::Router;
use uuid::Uuid;
use erp_core::error::AppResult;
use erp_core::events::EventBus;
use erp_core::module::{ErpModule, PermissionDescriptor};
use crate::handler::{
appointment_handler, consultation_handler, doctor_handler, follow_up_handler,
health_data_handler, patient_handler,
};
pub struct HealthModule;
impl HealthModule {
pub fn new() -> Self {
Self
}
pub fn public_routes<S>() -> Router<S>
where
crate::state::HealthState: axum::extract::FromRef<S>,
S: Clone + Send + Sync + 'static,
{
Router::new()
}
pub fn protected_routes<S>() -> Router<S>
where
crate::state::HealthState: axum::extract::FromRef<S>,
S: Clone + Send + Sync + 'static,
{
Router::new()
// 患者管理
.route(
"/health/patients",
axum::routing::get(patient_handler::list_patients)
.post(patient_handler::create_patient),
)
.route(
"/health/patients/{id}",
axum::routing::get(patient_handler::get_patient)
.put(patient_handler::update_patient)
.delete(patient_handler::delete_patient),
)
.route(
"/health/patients/{id}/tags",
axum::routing::post(patient_handler::manage_patient_tags),
)
.route(
"/health/patients/{id}/health-summary",
axum::routing::get(patient_handler::get_health_summary),
)
.route(
"/health/patients/{id}/family-members",
axum::routing::get(patient_handler::list_family_members)
.post(patient_handler::create_family_member),
)
.route(
"/health/patients/{id}/family-members/{fid}",
axum::routing::put(patient_handler::update_family_member)
.delete(patient_handler::delete_family_member),
)
.route(
"/health/patients/{id}/doctors",
axum::routing::post(patient_handler::assign_doctor),
)
.route(
"/health/patients/{id}/doctors/{did}",
axum::routing::delete(patient_handler::remove_doctor),
)
// 健康数据
.route(
"/health/patients/{id}/vital-signs",
axum::routing::get(health_data_handler::list_vital_signs)
.post(health_data_handler::create_vital_signs),
)
.route(
"/health/patients/{id}/vital-signs/{vid}",
axum::routing::put(health_data_handler::update_vital_signs)
.delete(health_data_handler::delete_vital_signs),
)
.route(
"/health/patients/{id}/lab-reports",
axum::routing::get(health_data_handler::list_lab_reports)
.post(health_data_handler::create_lab_report),
)
.route(
"/health/patients/{id}/lab-reports/{rid}",
axum::routing::put(health_data_handler::update_lab_report)
.delete(health_data_handler::delete_lab_report),
)
.route(
"/health/patients/{id}/health-records",
axum::routing::get(health_data_handler::list_health_records)
.post(health_data_handler::create_health_record),
)
.route(
"/health/patients/{id}/health-records/{rid}",
axum::routing::put(health_data_handler::update_health_record)
.delete(health_data_handler::delete_health_record),
)
.route(
"/health/patients/{id}/trends",
axum::routing::get(health_data_handler::list_trends),
)
.route(
"/health/patients/{id}/trends/generate",
axum::routing::post(health_data_handler::generate_trend),
)
.route(
"/health/patients/{id}/trends/{indicator}",
axum::routing::get(health_data_handler::get_indicator_timeseries),
)
// 预约排班
.route(
"/health/appointments",
axum::routing::get(appointment_handler::list_appointments)
.post(appointment_handler::create_appointment),
)
.route(
"/health/appointments/{id}/status",
axum::routing::put(appointment_handler::update_appointment_status),
)
.route(
"/health/doctor-schedules",
axum::routing::get(appointment_handler::list_schedules)
.post(appointment_handler::create_schedule),
)
.route(
"/health/doctor-schedules/{id}",
axum::routing::put(appointment_handler::update_schedule),
)
.route(
"/health/doctor-schedules/calendar",
axum::routing::get(appointment_handler::calendar_view),
)
// 随访管理
.route(
"/health/follow-up-tasks",
axum::routing::get(follow_up_handler::list_tasks)
.post(follow_up_handler::create_task),
)
.route(
"/health/follow-up-tasks/{id}",
axum::routing::put(follow_up_handler::update_task)
.delete(follow_up_handler::delete_task),
)
.route(
"/health/follow-up-tasks/{id}/records",
axum::routing::post(follow_up_handler::create_record),
)
.route(
"/health/follow-up-records",
axum::routing::get(follow_up_handler::list_records),
)
// 咨询管理
.route(
"/health/consultation-sessions",
axum::routing::get(consultation_handler::list_sessions),
)
.route(
"/health/consultation-sessions/{id}/messages",
axum::routing::get(consultation_handler::list_messages),
)
.route(
"/health/consultation-sessions/{id}/close",
axum::routing::put(consultation_handler::close_session),
)
.route(
"/health/consultation-messages",
axum::routing::post(consultation_handler::create_message),
)
.route(
"/health/consultation-sessions/export",
axum::routing::get(consultation_handler::export_sessions),
)
// 医护管理
.route(
"/health/doctors",
axum::routing::get(doctor_handler::list_doctors)
.post(doctor_handler::create_doctor),
)
.route(
"/health/doctors/{id}",
axum::routing::get(doctor_handler::get_doctor)
.put(doctor_handler::update_doctor)
.delete(doctor_handler::delete_doctor),
)
}
}
impl Default for HealthModule {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl ErpModule for HealthModule {
fn name(&self) -> &str {
"health"
}
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
fn dependencies(&self) -> Vec<&str> {
vec!["auth"]
}
fn register_event_handlers(&self, bus: &EventBus) {
crate::event::register_handlers(bus);
}
async fn on_tenant_created(
&self,
tenant_id: Uuid,
db: &sea_orm::DatabaseConnection,
_event_bus: &EventBus,
) -> AppResult<()> {
crate::service::seed::seed_tenant_health(db, tenant_id)
.await
.map_err(|e| erp_core::error::AppError::Internal(e.to_string()))?;
tracing::info!(tenant_id = %tenant_id, "Health module tenant initialized");
Ok(())
}
async fn on_tenant_deleted(
&self,
tenant_id: Uuid,
db: &sea_orm::DatabaseConnection,
) -> AppResult<()> {
crate::service::seed::soft_delete_tenant_data(db, tenant_id)
.await
.map_err(|e| erp_core::error::AppError::Internal(e.to_string()))?;
tracing::info!(tenant_id = %tenant_id, "Health module tenant data soft-deleted");
Ok(())
}
fn permissions(&self) -> Vec<PermissionDescriptor> {
vec![
PermissionDescriptor {
code: "health.patient.list".into(),
name: "查看患者列表".into(),
description: "查看和搜索患者列表、详情".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.patient.manage".into(),
name: "管理患者".into(),
description: "创建、编辑、删除患者".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.health-data.list".into(),
name: "查看健康数据".into(),
description: "查看体检记录、监测数据、化验报告".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.health-data.manage".into(),
name: "管理健康数据".into(),
description: "录入、编辑、删除健康数据".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.appointment.list".into(),
name: "查看预约".into(),
description: "查看预约列表和排班".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.appointment.manage".into(),
name: "管理预约".into(),
description: "创建、确认、取消预约".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.follow-up.list".into(),
name: "查看随访".into(),
description: "查看随访任务和记录".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.follow-up.manage".into(),
name: "管理随访".into(),
description: "创建、分配、完成随访任务".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.consultation.list".into(),
name: "查看咨询".into(),
description: "查看咨询会话和消息记录".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.consultation.manage".into(),
name: "管理咨询".into(),
description: "关闭会话、导出记录".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.doctor.list".into(),
name: "查看医护".into(),
description: "查看医护列表和详情".into(),
module: "health".into(),
},
PermissionDescriptor {
code: "health.doctor.manage".into(),
name: "管理医护".into(),
description: "创建、编辑医护档案、排班".into(),
module: "health".into(),
},
]
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}