P0: consultation handler sender_role 从请求体移除,改为服务端推导(防伪造) P1: 所有软删除操作统一使用 check_version 乐观锁(6个函数) P1: 修复 health_trend 索引缺少 tenant_id 前导列 + follow_up_record 补 (tenant_id, executed_date) 索引 P2: Decimal->f64 使用 ToPrimitive::to_f64 替代脆弱的 to_string().parse() P2: 预约取消释放槽位+状态更新包裹进同一事务
307 lines
9.4 KiB
Rust
307 lines
9.4 KiB
Rust
use axum::Extension;
|
|
use axum::extract::{FromRef, Json, Path, Query, State};
|
|
use serde::Deserialize;
|
|
use utoipa::IntoParams;
|
|
use uuid::Uuid;
|
|
|
|
use erp_core::error::AppError;
|
|
use erp_core::rbac::require_permission;
|
|
use erp_core::types::{ApiResponse, PaginatedResponse, TenantContext};
|
|
|
|
use crate::dto::patient_dto::{
|
|
CreatePatientReq, FamilyMemberReq, FamilyMemberResp, ManageTagsReq, PatientResp,
|
|
UpdatePatientReq,
|
|
};
|
|
use crate::service::patient_service;
|
|
use crate::state::HealthState;
|
|
|
|
#[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>,
|
|
}
|
|
|
|
/// 分配医生请求
|
|
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
|
pub struct AssignDoctorReq {
|
|
pub doctor_id: Uuid,
|
|
pub relationship_type: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
|
pub struct DeleteWithVersion {
|
|
pub version: i32,
|
|
}
|
|
|
|
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,
|
|
{
|
|
require_permission(&ctx, "health.patient.list")?;
|
|
let page = params.page.unwrap_or(1);
|
|
let page_size = params.page_size.unwrap_or(20);
|
|
let result = patient_service::list_patients(
|
|
&state, ctx.tenant_id, page, page_size, params.search, params.tag_id,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
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,
|
|
{
|
|
require_permission(&ctx, "health.patient.manage")?;
|
|
let result = patient_service::create_patient(
|
|
&state, ctx.tenant_id, Some(ctx.user_id), req,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
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,
|
|
{
|
|
require_permission(&ctx, "health.patient.list")?;
|
|
let result = patient_service::get_patient(&state, ctx.tenant_id, id).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn update_patient<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(id): Path<Uuid>,
|
|
Json(req): Json<UpdatePatientWithVersion>,
|
|
) -> Result<Json<ApiResponse<PatientResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.patient.manage")?;
|
|
let version = req.version;
|
|
let update = UpdatePatientReq {
|
|
name: req.name,
|
|
gender: req.gender,
|
|
birth_date: req.birth_date,
|
|
blood_type: req.blood_type,
|
|
id_number: req.id_number,
|
|
allergy_history: req.allergy_history,
|
|
medical_history_summary: req.medical_history_summary,
|
|
emergency_contact_name: req.emergency_contact_name,
|
|
emergency_contact_phone: req.emergency_contact_phone,
|
|
source: req.source,
|
|
notes: req.notes,
|
|
status: req.status,
|
|
verification_status: req.verification_status,
|
|
};
|
|
let result = patient_service::update_patient(
|
|
&state, ctx.tenant_id, id, Some(ctx.user_id), update, version,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn delete_patient<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(id): Path<Uuid>,
|
|
Json(req): Json<DeleteWithVersion>,
|
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.patient.manage")?;
|
|
patient_service::delete_patient(&state, ctx.tenant_id, id, Some(ctx.user_id), req.version).await?;
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|
|
|
|
pub async fn manage_patient_tags<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(id): Path<Uuid>,
|
|
Json(req): Json<ManageTagsReq>,
|
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.patient.manage")?;
|
|
patient_service::manage_patient_tags(&state, ctx.tenant_id, id, req, Some(ctx.user_id)).await?;
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|
|
|
|
pub async fn get_health_summary<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<Json<ApiResponse<serde_json::Value>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.patient.list")?;
|
|
let result = patient_service::get_health_summary(&state, ctx.tenant_id, id).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
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,
|
|
{
|
|
require_permission(&ctx, "health.patient.list")?;
|
|
let result = patient_service::list_family_members(&state, ctx.tenant_id, id).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn create_family_member<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(id): Path<Uuid>,
|
|
Json(req): Json<FamilyMemberReq>,
|
|
) -> Result<Json<ApiResponse<FamilyMemberResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.patient.manage")?;
|
|
let result = patient_service::create_family_member(
|
|
&state, ctx.tenant_id, id, Some(ctx.user_id), req,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
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<FamilyMemberUpdateWithVersion>,
|
|
) -> Result<Json<ApiResponse<FamilyMemberResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.patient.manage")?;
|
|
let version = req.version;
|
|
let update = FamilyMemberReq {
|
|
name: req.name,
|
|
relationship: req.relationship,
|
|
phone: req.phone,
|
|
birth_date: req.birth_date,
|
|
notes: req.notes,
|
|
};
|
|
let result = patient_service::update_family_member(
|
|
&state, ctx.tenant_id, _patient_id, member_id, Some(ctx.user_id), update, version,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn delete_family_member<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path((patient_id, member_id)): Path<(Uuid, Uuid)>,
|
|
Json(req): Json<DeleteWithVersion>,
|
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.patient.manage")?;
|
|
patient_service::delete_family_member(
|
|
&state, ctx.tenant_id, patient_id, member_id, Some(ctx.user_id), req.version,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|
|
|
|
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,
|
|
{
|
|
require_permission(&ctx, "health.patient.manage")?;
|
|
patient_service::assign_doctor(
|
|
&state,
|
|
ctx.tenant_id,
|
|
id,
|
|
req.doctor_id,
|
|
req.relationship_type.unwrap_or_else(|| "primary".to_string()),
|
|
Some(ctx.user_id),
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|
|
|
|
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,
|
|
{
|
|
require_permission(&ctx, "health.patient.manage")?;
|
|
patient_service::remove_doctor(&state, ctx.tenant_id, patient_id, doctor_id).await?;
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|
|
|
|
// 带版本号的更新请求包装
|
|
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
|
pub struct UpdatePatientWithVersion {
|
|
pub name: Option<String>,
|
|
pub gender: Option<String>,
|
|
pub birth_date: Option<chrono::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>,
|
|
pub status: Option<String>,
|
|
pub verification_status: Option<String>,
|
|
pub version: i32,
|
|
}
|
|
|
|
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
|
|
pub struct FamilyMemberUpdateWithVersion {
|
|
pub name: String,
|
|
pub relationship: String,
|
|
pub phone: Option<String>,
|
|
pub birth_date: Option<chrono::NaiveDate>,
|
|
pub notes: Option<String>,
|
|
pub version: i32,
|
|
}
|