Files
hms/crates/erp-health/src/handler/consultation_handler.rs
iven 7a9054c914
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
feat: 医护仪表盘增强 + 患者端文章分类浏览
- DoctorDashboard 增加 pending_dialysis_review/pending_lab_review/today_appointments
- 医护小程序首页增加「健康审核」区块(待审透析/化验/今日预约)
- 患者端文章列表增加分类 tabs 横向滚动筛选
- article service 增加 listCategories + category_id 筛选
2026-04-26 14:25:06 +08:00

257 lines
8.0 KiB
Rust

use axum::Extension;
use axum::extract::{FromRef, Json, Path, Query, State};
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
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::consultation_dto::*;
use crate::service::consultation_service;
use crate::state::HealthState;
#[derive(Debug, Deserialize, IntoParams)]
pub struct SessionListParams {
pub page: Option<u64>,
pub page_size: Option<u64>,
pub status: Option<String>,
pub patient_id: Option<Uuid>,
pub doctor_id: Option<Uuid>,
}
#[derive(Debug, Deserialize, IntoParams)]
pub struct MessageListParams {
pub page: Option<u64>,
pub page_size: Option<u64>,
pub after_id: Option<Uuid>,
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct CloseSessionReq {
pub version: i32,
}
#[derive(Debug, serde::Deserialize, utoipa::ToSchema)]
pub struct CreateConsultationMessageReq {
pub session_id: Uuid,
pub content_type: Option<String>,
pub content: String,
}
#[derive(Debug, Deserialize, IntoParams)]
pub struct ExportSessionsParams {
pub status: Option<String>,
pub patient_id: Option<Uuid>,
pub doctor_id: Option<Uuid>,
pub page: Option<u64>,
pub page_size: Option<u64>,
}
pub async fn create_session<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Json(req): Json<CreateSessionReq>,
) -> Result<Json<ApiResponse<SessionResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.manage")?;
let result = consultation_service::create_session(
&state, ctx.tenant_id, Some(ctx.user_id), req,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn list_sessions<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Query(params): Query<SessionListParams>,
) -> Result<Json<ApiResponse<PaginatedResponse<SessionResp>>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.list")?;
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let result = consultation_service::list_sessions(
&state, ctx.tenant_id, page, page_size, params.status, params.patient_id,
params.doctor_id,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn get_session<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(id): Path<Uuid>,
) -> Result<Json<ApiResponse<SessionResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.list")?;
let result = consultation_service::get_session(&state, ctx.tenant_id, id).await?;
Ok(Json(ApiResponse::ok(result)))
}
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<MessageResp>>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.list")?;
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let result = consultation_service::list_messages(
&state, ctx.tenant_id, session_id, page, page_size, params.after_id,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn close_session<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Path(id): Path<Uuid>,
Json(req): Json<CloseSessionReq>,
) -> Result<Json<ApiResponse<SessionResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.manage")?;
let result = consultation_service::close_session(
&state, ctx.tenant_id, id, Some(ctx.user_id), req.version,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn create_message<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Json(req): Json<CreateConsultationMessageReq>,
) -> Result<Json<ApiResponse<MessageResp>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.manage")?;
let is_doctor = crate::entity::doctor_profile::Entity::find()
.filter(crate::entity::doctor_profile::Column::UserId.eq(ctx.user_id))
.filter(crate::entity::doctor_profile::Column::TenantId.eq(ctx.tenant_id))
.filter(crate::entity::doctor_profile::Column::DeletedAt.is_null())
.one(&state.db)
.await
.map_err(|e| AppError::Internal(e.to_string()))?
.is_some();
let mut msg_req = CreateMessageReq {
session_id: req.session_id,
sender_id: ctx.user_id,
sender_role: if is_doctor { "doctor" } else { "patient" }.to_string(),
content_type: req.content_type,
content: req.content,
};
msg_req.sanitize();
let result = consultation_service::create_message(
&state, ctx.tenant_id, Some(ctx.user_id), msg_req,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
pub async fn export_sessions<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
Query(params): Query<ExportSessionsParams>,
) -> Result<Json<ApiResponse<PaginatedResponse<SessionResp>>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.list")?;
let result = consultation_service::export_sessions(
&state, ctx.tenant_id, params.status, params.patient_id, params.doctor_id,
params.page, params.page_size,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}
/// 标记会话消息为已读。
#[utoipa::path(
put,
path = "/consultation-sessions/{id}/read",
responses(
(status = 200, description = "标记成功"),
(status = 404, description = "会话不存在"),
),
tag = "咨询管理",
)]
pub async fn mark_session_read<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,
{
require_permission(&ctx, "health.consultation.manage")?;
let is_doctor = crate::entity::doctor_profile::Entity::find()
.filter(crate::entity::doctor_profile::Column::UserId.eq(ctx.user_id))
.filter(crate::entity::doctor_profile::Column::TenantId.eq(ctx.tenant_id))
.filter(crate::entity::doctor_profile::Column::DeletedAt.is_null())
.one(&state.db)
.await
.map_err(|e| AppError::Internal(e.to_string()))?
.is_some();
let role = if is_doctor { "doctor" } else { "patient" };
consultation_service::mark_session_read(
&state, ctx.tenant_id, id, ctx.user_id, role,
)
.await?;
Ok(Json(ApiResponse::ok(())))
}
/// 获取当前医生的仪表盘数据。
#[utoipa::path(
get,
path = "/doctor/dashboard",
responses(
(status = 200, description = "仪表盘数据"),
),
tag = "医护端",
)]
pub async fn get_doctor_dashboard<S>(
State(state): State<HealthState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<consultation_service::DoctorDashboard>>, AppError>
where
HealthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "health.consultation.list")?;
let mut result = consultation_service::get_doctor_dashboard(
&state, ctx.tenant_id, ctx.user_id,
)
.await?;
consultation_service::enrich_doctor_dashboard_health(
&state, ctx.tenant_id, ctx.user_id, &mut result,
)
.await?;
Ok(Json(ApiResponse::ok(result)))
}