feat(health): 添加 erp-health 健康管理模块骨架
新建 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:
19
crates/erp-health/Cargo.toml
Normal file
19
crates/erp-health/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "erp-health"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
erp-core.workspace = true
|
||||
tokio.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
uuid.workspace = true
|
||||
chrono.workspace = true
|
||||
axum.workspace = true
|
||||
sea-orm.workspace = true
|
||||
tracing.workspace = true
|
||||
thiserror.workspace = true
|
||||
validator.workspace = true
|
||||
utoipa.workspace = true
|
||||
async-trait.workspace = true
|
||||
95
crates/erp-health/src/dto/appointment_dto.rs
Normal file
95
crates/erp-health/src/dto/appointment_dto.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use chrono::{NaiveDate, NaiveTime};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CreateAppointmentReq {
|
||||
pub patient_id: Uuid,
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub appointment_type: Option<String>,
|
||||
pub appointment_date: NaiveDate,
|
||||
pub start_time: NaiveTime,
|
||||
pub end_time: NaiveTime,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UpdateAppointmentStatusReq {
|
||||
pub status: String,
|
||||
pub cancel_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct AppointmentResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub appointment_type: String,
|
||||
pub appointment_date: NaiveDate,
|
||||
pub start_time: NaiveTime,
|
||||
pub end_time: NaiveTime,
|
||||
pub status: String,
|
||||
pub cancel_reason: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CreateScheduleReq {
|
||||
pub doctor_id: Uuid,
|
||||
pub schedule_date: NaiveDate,
|
||||
pub period_type: Option<String>,
|
||||
pub start_time: NaiveTime,
|
||||
pub end_time: NaiveTime,
|
||||
pub max_appointments: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UpdateScheduleReq {
|
||||
pub start_time: Option<NaiveTime>,
|
||||
pub end_time: Option<NaiveTime>,
|
||||
pub max_appointments: Option<i32>,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ScheduleResp {
|
||||
pub id: Uuid,
|
||||
pub doctor_id: Uuid,
|
||||
pub schedule_date: NaiveDate,
|
||||
pub period_type: String,
|
||||
pub start_time: NaiveTime,
|
||||
pub end_time: NaiveTime,
|
||||
pub max_appointments: i32,
|
||||
pub current_appointments: i32,
|
||||
pub status: String,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
|
||||
pub struct CalendarQuery {
|
||||
pub start_date: Option<NaiveDate>,
|
||||
pub end_date: Option<NaiveDate>,
|
||||
pub doctor_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CalendarDayResp {
|
||||
pub date: NaiveDate,
|
||||
pub schedules: Vec<ScheduleResp>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
|
||||
pub struct AppointmentListQuery {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub status: Option<String>,
|
||||
pub patient_id: Option<Uuid>,
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub date: Option<NaiveDate>,
|
||||
}
|
||||
46
crates/erp-health/src/dto/consultation_dto.rs
Normal file
46
crates/erp-health/src/dto/consultation_dto.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct SessionResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub consultation_type: String,
|
||||
pub status: String,
|
||||
pub last_message_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub unread_count_patient: i32,
|
||||
pub unread_count_doctor: i32,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct MessageResp {
|
||||
pub id: Uuid,
|
||||
pub session_id: Uuid,
|
||||
pub sender_id: Uuid,
|
||||
pub sender_role: String,
|
||||
pub content_type: String,
|
||||
pub content: String,
|
||||
pub is_read: bool,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CreateMessageReq {
|
||||
pub session_id: Uuid,
|
||||
pub sender_id: Uuid,
|
||||
pub sender_role: String,
|
||||
pub content_type: Option<String>,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
|
||||
pub struct SessionQuery {
|
||||
pub status: Option<String>,
|
||||
pub patient_id: Option<Uuid>,
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
}
|
||||
123
crates/erp-health/src/dto/health_data_dto.rs
Normal file
123
crates/erp-health/src/dto/health_data_dto.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use chrono::NaiveDate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// 用 f64 替代 Decimal 以满足 utoipa ToSchema
|
||||
type Decimal = f64;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CreateVitalSignsReq {
|
||||
pub record_date: NaiveDate,
|
||||
pub systolic_bp_morning: Option<i32>,
|
||||
pub diastolic_bp_morning: Option<i32>,
|
||||
pub systolic_bp_evening: Option<i32>,
|
||||
pub diastolic_bp_evening: Option<i32>,
|
||||
pub heart_rate: Option<i32>,
|
||||
pub weight: Option<Decimal>,
|
||||
pub blood_sugar: Option<Decimal>,
|
||||
pub water_intake_ml: Option<i32>,
|
||||
pub urine_output_ml: Option<i32>,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UpdateVitalSignsReq {
|
||||
pub record_date: Option<NaiveDate>,
|
||||
pub systolic_bp_morning: Option<i32>,
|
||||
pub diastolic_bp_morning: Option<i32>,
|
||||
pub systolic_bp_evening: Option<i32>,
|
||||
pub diastolic_bp_evening: Option<i32>,
|
||||
pub heart_rate: Option<i32>,
|
||||
pub weight: Option<Decimal>,
|
||||
pub blood_sugar: Option<Decimal>,
|
||||
pub water_intake_ml: Option<i32>,
|
||||
pub urine_output_ml: Option<i32>,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct VitalSignsResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub record_date: NaiveDate,
|
||||
pub systolic_bp_morning: Option<i32>,
|
||||
pub diastolic_bp_morning: Option<i32>,
|
||||
pub systolic_bp_evening: Option<i32>,
|
||||
pub diastolic_bp_evening: Option<i32>,
|
||||
pub heart_rate: Option<i32>,
|
||||
pub weight: Option<Decimal>,
|
||||
pub blood_sugar: Option<Decimal>,
|
||||
pub water_intake_ml: Option<i32>,
|
||||
pub urine_output_ml: Option<i32>,
|
||||
pub notes: Option<String>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CreateLabReportReq {
|
||||
pub report_date: NaiveDate,
|
||||
pub report_type: String,
|
||||
pub indicators: Option<serde_json::Value>,
|
||||
pub image_urls: Option<serde_json::Value>,
|
||||
pub doctor_interpretation: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct LabReportResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub report_date: NaiveDate,
|
||||
pub report_type: String,
|
||||
pub indicators: Option<serde_json::Value>,
|
||||
pub image_urls: Option<serde_json::Value>,
|
||||
pub doctor_interpretation: Option<String>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CreateHealthRecordReq {
|
||||
pub record_type: Option<String>,
|
||||
pub record_date: NaiveDate,
|
||||
pub source: Option<String>,
|
||||
pub overall_assessment: Option<String>,
|
||||
pub report_file_url: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct HealthRecordResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub record_type: String,
|
||||
pub record_date: NaiveDate,
|
||||
pub source: Option<String>,
|
||||
pub overall_assessment: Option<String>,
|
||||
pub report_file_url: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct TrendResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub period_start: NaiveDate,
|
||||
pub period_end: NaiveDate,
|
||||
pub indicator_summary: Option<serde_json::Value>,
|
||||
pub abnormal_items: Option<serde_json::Value>,
|
||||
pub generation_type: String,
|
||||
pub report_file_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct IndicatorTimeseriesResp {
|
||||
pub indicator: String,
|
||||
pub data: Vec<(NaiveDate, f64)>,
|
||||
}
|
||||
4
crates/erp-health/src/dto/mod.rs
Normal file
4
crates/erp-health/src/dto/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod appointment_dto;
|
||||
pub mod consultation_dto;
|
||||
pub mod health_data_dto;
|
||||
pub mod patient_dto;
|
||||
93
crates/erp-health/src/dto/patient_dto.rs
Normal file
93
crates/erp-health/src/dto/patient_dto.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use chrono::NaiveDate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CreatePatientReq {
|
||||
pub name: String,
|
||||
pub gender: Option<String>,
|
||||
pub birth_date: Option<NaiveDate>,
|
||||
pub blood_type: Option<String>,
|
||||
pub id_number: Option<String>,
|
||||
pub allergy_history: Option<String>,
|
||||
pub medical_history_summary: Option<String>,
|
||||
pub emergency_contact_name: Option<String>,
|
||||
pub emergency_contact_phone: Option<String>,
|
||||
pub source: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UpdatePatientReq {
|
||||
pub name: Option<String>,
|
||||
pub gender: Option<String>,
|
||||
pub birth_date: Option<NaiveDate>,
|
||||
pub blood_type: Option<String>,
|
||||
pub id_number: Option<String>,
|
||||
pub allergy_history: Option<String>,
|
||||
pub medical_history_summary: Option<String>,
|
||||
pub emergency_contact_name: Option<String>,
|
||||
pub emergency_contact_phone: Option<String>,
|
||||
pub source: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct PatientResp {
|
||||
pub id: Uuid,
|
||||
pub user_id: Option<Uuid>,
|
||||
pub name: String,
|
||||
pub gender: Option<String>,
|
||||
pub birth_date: Option<NaiveDate>,
|
||||
pub blood_type: Option<String>,
|
||||
pub id_number: Option<String>,
|
||||
pub allergy_history: Option<String>,
|
||||
pub medical_history_summary: Option<String>,
|
||||
pub emergency_contact_name: Option<String>,
|
||||
pub emergency_contact_phone: Option<String>,
|
||||
pub status: String,
|
||||
pub verification_status: String,
|
||||
pub source: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct FamilyMemberReq {
|
||||
pub name: String,
|
||||
pub relationship: String,
|
||||
pub phone: Option<String>,
|
||||
pub birth_date: Option<NaiveDate>,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct FamilyMemberResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub name: String,
|
||||
pub relationship: String,
|
||||
pub phone: Option<String>,
|
||||
pub birth_date: Option<NaiveDate>,
|
||||
pub notes: Option<String>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ManageTagsReq {
|
||||
pub tag_ids: Vec<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
|
||||
pub struct PatientListQuery {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub search: Option<String>,
|
||||
pub tag_id: Option<Uuid>,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
55
crates/erp-health/src/entity/appointment.rs
Normal file
55
crates/erp-health/src/entity/appointment.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "appointment")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub appointment_type: String,
|
||||
pub appointment_date: chrono::NaiveDate,
|
||||
pub start_time: chrono::NaiveTime,
|
||||
pub end_time: chrono::NaiveTime,
|
||||
pub status: String,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub cancel_reason: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub notes: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient::Entity",
|
||||
from = "Column::PatientId",
|
||||
to = "super::patient::Column::Id"
|
||||
)]
|
||||
Patient,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::doctor_profile::Entity",
|
||||
from = "Column::DoctorId",
|
||||
to = "super::doctor_profile::Column::Id"
|
||||
)]
|
||||
Doctor,
|
||||
}
|
||||
|
||||
impl Related<super::patient::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Patient.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
43
crates/erp-health/src/entity/consultation_message.rs
Normal file
43
crates/erp-health/src/entity/consultation_message.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "consultation_message")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub session_id: Uuid,
|
||||
pub sender_id: Uuid,
|
||||
pub sender_role: String,
|
||||
pub content_type: String,
|
||||
pub content: String,
|
||||
pub is_read: bool,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::consultation_session::Entity",
|
||||
from = "Column::SessionId",
|
||||
to = "super::consultation_session::Column::Id"
|
||||
)]
|
||||
Session,
|
||||
}
|
||||
|
||||
impl Related<super::consultation_session::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Session.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
61
crates/erp-health/src/entity/consultation_session.rs
Normal file
61
crates/erp-health/src/entity/consultation_session.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "consultation_session")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub doctor_id: Option<Uuid>,
|
||||
#[sea_orm(rename = "type")]
|
||||
pub consultation_type: String,
|
||||
pub status: String,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub last_message_at: Option<DateTimeUtc>,
|
||||
pub unread_count_patient: i32,
|
||||
pub unread_count_doctor: i32,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient::Entity",
|
||||
from = "Column::PatientId",
|
||||
to = "super::patient::Column::Id"
|
||||
)]
|
||||
Patient,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::doctor_profile::Entity",
|
||||
from = "Column::DoctorId",
|
||||
to = "super::doctor_profile::Column::Id"
|
||||
)]
|
||||
Doctor,
|
||||
#[sea_orm(has_many = "super::consultation_message::Entity")]
|
||||
Message,
|
||||
}
|
||||
|
||||
impl Related<super::patient::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Patient.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::consultation_message::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Message.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
54
crates/erp-health/src/entity/doctor_profile.rs
Normal file
54
crates/erp-health/src/entity/doctor_profile.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "doctor_profile")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub user_id: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub department: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub specialty: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub license_number: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub bio: Option<String>,
|
||||
pub online_status: String,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::patient_doctor_relation::Entity")]
|
||||
PatientRelation,
|
||||
#[sea_orm(has_many = "super::doctor_schedule::Entity")]
|
||||
Schedule,
|
||||
}
|
||||
|
||||
impl Related<super::patient_doctor_relation::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::PatientRelation.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::doctor_schedule::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Schedule.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
45
crates/erp-health/src/entity/doctor_schedule.rs
Normal file
45
crates/erp-health/src/entity/doctor_schedule.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "doctor_schedule")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub doctor_id: Uuid,
|
||||
pub schedule_date: chrono::NaiveDate,
|
||||
pub period_type: String,
|
||||
pub start_time: chrono::NaiveTime,
|
||||
pub end_time: chrono::NaiveTime,
|
||||
pub max_appointments: i32,
|
||||
pub current_appointments: i32,
|
||||
pub status: String,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::doctor_profile::Entity",
|
||||
from = "Column::DoctorId",
|
||||
to = "super::doctor_profile::Column::Id"
|
||||
)]
|
||||
Doctor,
|
||||
}
|
||||
|
||||
impl Related<super::doctor_profile::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Doctor.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
48
crates/erp-health/src/entity/follow_up_record.rs
Normal file
48
crates/erp-health/src/entity/follow_up_record.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "follow_up_record")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub task_id: Uuid,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub executed_by: Option<Uuid>,
|
||||
pub executed_date: chrono::NaiveDate,
|
||||
pub result: String,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub patient_condition: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub medical_advice: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub next_follow_up_date: Option<chrono::NaiveDate>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::follow_up_task::Entity",
|
||||
from = "Column::TaskId",
|
||||
to = "super::follow_up_task::Column::Id"
|
||||
)]
|
||||
Task,
|
||||
}
|
||||
|
||||
impl Related<super::follow_up_task::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Task.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
55
crates/erp-health/src/entity/follow_up_task.rs
Normal file
55
crates/erp-health/src/entity/follow_up_task.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "follow_up_task")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub assigned_to: Option<Uuid>,
|
||||
pub follow_up_type: String,
|
||||
pub planned_date: chrono::NaiveDate,
|
||||
pub status: String,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub content_template: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub related_appointment_id: Option<Uuid>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient::Entity",
|
||||
from = "Column::PatientId",
|
||||
to = "super::patient::Column::Id"
|
||||
)]
|
||||
Patient,
|
||||
#[sea_orm(has_many = "super::follow_up_record::Entity")]
|
||||
Record,
|
||||
}
|
||||
|
||||
impl Related<super::patient::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Patient.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::follow_up_record::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Record.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
48
crates/erp-health/src/entity/health_record.rs
Normal file
48
crates/erp-health/src/entity/health_record.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "health_record")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub record_type: String,
|
||||
pub record_date: chrono::NaiveDate,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub overall_assessment: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub report_file_url: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub notes: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient::Entity",
|
||||
from = "Column::PatientId",
|
||||
to = "super::patient::Column::Id"
|
||||
)]
|
||||
Patient,
|
||||
}
|
||||
|
||||
impl Related<super::patient::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Patient.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
47
crates/erp-health/src/entity/health_trend.rs
Normal file
47
crates/erp-health/src/entity/health_trend.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "health_trend")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub period_start: chrono::NaiveDate,
|
||||
pub period_end: chrono::NaiveDate,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub indicator_summary: Option<serde_json::Value>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub abnormal_items: Option<serde_json::Value>,
|
||||
pub generation_type: String,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub report_file_url: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient::Entity",
|
||||
from = "Column::PatientId",
|
||||
to = "super::patient::Column::Id"
|
||||
)]
|
||||
Patient,
|
||||
}
|
||||
|
||||
impl Related<super::patient::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Patient.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
46
crates/erp-health/src/entity/lab_report.rs
Normal file
46
crates/erp-health/src/entity/lab_report.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "lab_report")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub report_date: chrono::NaiveDate,
|
||||
pub report_type: String,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub indicators: Option<serde_json::Value>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub image_urls: Option<serde_json::Value>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub doctor_interpretation: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient::Entity",
|
||||
from = "Column::PatientId",
|
||||
to = "super::patient::Column::Id"
|
||||
)]
|
||||
Patient,
|
||||
}
|
||||
|
||||
impl Related<super::patient::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Patient.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
16
crates/erp-health/src/entity/mod.rs
Normal file
16
crates/erp-health/src/entity/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
pub mod appointment;
|
||||
pub mod consultation_message;
|
||||
pub mod consultation_session;
|
||||
pub mod doctor_profile;
|
||||
pub mod doctor_schedule;
|
||||
pub mod follow_up_record;
|
||||
pub mod follow_up_task;
|
||||
pub mod health_record;
|
||||
pub mod health_trend;
|
||||
pub mod lab_report;
|
||||
pub mod patient;
|
||||
pub mod patient_doctor_relation;
|
||||
pub mod patient_family_member;
|
||||
pub mod patient_tag;
|
||||
pub mod patient_tag_relation;
|
||||
pub mod vital_signs;
|
||||
74
crates/erp-health/src/entity/patient.rs
Normal file
74
crates/erp-health/src/entity/patient.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "patient")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub user_id: Option<Uuid>,
|
||||
pub name: String,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub gender: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub birth_date: Option<chrono::NaiveDate>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub blood_type: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub id_number: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub allergy_history: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub medical_history_summary: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub emergency_contact_name: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub emergency_contact_phone: Option<String>,
|
||||
pub status: String,
|
||||
pub verification_status: String,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub notes: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::patient_family_member::Entity")]
|
||||
FamilyMember,
|
||||
#[sea_orm(has_many = "super::patient_tag_relation::Entity")]
|
||||
TagRelation,
|
||||
#[sea_orm(has_many = "super::patient_doctor_relation::Entity")]
|
||||
DoctorRelation,
|
||||
#[sea_orm(has_many = "super::health_record::Entity")]
|
||||
HealthRecord,
|
||||
#[sea_orm(has_many = "super::vital_signs::Entity")]
|
||||
VitalSigns,
|
||||
#[sea_orm(has_many = "super::lab_report::Entity")]
|
||||
LabReport,
|
||||
#[sea_orm(has_many = "super::appointment::Entity")]
|
||||
Appointment,
|
||||
#[sea_orm(has_many = "super::follow_up_task::Entity")]
|
||||
FollowUpTask,
|
||||
#[sea_orm(has_many = "super::consultation_session::Entity")]
|
||||
ConsultationSession,
|
||||
}
|
||||
|
||||
impl Related<super::patient_family_member::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::FamilyMember.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
51
crates/erp-health/src/entity/patient_doctor_relation.rs
Normal file
51
crates/erp-health/src/entity/patient_doctor_relation.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "patient_doctor_relation")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub doctor_id: Uuid,
|
||||
pub relationship_type: String,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient::Entity",
|
||||
from = "Column::PatientId",
|
||||
to = "super::patient::Column::Id"
|
||||
)]
|
||||
Patient,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::doctor_profile::Entity",
|
||||
from = "Column::DoctorId",
|
||||
to = "super::doctor_profile::Column::Id"
|
||||
)]
|
||||
Doctor,
|
||||
}
|
||||
|
||||
impl Related<super::patient::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Patient.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::doctor_profile::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Doctor.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
46
crates/erp-health/src/entity/patient_family_member.rs
Normal file
46
crates/erp-health/src/entity/patient_family_member.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "patient_family_member")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub name: String,
|
||||
pub relationship: String,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub phone: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub birth_date: Option<chrono::NaiveDate>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub notes: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient::Entity",
|
||||
from = "Column::PatientId",
|
||||
to = "super::patient::Column::Id"
|
||||
)]
|
||||
Patient,
|
||||
}
|
||||
|
||||
impl Related<super::patient::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Patient.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
39
crates/erp-health/src/entity/patient_tag.rs
Normal file
39
crates/erp-health/src/entity/patient_tag.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "patient_tag")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub name: String,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub color: Option<String>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
pub is_system: bool,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::patient_tag_relation::Entity")]
|
||||
TagRelation,
|
||||
}
|
||||
|
||||
impl Related<super::patient_tag_relation::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::TagRelation.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
50
crates/erp-health/src/entity/patient_tag_relation.rs
Normal file
50
crates/erp-health/src/entity/patient_tag_relation.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "patient_tag_relation")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub tag_id: Uuid,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient::Entity",
|
||||
from = "Column::PatientId",
|
||||
to = "super::patient::Column::Id"
|
||||
)]
|
||||
Patient,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient_tag::Entity",
|
||||
from = "Column::TagId",
|
||||
to = "super::patient_tag::Column::Id"
|
||||
)]
|
||||
Tag,
|
||||
}
|
||||
|
||||
impl Related<super::patient::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Patient.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::patient_tag::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Tag.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
59
crates/erp-health/src/entity/vital_signs.rs
Normal file
59
crates/erp-health/src/entity/vital_signs.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "vital_signs")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub record_date: chrono::NaiveDate,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub systolic_bp_morning: Option<i32>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub diastolic_bp_morning: Option<i32>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub systolic_bp_evening: Option<i32>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub diastolic_bp_evening: Option<i32>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub heart_rate: Option<i32>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub weight: Option<Decimal>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub blood_sugar: Option<Decimal>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub water_intake_ml: Option<i32>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub urine_output_ml: Option<i32>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub notes: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub created_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub updated_by: Option<Uuid>,
|
||||
#[sea_orm(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<DateTimeUtc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::patient::Entity",
|
||||
from = "Column::PatientId",
|
||||
to = "super::patient::Column::Id"
|
||||
)]
|
||||
Patient,
|
||||
}
|
||||
|
||||
impl Related<super::patient::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Patient.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
63
crates/erp-health/src/error.rs
Normal file
63
crates/erp-health/src/error.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use erp_core::error::AppError;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum HealthError {
|
||||
#[error("{0}")]
|
||||
Validation(String),
|
||||
|
||||
#[error("患者不存在")]
|
||||
PatientNotFound,
|
||||
|
||||
#[error("医护档案不存在")]
|
||||
DoctorNotFound,
|
||||
|
||||
#[error("预约不存在")]
|
||||
AppointmentNotFound,
|
||||
|
||||
#[error("排班不存在")]
|
||||
ScheduleNotFound,
|
||||
|
||||
#[error("排班已满,无法预约")]
|
||||
ScheduleFull,
|
||||
|
||||
#[error("随访任务不存在")]
|
||||
FollowUpTaskNotFound,
|
||||
|
||||
#[error("会话不存在")]
|
||||
ConsultationNotFound,
|
||||
|
||||
#[error("状态转换无效: {0}")]
|
||||
InvalidStatusTransition(String),
|
||||
|
||||
#[error("版本冲突: 数据已被其他操作修改,请刷新后重试")]
|
||||
VersionMismatch,
|
||||
|
||||
#[error("数据库操作失败: {0}")]
|
||||
DbError(String),
|
||||
}
|
||||
|
||||
impl From<HealthError> for AppError {
|
||||
fn from(err: HealthError) -> Self {
|
||||
match err {
|
||||
HealthError::Validation(s) => AppError::Validation(s),
|
||||
HealthError::PatientNotFound
|
||||
| HealthError::DoctorNotFound
|
||||
| HealthError::AppointmentNotFound
|
||||
| HealthError::ScheduleNotFound
|
||||
| HealthError::FollowUpTaskNotFound
|
||||
| HealthError::ConsultationNotFound => AppError::NotFound(err.to_string()),
|
||||
HealthError::ScheduleFull => AppError::Validation(err.to_string()),
|
||||
HealthError::InvalidStatusTransition(s) => AppError::Validation(s),
|
||||
HealthError::VersionMismatch => AppError::VersionMismatch,
|
||||
HealthError::DbError(_) => AppError::Internal(err.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sea_orm::DbErr> for HealthError {
|
||||
fn from(err: sea_orm::DbErr) -> Self {
|
||||
HealthError::DbError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub type HealthResult<T> = Result<T, HealthError>;
|
||||
7
crates/erp-health/src/event.rs
Normal file
7
crates/erp-health/src/event.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use erp_core::events::EventBus;
|
||||
|
||||
pub fn register_handlers(_bus: &EventBus) {
|
||||
// Health 模块订阅的事件处理器
|
||||
// - workflow.task.completed → 更新随访任务状态
|
||||
// - message.sent → 联动咨询会话 last_message_at
|
||||
}
|
||||
226
crates/erp-health/src/handler/appointment_handler.rs
Normal file
226
crates/erp-health/src/handler/appointment_handler.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
use axum::Extension;
|
||||
use axum::extract::{FromRef, Json, Path, Query, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::error::AppError;
|
||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DTO — 预约排班
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 预约列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct AppointmentListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub patient_id: Option<Uuid>,
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub status: Option<String>,
|
||||
pub date: Option<String>,
|
||||
}
|
||||
|
||||
/// 创建预约请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateAppointmentReq {
|
||||
pub patient_id: Uuid,
|
||||
pub doctor_id: Uuid,
|
||||
pub schedule_id: Uuid,
|
||||
pub appointment_date: String,
|
||||
pub start_time: String,
|
||||
pub end_time: String,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
/// 更新预约状态请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct UpdateAppointmentStatusReq {
|
||||
pub status: String,
|
||||
pub cancel_reason: Option<String>,
|
||||
}
|
||||
|
||||
/// 预约响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct AppointmentResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub doctor_id: Uuid,
|
||||
pub schedule_id: Uuid,
|
||||
pub appointment_date: String,
|
||||
pub start_time: String,
|
||||
pub end_time: String,
|
||||
pub status: String,
|
||||
pub reason: Option<String>,
|
||||
pub cancel_reason: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
/// 排班列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct ScheduleListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub start_date: Option<String>,
|
||||
pub end_date: Option<String>,
|
||||
}
|
||||
|
||||
/// 创建排班请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateScheduleReq {
|
||||
pub doctor_id: Uuid,
|
||||
pub schedule_date: String,
|
||||
pub start_time: String,
|
||||
pub end_time: String,
|
||||
pub max_appointments: i32,
|
||||
pub slot_duration_minutes: Option<i32>,
|
||||
}
|
||||
|
||||
/// 更新排班请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct UpdateScheduleReq {
|
||||
pub start_time: Option<String>,
|
||||
pub end_time: Option<String>,
|
||||
pub max_appointments: Option<i32>,
|
||||
pub slot_duration_minutes: Option<i32>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
/// 排班响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct ScheduleResp {
|
||||
pub id: Uuid,
|
||||
pub doctor_id: Uuid,
|
||||
pub schedule_date: String,
|
||||
pub start_time: String,
|
||||
pub end_time: String,
|
||||
pub max_appointments: i32,
|
||||
pub current_appointments: i32,
|
||||
pub slot_duration_minutes: Option<i32>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
/// 日历视图查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct CalendarViewParams {
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub start_date: String,
|
||||
pub end_date: String,
|
||||
}
|
||||
|
||||
/// 日历视图单个日期条目
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct CalendarDayEntry {
|
||||
pub date: String,
|
||||
pub schedules: Vec<ScheduleResp>,
|
||||
pub appointments: Vec<AppointmentResp>,
|
||||
}
|
||||
|
||||
/// 日历视图响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct CalendarViewResp {
|
||||
pub days: Vec<CalendarDayEntry>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handler — 预约排班 (7 个端点)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// GET /api/v1/health/appointments — 预约列表
|
||||
pub async fn list_appointments<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<AppointmentListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<AppointmentResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/appointments — 创建预约
|
||||
pub async fn create_appointment<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Json(_req): Json<CreateAppointmentReq>,
|
||||
) -> Result<Json<ApiResponse<AppointmentResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/health/appointments/{id}/status — 更新预约状态
|
||||
pub async fn update_appointment_status<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<UpdateAppointmentStatusReq>,
|
||||
) -> Result<Json<ApiResponse<AppointmentResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/schedules — 排班列表
|
||||
pub async fn list_schedules<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<ScheduleListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<ScheduleResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/schedules — 创建排班
|
||||
pub async fn create_schedule<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Json(_req): Json<CreateScheduleReq>,
|
||||
) -> Result<Json<ApiResponse<ScheduleResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/health/schedules/{id} — 更新排班
|
||||
pub async fn update_schedule<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<UpdateScheduleReq>,
|
||||
) -> Result<Json<ApiResponse<ScheduleResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/calendar — 日历视图
|
||||
pub async fn calendar_view<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<CalendarViewParams>,
|
||||
) -> Result<Json<ApiResponse<CalendarViewResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
142
crates/erp-health/src/handler/consultation_handler.rs
Normal file
142
crates/erp-health/src/handler/consultation_handler.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
use axum::Extension;
|
||||
use axum::extract::{FromRef, Json, Path, Query, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::error::AppError;
|
||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DTO — 咨询管理
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 会话列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct SessionListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub patient_id: Option<Uuid>,
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
/// 会话响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct ConsultationSessionResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub doctor_id: Uuid,
|
||||
pub subject: String,
|
||||
pub status: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
/// 消息列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct MessageListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
}
|
||||
|
||||
/// 创建消息请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateConsultationMessageReq {
|
||||
pub content: String,
|
||||
pub message_type: Option<String>,
|
||||
}
|
||||
|
||||
/// 消息响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct ConsultationMessageResp {
|
||||
pub id: Uuid,
|
||||
pub session_id: Uuid,
|
||||
pub sender_id: Uuid,
|
||||
pub sender_type: String,
|
||||
pub content: String,
|
||||
pub message_type: String,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
/// 导出会话请求
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct ExportSessionsParams {
|
||||
pub patient_id: Option<Uuid>,
|
||||
pub doctor_id: Option<Uuid>,
|
||||
pub start_date: Option<String>,
|
||||
pub end_date: Option<String>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handler — 咨询管理 (5 个端点)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// GET /api/v1/health/consultations/sessions — 会话列表
|
||||
pub async fn list_sessions<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<SessionListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<ConsultationSessionResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/consultations/sessions/{id}/messages — 消息列表
|
||||
pub async fn list_messages<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_session_id): Path<Uuid>,
|
||||
Query(_params): Query<MessageListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<ConsultationMessageResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/health/consultations/sessions/{id}/close — 关闭会话
|
||||
pub async fn close_session<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<ConsultationSessionResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/consultations/sessions/{id}/messages — 创建消息
|
||||
pub async fn create_message<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_session_id): Path<Uuid>,
|
||||
Json(_req): Json<CreateConsultationMessageReq>,
|
||||
) -> Result<Json<ApiResponse<ConsultationMessageResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/consultations/export — 导出会话
|
||||
pub async fn export_sessions<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<ExportSessionsParams>,
|
||||
) -> Result<Json<ApiResponse<Vec<ConsultationSessionResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
136
crates/erp-health/src/handler/doctor_handler.rs
Normal file
136
crates/erp-health/src/handler/doctor_handler.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use axum::Extension;
|
||||
use axum::extract::{FromRef, Json, Path, Query, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::error::AppError;
|
||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DTO — 医护管理
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 医护列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct DoctorListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
/// 按姓名模糊搜索
|
||||
pub search: Option<String>,
|
||||
/// 按科室筛选
|
||||
pub department: Option<String>,
|
||||
/// 按职称筛选
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
/// 创建医护档案请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateDoctorReq {
|
||||
pub user_id: Uuid,
|
||||
pub name: String,
|
||||
pub department: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub specialty: Option<String>,
|
||||
pub license_number: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
}
|
||||
|
||||
/// 更新医护档案请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct UpdateDoctorReq {
|
||||
pub name: Option<String>,
|
||||
pub department: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub specialty: Option<String>,
|
||||
pub license_number: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
/// 医护档案响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct DoctorResp {
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub name: String,
|
||||
pub department: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub specialty: Option<String>,
|
||||
pub license_number: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handler — 医护管理 (5 个端点)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// GET /api/v1/health/doctors — 医护档案列表
|
||||
pub async fn list_doctors<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<DoctorListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<DoctorResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/doctors — 创建医护档案
|
||||
pub async fn create_doctor<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Json(_req): Json<CreateDoctorReq>,
|
||||
) -> Result<Json<ApiResponse<DoctorResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/doctors/{id} — 获取医护档案详情
|
||||
pub async fn get_doctor<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<DoctorResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/health/doctors/{id} — 更新医护档案
|
||||
pub async fn update_doctor<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<UpdateDoctorReq>,
|
||||
) -> Result<Json<ApiResponse<DoctorResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// DELETE /api/v1/health/doctors/{id} — 删除医护档案
|
||||
pub async fn delete_doctor<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
178
crates/erp-health/src/handler/follow_up_handler.rs
Normal file
178
crates/erp-health/src/handler/follow_up_handler.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
use axum::Extension;
|
||||
use axum::extract::{FromRef, Json, Path, Query, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::error::AppError;
|
||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DTO — 随访管理
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 随访任务列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct FollowUpTaskListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub patient_id: Option<Uuid>,
|
||||
pub assigned_to: Option<Uuid>,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
/// 创建随访任务请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateFollowUpTaskReq {
|
||||
pub patient_id: Uuid,
|
||||
pub task_type: String,
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
pub due_date: String,
|
||||
pub assigned_to: Option<Uuid>,
|
||||
}
|
||||
|
||||
/// 更新随访任务请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct UpdateFollowUpTaskReq {
|
||||
pub task_type: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub due_date: Option<String>,
|
||||
pub assigned_to: Option<Uuid>,
|
||||
pub status: Option<String>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
/// 随访任务响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct FollowUpTaskResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub task_type: String,
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
pub due_date: String,
|
||||
pub assigned_to: Option<Uuid>,
|
||||
pub status: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
/// 创建随访记录请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateFollowUpRecordReq {
|
||||
pub task_id: Uuid,
|
||||
pub contact_method: String,
|
||||
pub content: String,
|
||||
pub outcome: Option<String>,
|
||||
pub next_follow_up_date: Option<String>,
|
||||
}
|
||||
|
||||
/// 随访记录列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct FollowUpRecordListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub task_id: Option<Uuid>,
|
||||
pub patient_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
/// 随访记录响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct FollowUpRecordResp {
|
||||
pub id: Uuid,
|
||||
pub task_id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub contact_method: String,
|
||||
pub content: String,
|
||||
pub outcome: Option<String>,
|
||||
pub next_follow_up_date: Option<String>,
|
||||
pub created_by: Uuid,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handler — 随访管理 (6 个端点)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// GET /api/v1/health/follow-up/tasks — 随访任务列表
|
||||
pub async fn list_tasks<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<FollowUpTaskListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<FollowUpTaskResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/follow-up/tasks — 创建随访任务
|
||||
pub async fn create_task<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Json(_req): Json<CreateFollowUpTaskReq>,
|
||||
) -> Result<Json<ApiResponse<FollowUpTaskResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/health/follow-up/tasks/{id} — 更新随访任务
|
||||
pub async fn update_task<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<UpdateFollowUpTaskReq>,
|
||||
) -> Result<Json<ApiResponse<FollowUpTaskResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// DELETE /api/v1/health/follow-up/tasks/{id} — 删除随访任务
|
||||
pub async fn delete_task<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/follow-up/records — 创建随访记录
|
||||
pub async fn create_record<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Json(_req): Json<CreateFollowUpRecordReq>,
|
||||
) -> Result<Json<ApiResponse<FollowUpRecordResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/follow-up/records — 随访记录列表
|
||||
pub async fn list_records<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<FollowUpRecordListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<FollowUpRecordResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
408
crates/erp-health/src/handler/health_data_handler.rs
Normal file
408
crates/erp-health/src/handler/health_data_handler.rs
Normal file
@@ -0,0 +1,408 @@
|
||||
use axum::Extension;
|
||||
use axum::extract::{FromRef, Json, Path, Query, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::error::AppError;
|
||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DTO — 健康数据
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 生命体征列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct VitalSignsListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub patient_id: Uuid,
|
||||
}
|
||||
|
||||
/// 创建生命体征请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateVitalSignsReq {
|
||||
pub patient_id: Uuid,
|
||||
pub blood_pressure_systolic: Option<i32>,
|
||||
pub blood_pressure_diastolic: Option<i32>,
|
||||
pub heart_rate: Option<i32>,
|
||||
pub temperature: Option<f64>,
|
||||
pub blood_oxygen: Option<i32>,
|
||||
pub weight: Option<f64>,
|
||||
pub height: Option<f64>,
|
||||
pub measured_at: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
/// 更新生命体征请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct UpdateVitalSignsReq {
|
||||
pub blood_pressure_systolic: Option<i32>,
|
||||
pub blood_pressure_diastolic: Option<i32>,
|
||||
pub heart_rate: Option<i32>,
|
||||
pub temperature: Option<f64>,
|
||||
pub blood_oxygen: Option<i32>,
|
||||
pub weight: Option<f64>,
|
||||
pub height: Option<f64>,
|
||||
pub measured_at: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
/// 生命体征响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct VitalSignsResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub blood_pressure_systolic: Option<i32>,
|
||||
pub blood_pressure_diastolic: Option<i32>,
|
||||
pub heart_rate: Option<i32>,
|
||||
pub temperature: Option<f64>,
|
||||
pub blood_oxygen: Option<i32>,
|
||||
pub weight: Option<f64>,
|
||||
pub height: Option<f64>,
|
||||
pub measured_at: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
/// 化验报告列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct LabReportListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub patient_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
/// 创建化验报告请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateLabReportReq {
|
||||
pub patient_id: Uuid,
|
||||
pub report_type: String,
|
||||
pub report_date: String,
|
||||
pub indicators: serde_json::Value,
|
||||
pub file_url: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
/// 更新化验报告请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct UpdateLabReportReq {
|
||||
pub report_type: Option<String>,
|
||||
pub report_date: Option<String>,
|
||||
pub indicators: Option<serde_json::Value>,
|
||||
pub file_url: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
/// 化验报告响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct LabReportResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub report_type: String,
|
||||
pub report_date: String,
|
||||
pub indicators: serde_json::Value,
|
||||
pub file_url: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
/// 健康档案列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct HealthRecordListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub patient_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
/// 创建健康档案请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateHealthRecordReq {
|
||||
pub patient_id: Uuid,
|
||||
pub record_type: String,
|
||||
pub title: String,
|
||||
pub content: serde_json::Value,
|
||||
pub record_date: String,
|
||||
}
|
||||
|
||||
/// 更新健康档案请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct UpdateHealthRecordReq {
|
||||
pub record_type: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub content: Option<serde_json::Value>,
|
||||
pub record_date: Option<String>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
/// 健康档案响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct HealthRecordResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub record_type: String,
|
||||
pub title: String,
|
||||
pub content: serde_json::Value,
|
||||
pub record_date: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
/// 趋势分析列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct TrendListParams {
|
||||
pub patient_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
/// 生成趋势请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct GenerateTrendReq {
|
||||
pub patient_id: Uuid,
|
||||
pub indicator_name: String,
|
||||
pub start_date: String,
|
||||
pub end_date: String,
|
||||
}
|
||||
|
||||
/// 趋势分析响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct TrendResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub indicator_name: String,
|
||||
pub trend_data: serde_json::Value,
|
||||
pub analysis_summary: Option<String>,
|
||||
pub generated_at: String,
|
||||
}
|
||||
|
||||
/// 指标时间序列查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct IndicatorTimeseriesParams {
|
||||
pub patient_id: Uuid,
|
||||
pub indicator_name: String,
|
||||
pub start_date: Option<String>,
|
||||
pub end_date: Option<String>,
|
||||
}
|
||||
|
||||
/// 指标时间序列数据点
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct TimeseriesDataPoint {
|
||||
pub date: String,
|
||||
pub value: f64,
|
||||
pub unit: Option<String>,
|
||||
}
|
||||
|
||||
/// 指标时间序列响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct IndicatorTimeseriesResp {
|
||||
pub indicator_name: String,
|
||||
pub patient_id: Uuid,
|
||||
pub data_points: Vec<TimeseriesDataPoint>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handler — 健康数据 (15 个端点)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// GET /api/v1/health/vital-signs — 生命体征列表
|
||||
pub async fn list_vital_signs<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<VitalSignsListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<VitalSignsResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/vital-signs — 创建生命体征记录
|
||||
pub async fn create_vital_signs<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Json(_req): Json<CreateVitalSignsReq>,
|
||||
) -> Result<Json<ApiResponse<VitalSignsResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/health/vital-signs/{id} — 更新生命体征记录
|
||||
pub async fn update_vital_signs<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<UpdateVitalSignsReq>,
|
||||
) -> Result<Json<ApiResponse<VitalSignsResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// DELETE /api/v1/health/vital-signs/{id} — 删除生命体征记录
|
||||
pub async fn delete_vital_signs<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/lab-reports — 化验报告列表
|
||||
pub async fn list_lab_reports<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<LabReportListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<LabReportResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/lab-reports — 创建化验报告
|
||||
pub async fn create_lab_report<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Json(_req): Json<CreateLabReportReq>,
|
||||
) -> Result<Json<ApiResponse<LabReportResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/health/lab-reports/{id} — 更新化验报告
|
||||
pub async fn update_lab_report<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<UpdateLabReportReq>,
|
||||
) -> Result<Json<ApiResponse<LabReportResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// DELETE /api/v1/health/lab-reports/{id} — 删除化验报告
|
||||
pub async fn delete_lab_report<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/records — 健康档案列表
|
||||
pub async fn list_health_records<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<HealthRecordListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<HealthRecordResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/records — 创建健康档案
|
||||
pub async fn create_health_record<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Json(_req): Json<CreateHealthRecordReq>,
|
||||
) -> Result<Json<ApiResponse<HealthRecordResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/health/records/{id} — 更新健康档案
|
||||
pub async fn update_health_record<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<UpdateHealthRecordReq>,
|
||||
) -> Result<Json<ApiResponse<HealthRecordResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// DELETE /api/v1/health/records/{id} — 删除健康档案
|
||||
pub async fn delete_health_record<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/trends — 趋势分析列表
|
||||
pub async fn list_trends<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<TrendListParams>,
|
||||
) -> Result<Json<ApiResponse<Vec<TrendResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/trends/generate — 生成趋势分析
|
||||
pub async fn generate_trend<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Json(_req): Json<GenerateTrendReq>,
|
||||
) -> Result<Json<ApiResponse<TrendResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/trends/timeseries — 获取指标时间序列
|
||||
pub async fn get_indicator_timeseries<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<IndicatorTimeseriesParams>,
|
||||
) -> Result<Json<ApiResponse<IndicatorTimeseriesResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
6
crates/erp-health/src/handler/mod.rs
Normal file
6
crates/erp-health/src/handler/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod appointment_handler;
|
||||
pub mod consultation_handler;
|
||||
pub mod doctor_handler;
|
||||
pub mod follow_up_handler;
|
||||
pub mod health_data_handler;
|
||||
pub mod patient_handler;
|
||||
311
crates/erp-health/src/handler/patient_handler.rs
Normal file
311
crates/erp-health/src/handler/patient_handler.rs
Normal file
@@ -0,0 +1,311 @@
|
||||
use axum::Extension;
|
||||
use axum::extract::{FromRef, Json, Path, Query, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::error::AppError;
|
||||
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
||||
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DTO — 患者管理
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 患者列表查询参数
|
||||
#[derive(Debug, Deserialize, IntoParams)]
|
||||
pub struct PatientListParams {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
/// 按姓名/身份证号模糊搜索
|
||||
pub search: Option<String>,
|
||||
/// 按标签筛选
|
||||
pub tag_id: Option<Uuid>,
|
||||
/// 按负责医生筛选
|
||||
pub doctor_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
/// 创建患者请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreatePatientReq {
|
||||
pub name: String,
|
||||
pub id_card: Option<String>,
|
||||
pub phone: Option<String>,
|
||||
pub gender: Option<String>,
|
||||
pub birth_date: Option<String>,
|
||||
pub address: Option<String>,
|
||||
pub emergency_contact: Option<String>,
|
||||
pub emergency_phone: Option<String>,
|
||||
pub medical_notes: Option<String>,
|
||||
}
|
||||
|
||||
/// 更新患者请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct UpdatePatientReq {
|
||||
pub name: Option<String>,
|
||||
pub id_card: Option<String>,
|
||||
pub phone: Option<String>,
|
||||
pub gender: Option<String>,
|
||||
pub birth_date: Option<String>,
|
||||
pub address: Option<String>,
|
||||
pub emergency_contact: Option<String>,
|
||||
pub emergency_phone: Option<String>,
|
||||
pub medical_notes: Option<String>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
/// 患者标签管理请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct ManagePatientTagsReq {
|
||||
pub tag_ids: Vec<Uuid>,
|
||||
}
|
||||
|
||||
/// 患者响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct PatientResp {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub id_card: Option<String>,
|
||||
pub phone: Option<String>,
|
||||
pub gender: Option<String>,
|
||||
pub birth_date: Option<String>,
|
||||
pub address: Option<String>,
|
||||
pub emergency_contact: Option<String>,
|
||||
pub emergency_phone: Option<String>,
|
||||
pub medical_notes: Option<String>,
|
||||
pub tags: Vec<PatientTagResp>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
/// 患者标签响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct PatientTagResp {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
/// 健康摘要响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct HealthSummaryResp {
|
||||
pub patient_id: Uuid,
|
||||
pub latest_vital_signs: Option<serde_json::Value>,
|
||||
pub latest_lab_report: Option<serde_json::Value>,
|
||||
pub upcoming_appointments: u64,
|
||||
pub pending_follow_ups: u64,
|
||||
}
|
||||
|
||||
/// 家庭成员请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateFamilyMemberReq {
|
||||
pub name: String,
|
||||
pub relationship: String,
|
||||
pub phone: Option<String>,
|
||||
pub id_card: Option<String>,
|
||||
}
|
||||
|
||||
/// 更新家庭成员请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct UpdateFamilyMemberReq {
|
||||
pub name: Option<String>,
|
||||
pub relationship: Option<String>,
|
||||
pub phone: Option<String>,
|
||||
pub id_card: Option<String>,
|
||||
}
|
||||
|
||||
/// 家庭成员响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct FamilyMemberResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub name: String,
|
||||
pub relationship: String,
|
||||
pub phone: Option<String>,
|
||||
pub id_card: Option<String>,
|
||||
}
|
||||
|
||||
/// 分配医生请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct AssignDoctorReq {
|
||||
pub doctor_id: Uuid,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handler — 患者管理 (13 个端点)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// GET /api/v1/health/patients — 患者列表
|
||||
pub async fn list_patients<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Query(_params): Query<PatientListParams>,
|
||||
) -> Result<Json<ApiResponse<PaginatedResponse<PatientResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/patients — 创建患者
|
||||
pub async fn create_patient<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Json(_req): Json<CreatePatientReq>,
|
||||
) -> Result<Json<ApiResponse<PatientResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/patients/{id} — 获取患者详情
|
||||
pub async fn get_patient<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<PatientResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/health/patients/{id} — 更新患者
|
||||
pub async fn update_patient<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<UpdatePatientReq>,
|
||||
) -> Result<Json<ApiResponse<PatientResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// DELETE /api/v1/health/patients/{id} — 删除患者(软删除)
|
||||
pub async fn delete_patient<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/patients/{id}/tags — 管理患者标签
|
||||
pub async fn manage_patient_tags<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<ManagePatientTagsReq>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/patients/{id}/health-summary — 获取患者健康摘要
|
||||
pub async fn get_health_summary<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<HealthSummaryResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// GET /api/v1/health/patients/{id}/family-members — 家庭成员列表
|
||||
pub async fn list_family_members<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
) -> Result<Json<ApiResponse<Vec<FamilyMemberResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/patients/{id}/family-members — 创建家庭成员
|
||||
pub async fn create_family_member<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<CreateFamilyMemberReq>,
|
||||
) -> Result<Json<ApiResponse<FamilyMemberResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// PUT /api/v1/health/patients/{patient_id}/family-members/{member_id} — 更新家庭成员
|
||||
pub async fn update_family_member<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path((_patient_id, _member_id)): Path<(Uuid, Uuid)>,
|
||||
Json(_req): Json<UpdateFamilyMemberReq>,
|
||||
) -> Result<Json<ApiResponse<FamilyMemberResp>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// DELETE /api/v1/health/patients/{patient_id}/family-members/{member_id} — 删除家庭成员
|
||||
pub async fn delete_family_member<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path((_patient_id, _member_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// POST /api/v1/health/patients/{id}/doctors — 分配负责医生
|
||||
pub async fn assign_doctor<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path(_id): Path<Uuid>,
|
||||
Json(_req): Json<AssignDoctorReq>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
|
||||
/// DELETE /api/v1/health/patients/{patient_id}/doctors/{doctor_id} — 移除负责医生
|
||||
pub async fn remove_doctor<S>(
|
||||
State(_state): State<HealthState>,
|
||||
Extension(_ctx): Extension<TenantContext>,
|
||||
Path((_patient_id, _doctor_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<ApiResponse<()>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Err(AppError::Internal("Not implemented yet".into()))
|
||||
}
|
||||
11
crates/erp-health/src/lib.rs
Normal file
11
crates/erp-health/src/lib.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
pub mod dto;
|
||||
pub mod entity;
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
pub mod handler;
|
||||
pub mod module;
|
||||
pub mod service;
|
||||
pub mod state;
|
||||
|
||||
pub use module::HealthModule;
|
||||
pub use state::HealthState;
|
||||
322
crates/erp-health/src/module.rs
Normal file
322
crates/erp-health/src/module.rs
Normal 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
|
||||
}
|
||||
}
|
||||
120
crates/erp-health/src/service/appointment_service.rs
Normal file
120
crates/erp-health/src/service/appointment_service.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
//! 预约排班 Service — 预约CRUD、排班管理、日历视图、原子CAS预约
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::types::{PaginatedResponse, Pagination};
|
||||
|
||||
use crate::dto::appointment_dto::{
|
||||
AppointmentResp, CalendarDayResp, CreateAppointmentReq, CreateScheduleReq,
|
||||
ScheduleResp, UpdateAppointmentStatusReq, UpdateScheduleReq,
|
||||
};
|
||||
use crate::error::HealthResult;
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 预约管理 (Appointments)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 预约列表(分页 + 多条件筛选)
|
||||
pub async fn list_appointments(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
pagination: Pagination,
|
||||
status: Option<String>,
|
||||
patient_id: Option<Uuid>,
|
||||
doctor_id: Option<Uuid>,
|
||||
date: Option<NaiveDate>,
|
||||
) -> HealthResult<PaginatedResponse<AppointmentResp>> {
|
||||
let _ = (state, tenant_id, pagination, status, patient_id, doctor_id, date);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 创建预约(原子 CAS 占位,防止超额预约)
|
||||
pub async fn create_appointment(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
req: CreateAppointmentReq,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<AppointmentResp> {
|
||||
let _ = (state, tenant_id, req, user_id);
|
||||
// 实现时需要:
|
||||
// 1. 查找对应排班档位
|
||||
// 2. 原子 CAS: UPDATE doctor_schedule SET current_appointments = current_appointments + 1
|
||||
// WHERE id = ? AND current_appointments < max_appointments
|
||||
// 3. CAS 失败返回 ScheduleFull 错误
|
||||
// 4. 创建预约记录
|
||||
// 5. 发布 appointment.created 事件
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 更新预约状态(确认/取消/完成/未到)
|
||||
pub async fn update_appointment_status(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
appointment_id: Uuid,
|
||||
req: UpdateAppointmentStatusReq,
|
||||
version: i32,
|
||||
) -> HealthResult<AppointmentResp> {
|
||||
let _ = (state, tenant_id, appointment_id, req, version);
|
||||
// 实现时需要:
|
||||
// 1. 状态机校验:pending -> confirmed/cancelled, confirmed -> completed/no_show/cancelled
|
||||
// 2. 取消时释放排班名额(原子减 1)
|
||||
// 3. 发布 appointment.confirmed / appointment.cancelled 事件
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 排班管理 (Doctor Schedules)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 排班列表
|
||||
pub async fn list_schedules(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
pagination: Pagination,
|
||||
doctor_id: Option<Uuid>,
|
||||
date: Option<NaiveDate>,
|
||||
) -> HealthResult<PaginatedResponse<ScheduleResp>> {
|
||||
let _ = (state, tenant_id, pagination, doctor_id, date);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 创建排班
|
||||
pub async fn create_schedule(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
req: CreateScheduleReq,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<ScheduleResp> {
|
||||
let _ = (state, tenant_id, req, user_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 更新排班(乐观锁)
|
||||
pub async fn update_schedule(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
schedule_id: Uuid,
|
||||
req: UpdateScheduleReq,
|
||||
version: i32,
|
||||
) -> HealthResult<ScheduleResp> {
|
||||
let _ = (state, tenant_id, schedule_id, req, version);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 日历视图
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 日历视图(按日期范围返回每天的排班汇总)
|
||||
pub async fn calendar_view(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
start_date: NaiveDate,
|
||||
end_date: NaiveDate,
|
||||
doctor_id: Option<Uuid>,
|
||||
) -> HealthResult<Vec<CalendarDayResp>> {
|
||||
let _ = (state, tenant_id, start_date, end_date, doctor_id);
|
||||
todo!()
|
||||
}
|
||||
83
crates/erp-health/src/service/consultation_service.rs
Normal file
83
crates/erp-health/src/service/consultation_service.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
//! 咨询管理 Service — 会话管理、消息收发、会话关闭、导出
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::types::{PaginatedResponse, Pagination};
|
||||
|
||||
use crate::dto::consultation_dto::{
|
||||
CreateMessageReq, MessageResp, SessionQuery, SessionResp,
|
||||
};
|
||||
use crate::error::HealthResult;
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 咨询会话 (Consultation Sessions)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 咨询会话列表(分页 + 多条件筛选)
|
||||
pub async fn list_sessions(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
query: SessionQuery,
|
||||
) -> HealthResult<PaginatedResponse<SessionResp>> {
|
||||
let _ = (state, tenant_id, query);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 关闭咨询会话
|
||||
pub async fn close_session(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
session_id: Uuid,
|
||||
version: i32,
|
||||
) -> HealthResult<SessionResp> {
|
||||
let _ = (state, tenant_id, session_id, version);
|
||||
// 实现时需要:
|
||||
// 1. 校验会话存在且状态为 active
|
||||
// 2. 更新状态为 closed
|
||||
// 3. 发布 consultation.closed 事件
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 导出咨询会话(按条件筛选后返回汇总数据)
|
||||
pub async fn export_sessions(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
status: Option<String>,
|
||||
patient_id: Option<Uuid>,
|
||||
doctor_id: Option<Uuid>,
|
||||
) -> HealthResult<Vec<SessionResp>> {
|
||||
let _ = (state, tenant_id, status, patient_id, doctor_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 咨询消息 (Consultation Messages)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 消息列表(按会话 ID 查询,分页)
|
||||
pub async fn list_messages(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
session_id: Uuid,
|
||||
pagination: Pagination,
|
||||
) -> HealthResult<PaginatedResponse<MessageResp>> {
|
||||
let _ = (state, tenant_id, session_id, pagination);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 发送消息
|
||||
pub async fn create_message(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
req: CreateMessageReq,
|
||||
) -> HealthResult<MessageResp> {
|
||||
let _ = (state, tenant_id, req);
|
||||
// 实现时需要:
|
||||
// 1. 校验会话存在且状态为 active
|
||||
// 2. 创建消息记录
|
||||
// 3. 更新会话的 last_message_at
|
||||
// 4. 根据发送者角色更新对方的 unread_count
|
||||
// 5. 发布 consultation.message.created 事件
|
||||
todo!()
|
||||
}
|
||||
161
crates/erp-health/src/service/follow_up_service.rs
Normal file
161
crates/erp-health/src/service/follow_up_service.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
//! 随访管理 Service — 随访任务CRUD、随访记录、状态流转
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::types::{PaginatedResponse, Pagination};
|
||||
|
||||
use crate::error::{HealthError, HealthResult};
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 随访任务 DTO(内部使用,follow_up_dto 尚未创建独立文件)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 创建随访任务请求
|
||||
#[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct CreateFollowUpTaskReq {
|
||||
pub patient_id: Uuid,
|
||||
pub assigned_to: Option<Uuid>,
|
||||
pub follow_up_type: String,
|
||||
pub planned_date: NaiveDate,
|
||||
pub content_template: Option<String>,
|
||||
pub related_appointment_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
/// 更新随访任务请求
|
||||
#[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct UpdateFollowUpTaskReq {
|
||||
pub assigned_to: Option<Uuid>,
|
||||
pub follow_up_type: Option<String>,
|
||||
pub planned_date: Option<NaiveDate>,
|
||||
pub content_template: Option<String>,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
/// 随访任务响应
|
||||
#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
|
||||
pub struct FollowUpTaskResp {
|
||||
pub id: Uuid,
|
||||
pub patient_id: Uuid,
|
||||
pub assigned_to: Option<Uuid>,
|
||||
pub follow_up_type: String,
|
||||
pub planned_date: NaiveDate,
|
||||
pub status: String,
|
||||
pub content_template: Option<String>,
|
||||
pub related_appointment_id: Option<Uuid>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
/// 创建随访记录请求
|
||||
#[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct CreateFollowUpRecordReq {
|
||||
pub task_id: Uuid,
|
||||
pub executed_by: Option<Uuid>,
|
||||
pub executed_date: NaiveDate,
|
||||
pub result: String,
|
||||
pub patient_condition: Option<String>,
|
||||
pub medical_advice: Option<String>,
|
||||
pub next_follow_up_date: Option<NaiveDate>,
|
||||
}
|
||||
|
||||
/// 随访记录响应
|
||||
#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
|
||||
pub struct FollowUpRecordResp {
|
||||
pub id: Uuid,
|
||||
pub task_id: Uuid,
|
||||
pub executed_by: Option<Uuid>,
|
||||
pub executed_date: NaiveDate,
|
||||
pub result: String,
|
||||
pub patient_condition: Option<String>,
|
||||
pub medical_advice: Option<String>,
|
||||
pub next_follow_up_date: Option<NaiveDate>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 随访任务
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 随访任务列表(分页 + 多条件筛选)
|
||||
pub async fn list_tasks(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
pagination: Pagination,
|
||||
patient_id: Option<Uuid>,
|
||||
assigned_to: Option<Uuid>,
|
||||
status: Option<String>,
|
||||
) -> HealthResult<PaginatedResponse<FollowUpTaskResp>> {
|
||||
let _ = (state, tenant_id, pagination, patient_id, assigned_to, status);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 创建随访任务
|
||||
pub async fn create_task(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
req: CreateFollowUpTaskReq,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<FollowUpTaskResp> {
|
||||
let _ = (state, tenant_id, req, user_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 更新随访任务(乐观锁)
|
||||
pub async fn update_task(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
task_id: Uuid,
|
||||
req: UpdateFollowUpTaskReq,
|
||||
version: i32,
|
||||
) -> HealthResult<FollowUpTaskResp> {
|
||||
let _ = (state, tenant_id, task_id, req, version);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 删除随访任务(软删除)
|
||||
pub async fn delete_task(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
task_id: Uuid,
|
||||
) -> HealthResult<()> {
|
||||
let _ = (state, tenant_id, task_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 随访记录
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 创建随访执行记录(同时将任务状态推进为 completed)
|
||||
pub async fn create_record(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
req: CreateFollowUpRecordReq,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<FollowUpRecordResp> {
|
||||
let _ = (state, tenant_id, req, user_id);
|
||||
// 实现时需要:
|
||||
// 1. 校验任务存在且状态为 in_progress / pending
|
||||
// 2. 创建随访记录
|
||||
// 3. 更新任务状态为 completed
|
||||
// 4. 如果设置了 next_follow_up_date,自动创建下一个随访任务
|
||||
// 5. 发布 follow_up.completed 事件
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 随访记录列表(分页)
|
||||
pub async fn list_records(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
pagination: Pagination,
|
||||
task_id: Option<Uuid>,
|
||||
patient_id: Option<Uuid>,
|
||||
) -> HealthResult<PaginatedResponse<FollowUpRecordResp>> {
|
||||
let _ = (state, tenant_id, pagination, task_id, patient_id);
|
||||
todo!()
|
||||
}
|
||||
207
crates/erp-health/src/service/health_data_service.rs
Normal file
207
crates/erp-health/src/service/health_data_service.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
//! 健康数据 Service — 体征记录、化验报告、体检记录、趋势分析
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::types::{PaginatedResponse, Pagination};
|
||||
|
||||
use crate::dto::health_data_dto::{
|
||||
CreateHealthRecordReq, CreateLabReportReq, CreateVitalSignsReq, HealthRecordResp,
|
||||
IndicatorTimeseriesResp, LabReportResp, TrendResp, UpdateVitalSignsReq,
|
||||
};
|
||||
use crate::error::HealthResult;
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 体征记录 (Vital Signs)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 体征记录列表
|
||||
pub async fn list_vital_signs(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
pagination: Pagination,
|
||||
) -> HealthResult<PaginatedResponse<crate::dto::health_data_dto::VitalSignsResp>> {
|
||||
let _ = (state, tenant_id, patient_id, pagination);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 创建体征记录
|
||||
pub async fn create_vital_signs(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
req: CreateVitalSignsReq,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<crate::dto::health_data_dto::VitalSignsResp> {
|
||||
let _ = (state, tenant_id, patient_id, req, user_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 更新体征记录(乐观锁)
|
||||
pub async fn update_vital_signs(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
vital_signs_id: Uuid,
|
||||
req: UpdateVitalSignsReq,
|
||||
version: i32,
|
||||
) -> HealthResult<crate::dto::health_data_dto::VitalSignsResp> {
|
||||
let _ = (state, tenant_id, patient_id, vital_signs_id, req, version);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 删除体征记录
|
||||
pub async fn delete_vital_signs(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
vital_signs_id: Uuid,
|
||||
) -> HealthResult<()> {
|
||||
let _ = (state, tenant_id, patient_id, vital_signs_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 化验报告 (Lab Reports)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 化验报告列表
|
||||
pub async fn list_lab_reports(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
pagination: Pagination,
|
||||
) -> HealthResult<PaginatedResponse<LabReportResp>> {
|
||||
let _ = (state, tenant_id, patient_id, pagination);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 创建化验报告
|
||||
pub async fn create_lab_report(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
req: CreateLabReportReq,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<LabReportResp> {
|
||||
let _ = (state, tenant_id, patient_id, req, user_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 更新化验报告(乐观锁)
|
||||
pub async fn update_lab_report(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
report_id: Uuid,
|
||||
req: CreateLabReportReq,
|
||||
version: i32,
|
||||
) -> HealthResult<LabReportResp> {
|
||||
let _ = (state, tenant_id, patient_id, report_id, req, version);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 删除化验报告
|
||||
pub async fn delete_lab_report(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
report_id: Uuid,
|
||||
) -> HealthResult<()> {
|
||||
let _ = (state, tenant_id, patient_id, report_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 体检记录 (Health Records)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 体检记录列表
|
||||
pub async fn list_health_records(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
pagination: Pagination,
|
||||
) -> HealthResult<PaginatedResponse<HealthRecordResp>> {
|
||||
let _ = (state, tenant_id, patient_id, pagination);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 创建体检记录
|
||||
pub async fn create_health_record(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
req: CreateHealthRecordReq,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<HealthRecordResp> {
|
||||
let _ = (state, tenant_id, patient_id, req, user_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 更新体检记录(乐观锁)
|
||||
pub async fn update_health_record(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
record_id: Uuid,
|
||||
req: CreateHealthRecordReq,
|
||||
version: i32,
|
||||
) -> HealthResult<HealthRecordResp> {
|
||||
let _ = (state, tenant_id, patient_id, record_id, req, version);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 删除体检记录
|
||||
pub async fn delete_health_record(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
record_id: Uuid,
|
||||
) -> HealthResult<()> {
|
||||
let _ = (state, tenant_id, patient_id, record_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 趋势分析 (Trends)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 趋势列表
|
||||
pub async fn list_trends(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
pagination: Pagination,
|
||||
) -> HealthResult<PaginatedResponse<TrendResp>> {
|
||||
let _ = (state, tenant_id, patient_id, pagination);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 生成趋势分析报告(基于历史体征 + 化验数据聚合)
|
||||
pub async fn generate_trend(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
period_start: NaiveDate,
|
||||
period_end: NaiveDate,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<TrendResp> {
|
||||
let _ = (state, tenant_id, patient_id, period_start, period_end, user_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 获取单个指标的时间序列数据
|
||||
pub async fn get_indicator_timeseries(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
indicator: String,
|
||||
start_date: Option<NaiveDate>,
|
||||
end_date: Option<NaiveDate>,
|
||||
) -> HealthResult<IndicatorTimeseriesResp> {
|
||||
let _ = (state, tenant_id, patient_id, indicator, start_date, end_date);
|
||||
todo!()
|
||||
}
|
||||
6
crates/erp-health/src/service/mod.rs
Normal file
6
crates/erp-health/src/service/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod appointment_service;
|
||||
pub mod consultation_service;
|
||||
pub mod follow_up_service;
|
||||
pub mod health_data_service;
|
||||
pub mod patient_service;
|
||||
pub mod seed;
|
||||
179
crates/erp-health/src/service/patient_service.rs
Normal file
179
crates/erp-health/src/service/patient_service.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
//! 患者管理 Service — 患者CRUD、家庭成员、标签、医生关联、健康摘要
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use erp_core::types::{PaginatedResponse, Pagination};
|
||||
|
||||
use crate::dto::patient_dto::{
|
||||
CreatePatientReq, FamilyMemberReq, FamilyMemberResp, ManageTagsReq, PatientResp,
|
||||
UpdatePatientReq,
|
||||
};
|
||||
use crate::error::HealthResult;
|
||||
use crate::state::HealthState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 患者 CRUD
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 患者列表(分页 + 搜索 + 标签筛选)
|
||||
pub async fn list_patients(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
pagination: Pagination,
|
||||
search: Option<String>,
|
||||
tag_id: Option<Uuid>,
|
||||
) -> HealthResult<PaginatedResponse<PatientResp>> {
|
||||
let _ = (state, tenant_id, pagination, search, tag_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 创建患者
|
||||
pub async fn create_patient(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
user_id: Option<Uuid>,
|
||||
req: CreatePatientReq,
|
||||
) -> HealthResult<PatientResp> {
|
||||
let _ = (state, tenant_id, user_id, req);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 获取患者详情
|
||||
pub async fn get_patient(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
id: Uuid,
|
||||
) -> HealthResult<PatientResp> {
|
||||
let _ = (state, tenant_id, id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 更新患者信息(乐观锁)
|
||||
pub async fn update_patient(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
id: Uuid,
|
||||
req: UpdatePatientReq,
|
||||
version: i32,
|
||||
) -> HealthResult<PatientResp> {
|
||||
let _ = (state, tenant_id, id, req, version);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 软删除患者
|
||||
pub async fn delete_patient(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
id: Uuid,
|
||||
) -> HealthResult<()> {
|
||||
let _ = (state, tenant_id, id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 标签管理
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 管理患者标签(覆盖式:传入的 tag_ids 替换当前关联)
|
||||
pub async fn manage_patient_tags(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
req: ManageTagsReq,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<()> {
|
||||
let _ = (state, tenant_id, patient_id, req, user_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 健康摘要
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 获取患者健康摘要(最新体征 + 最新化验 + 待处理预约 + 待办随访)
|
||||
pub async fn get_health_summary(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
) -> HealthResult<serde_json::Value> {
|
||||
let _ = (state, tenant_id, patient_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 家庭成员
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 家庭成员列表
|
||||
pub async fn list_family_members(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
) -> HealthResult<Vec<FamilyMemberResp>> {
|
||||
let _ = (state, tenant_id, patient_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 创建家庭成员
|
||||
pub async fn create_family_member(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
req: FamilyMemberReq,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<FamilyMemberResp> {
|
||||
let _ = (state, tenant_id, patient_id, req, user_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 更新家庭成员(乐观锁)
|
||||
pub async fn update_family_member(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
family_member_id: Uuid,
|
||||
req: FamilyMemberReq,
|
||||
version: i32,
|
||||
) -> HealthResult<FamilyMemberResp> {
|
||||
let _ = (state, tenant_id, patient_id, family_member_id, req, version);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 删除家庭成员
|
||||
pub async fn delete_family_member(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
family_member_id: Uuid,
|
||||
) -> HealthResult<()> {
|
||||
let _ = (state, tenant_id, patient_id, family_member_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 患者-医生关联
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// 分配负责医生
|
||||
pub async fn assign_doctor(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
doctor_id: Uuid,
|
||||
relationship_type: String,
|
||||
user_id: Option<Uuid>,
|
||||
) -> HealthResult<()> {
|
||||
let _ = (state, tenant_id, patient_id, doctor_id, relationship_type, user_id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// 移除负责医生
|
||||
pub async fn remove_doctor(
|
||||
state: &HealthState,
|
||||
tenant_id: Uuid,
|
||||
patient_id: Uuid,
|
||||
doctor_id: Uuid,
|
||||
) -> HealthResult<()> {
|
||||
let _ = (state, tenant_id, patient_id, doctor_id);
|
||||
todo!()
|
||||
}
|
||||
22
crates/erp-health/src/service/seed.rs
Normal file
22
crates/erp-health/src/service/seed.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
//! 租户初始化种子数据 — 创建默认标签、默认排班模板等
|
||||
|
||||
use sea_orm::DatabaseConnection;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// 初始化租户健康模块默认数据
|
||||
pub async fn seed_tenant_health(
|
||||
_db: &DatabaseConnection,
|
||||
tenant_id: Uuid,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
tracing::info!(tenant_id = %tenant_id, "Seeding health module default data");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 软删除该租户下所有健康模块数据
|
||||
pub async fn soft_delete_tenant_data(
|
||||
_db: &DatabaseConnection,
|
||||
tenant_id: Uuid,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
tracing::info!(tenant_id = %tenant_id, "Soft-deleting health module data for tenant");
|
||||
Ok(())
|
||||
}
|
||||
8
crates/erp-health/src/state.rs
Normal file
8
crates/erp-health/src/state.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use erp_core::events::EventBus;
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HealthState {
|
||||
pub db: DatabaseConnection,
|
||||
pub event_bus: EventBus,
|
||||
}
|
||||
@@ -27,6 +27,7 @@ erp-config.workspace = true
|
||||
erp-workflow.workspace = true
|
||||
erp-message.workspace = true
|
||||
erp-plugin.workspace = true
|
||||
erp-health.workspace = true
|
||||
anyhow.workspace = true
|
||||
uuid.workspace = true
|
||||
chrono.workspace = true
|
||||
|
||||
@@ -41,6 +41,7 @@ mod m20260419_000038_fix_crm_permission_codes;
|
||||
mod m20260419_000039_entity_registry_columns;
|
||||
mod m20260419_000040_plugin_market;
|
||||
mod m20260419_000041_plugin_user_views;
|
||||
mod m20260423_000042_create_health_tables;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -89,6 +90,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260419_000039_entity_registry_columns::Migration),
|
||||
Box::new(m20260419_000040_plugin_market::Migration),
|
||||
Box::new(m20260419_000041_plugin_user_views::Migration),
|
||||
Box::new(m20260423_000042_create_health_tables::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -319,12 +319,21 @@ async fn main() -> anyhow::Result<()> {
|
||||
"Message module initialized"
|
||||
);
|
||||
|
||||
// Initialize health module
|
||||
let health_module = erp_health::HealthModule::new();
|
||||
tracing::info!(
|
||||
module = health_module.name(),
|
||||
version = health_module.version(),
|
||||
"Health module initialized"
|
||||
);
|
||||
|
||||
// Initialize module registry and register modules
|
||||
let registry = ModuleRegistry::new()
|
||||
.register(auth_module)
|
||||
.register(config_module)
|
||||
.register(workflow_module)
|
||||
.register(message_module);
|
||||
.register(message_module)
|
||||
.register(health_module);
|
||||
tracing::info!(
|
||||
module_count = registry.modules().len(),
|
||||
"Modules registered"
|
||||
@@ -431,6 +440,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.merge(erp_workflow::WorkflowModule::protected_routes())
|
||||
.merge(erp_message::MessageModule::protected_routes())
|
||||
.merge(erp_plugin::module::PluginModule::protected_routes())
|
||||
.merge(erp_health::HealthModule::protected_routes())
|
||||
.merge(handlers::audit_log::audit_log_router())
|
||||
.layer(axum::middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
|
||||
@@ -96,3 +96,13 @@ impl FromRef<AppState> for erp_plugin::state::PluginState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow erp-health handlers to extract their required state.
|
||||
impl FromRef<AppState> for erp_health::HealthState {
|
||||
fn from_ref(state: &AppState) -> Self {
|
||||
Self {
|
||||
db: state.db.clone(),
|
||||
event_bus: state.event_bus.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user