P0 (CRITICAL): - C1: 统计 API 全部改为 safe_aggregate 容错,防止单个子查询崩溃导致 500 - C2: Token 刷新增加用户身份验证,防止并发场景下身份切换 - C3: 患者端线下活动接口添加患者档案验证,防止 Doctor/HM 越权访问 P1 (HIGH): - H1: 操作记录用 EntityName 组件解析用户名,不再显示截断 UUID - H4: 告警标题添加中英文映射 (translateAlertTitle) - H5: 告警面板补全 message import + 修复 hooks 顺序 - H8: 咨询消息发送按钮添加 AuthButton 权限控制 - H9: routeConfig 日常监测权限码改为 health.daily-monitoring.* P2 (MEDIUM): - M4: 咨询类型映射补全 online/phone/doctor/follow_up 中文标签 DTO: LabReportStatisticsResp, AppointmentStatisticsResp, VitalSignsReportRateResp 添加 Default derive
611 lines
20 KiB
Rust
611 lines
20 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::points_dto::*;
|
|
use crate::service::points_service;
|
|
use crate::state::HealthState;
|
|
|
|
#[derive(Debug, Deserialize, IntoParams)]
|
|
pub struct PaginationParams {
|
|
pub page: Option<u64>,
|
|
pub page_size: Option<u64>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, IntoParams)]
|
|
pub struct ProductTypeParam {
|
|
pub product_type: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, IntoParams)]
|
|
pub struct AdminProductFilter {
|
|
pub is_active: Option<bool>,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 患者端:积分账户 + 打卡
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub async fn get_my_account<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
) -> Result<Json<ApiResponse<PointsAccountResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let patient_id = resolve_patient_id(&state, ctx.tenant_id, ctx.user_id).await?;
|
|
let result = points_service::get_account(&state, ctx.tenant_id, patient_id).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn daily_checkin<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
) -> Result<Json<ApiResponse<CheckinStatusResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let patient_id = resolve_patient_id(&state, ctx.tenant_id, ctx.user_id).await?;
|
|
let result =
|
|
points_service::daily_checkin(&state, ctx.tenant_id, patient_id, Some(ctx.user_id)).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn get_checkin_status<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
) -> Result<Json<ApiResponse<CheckinStatusResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let patient_id = resolve_patient_id(&state, ctx.tenant_id, ctx.user_id).await?;
|
|
let result = points_service::get_checkin_status(&state, ctx.tenant_id, patient_id).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 患者端:积分流水 + 商品 + 兑换
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub async fn list_my_transactions<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Query(params): Query<PaginationParams>,
|
|
) -> Result<Json<ApiResponse<PaginatedResponse<PointsTransactionResp>>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let patient_id = resolve_patient_id(&state, ctx.tenant_id, ctx.user_id).await?;
|
|
let page = params.page.unwrap_or(1);
|
|
let page_size = params.page_size.unwrap_or(20);
|
|
let result =
|
|
points_service::list_transactions(&state, ctx.tenant_id, patient_id, page, page_size)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn list_products<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Query(params): Query<ProductTypeParam>,
|
|
Query(page): Query<PaginationParams>,
|
|
) -> Result<Json<ApiResponse<PaginatedResponse<PointsProductResp>>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let p = page.page.unwrap_or(1);
|
|
let ps = page.page_size.unwrap_or(20);
|
|
let result =
|
|
points_service::list_products(&state, ctx.tenant_id, params.product_type, p, ps).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn get_product<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(product_id): Path<Uuid>,
|
|
) -> Result<Json<ApiResponse<PointsProductResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let result = points_service::get_product(&state, ctx.tenant_id, product_id).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn exchange_product<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Json(req): Json<ExchangeReq>,
|
|
) -> Result<Json<ApiResponse<PointsOrderResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
let patient_id = resolve_patient_id(&state, ctx.tenant_id, ctx.user_id).await?;
|
|
let result =
|
|
points_service::exchange_product(&state, ctx.tenant_id, patient_id, req, Some(ctx.user_id))
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn list_my_orders<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Query(params): Query<PaginationParams>,
|
|
) -> Result<Json<ApiResponse<PaginatedResponse<PointsOrderResp>>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let patient_id = resolve_patient_id(&state, ctx.tenant_id, ctx.user_id).await?;
|
|
let page = params.page.unwrap_or(1);
|
|
let page_size = params.page_size.unwrap_or(20);
|
|
let result =
|
|
points_service::list_orders(&state, ctx.tenant_id, patient_id, page, page_size).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 线下活动(患者端)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub async fn list_offline_events<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Query(params): Query<PaginationParams>,
|
|
) -> Result<Json<ApiResponse<PaginatedResponse<OfflineEventResp>>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
// 患者端端点:验证当前用户有关联的患者档案
|
|
let _patient_id = resolve_patient_id(&state, ctx.tenant_id, ctx.user_id).await?;
|
|
let page = params.page.unwrap_or(1);
|
|
let page_size = params.page_size.unwrap_or(20);
|
|
let result =
|
|
points_service::list_offline_events(&state, ctx.tenant_id, page, page_size).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn register_event<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(event_id): Path<Uuid>,
|
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
let patient_id = resolve_patient_id(&state, ctx.tenant_id, ctx.user_id).await?;
|
|
points_service::register_event(
|
|
&state,
|
|
ctx.tenant_id,
|
|
event_id,
|
|
patient_id,
|
|
Some(ctx.user_id),
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 管理端:核销 + 规则管理 + 商品管理
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub async fn verify_order<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Json(req): Json<VerifyOrderReq>,
|
|
) -> Result<Json<ApiResponse<PointsOrderResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
let result =
|
|
points_service::verify_order(&state, ctx.tenant_id, req.qr_code, ctx.user_id).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn list_rules<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
) -> Result<Json<ApiResponse<Vec<PointsRuleResp>>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let result = points_service::list_rules(&state, ctx.tenant_id).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn create_rule<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Json(req): Json<CreatePointsRuleReq>,
|
|
) -> Result<Json<ApiResponse<PointsRuleResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
let mut req = req;
|
|
req.sanitize();
|
|
let result = points_service::create_rule(&state, ctx.tenant_id, Some(ctx.user_id), req).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn update_rule<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(rule_id): Path<Uuid>,
|
|
Json(wrapper): Json<crate::dto::points_dto::UpdateRuleWithVersion>,
|
|
) -> Result<Json<ApiResponse<PointsRuleResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
let mut data = wrapper.data;
|
|
data.sanitize();
|
|
let result = points_service::update_rule(
|
|
&state,
|
|
ctx.tenant_id,
|
|
rule_id,
|
|
Some(ctx.user_id),
|
|
data,
|
|
wrapper.version,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn delete_rule<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(rule_id): Path<Uuid>,
|
|
Json(wrapper): Json<crate::dto::DeleteWithVersion>,
|
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
points_service::delete_rule(
|
|
&state,
|
|
ctx.tenant_id,
|
|
rule_id,
|
|
Some(ctx.user_id),
|
|
wrapper.version,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|
|
|
|
pub async fn admin_list_products<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Query(params): Query<ProductTypeParam>,
|
|
Query(page): Query<PaginationParams>,
|
|
Query(filter): Query<AdminProductFilter>,
|
|
) -> Result<Json<ApiResponse<PaginatedResponse<PointsProductResp>>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let p = page.page.unwrap_or(1);
|
|
let ps = page.page_size.unwrap_or(20);
|
|
let result = points_service::admin_list_products(
|
|
&state,
|
|
ctx.tenant_id,
|
|
params.product_type,
|
|
filter.is_active,
|
|
p,
|
|
ps,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn admin_create_product<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Json(req): Json<CreatePointsProductReq>,
|
|
) -> Result<Json<ApiResponse<PointsProductResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
let mut req = req;
|
|
req.sanitize();
|
|
let result =
|
|
points_service::create_product(&state, ctx.tenant_id, Some(ctx.user_id), req).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn admin_update_product<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(product_id): Path<Uuid>,
|
|
Json(wrapper): Json<crate::dto::points_dto::UpdateProductWithVersion>,
|
|
) -> Result<Json<ApiResponse<PointsProductResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
let mut data = wrapper.data;
|
|
data.sanitize();
|
|
let result = points_service::update_product(
|
|
&state,
|
|
ctx.tenant_id,
|
|
product_id,
|
|
Some(ctx.user_id),
|
|
data,
|
|
wrapper.version,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn admin_delete_product<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(product_id): Path<Uuid>,
|
|
Json(wrapper): Json<crate::dto::DeleteWithVersion>,
|
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
points_service::delete_product(
|
|
&state,
|
|
ctx.tenant_id,
|
|
product_id,
|
|
Some(ctx.user_id),
|
|
wrapper.version,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|
|
|
|
pub async fn admin_list_orders<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Query(params): Query<PaginationParams>,
|
|
) -> Result<Json<ApiResponse<PaginatedResponse<PointsOrderResp>>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let page = params.page.unwrap_or(1);
|
|
let page_size = params.page_size.unwrap_or(20);
|
|
// 管理端查看所有订单 — 不按 patient_id 过滤
|
|
let result = points_service::admin_list_orders(&state, ctx.tenant_id, page, page_size).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 线下活动 — 管理端 CRUD + 签到
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub async fn admin_create_event<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Json(req): Json<CreateOfflineEventReq>,
|
|
) -> Result<Json<ApiResponse<OfflineEventResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
let mut req = req;
|
|
req.sanitize();
|
|
let result =
|
|
points_service::create_offline_event(&state, ctx.tenant_id, Some(ctx.user_id), req).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn admin_update_event<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(event_id): Path<Uuid>,
|
|
Json(wrapper): Json<UpdateOfflineEventWithVersion>,
|
|
) -> Result<Json<ApiResponse<OfflineEventResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
let mut data = wrapper.data;
|
|
data.sanitize();
|
|
let result = points_service::update_offline_event(
|
|
&state,
|
|
ctx.tenant_id,
|
|
event_id,
|
|
Some(ctx.user_id),
|
|
data,
|
|
wrapper.version,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn admin_delete_event<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(event_id): Path<Uuid>,
|
|
Json(wrapper): Json<crate::dto::DeleteWithVersion>,
|
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
points_service::delete_offline_event(
|
|
&state,
|
|
ctx.tenant_id,
|
|
event_id,
|
|
Some(ctx.user_id),
|
|
wrapper.version,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, IntoParams)]
|
|
pub struct AdminListEventsParams {
|
|
pub page: Option<u64>,
|
|
pub page_size: Option<u64>,
|
|
pub status: Option<String>,
|
|
}
|
|
|
|
pub async fn admin_list_events<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Query(params): Query<AdminListEventsParams>,
|
|
) -> Result<Json<ApiResponse<PaginatedResponse<OfflineEventResp>>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let page = params.page.unwrap_or(1);
|
|
let page_size = params.page_size.unwrap_or(20);
|
|
let result = points_service::admin_list_offline_events(
|
|
&state,
|
|
ctx.tenant_id,
|
|
params.status,
|
|
page,
|
|
page_size,
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn admin_checkin_event<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(event_id): Path<Uuid>,
|
|
Json(req): Json<AdminCheckinReq>,
|
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.manage")?;
|
|
points_service::admin_checkin_event(
|
|
&state,
|
|
ctx.tenant_id,
|
|
event_id,
|
|
req.patient_id,
|
|
Some(ctx.user_id),
|
|
)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(())))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 积分统计 — 管理端
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub async fn get_points_statistics<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
) -> Result<Json<ApiResponse<PointsStatisticsResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let result = points_service::get_points_statistics(&state, ctx.tenant_id).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 管理端:按 patient_id 查询积分账户 + 流水
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub async fn admin_get_patient_account<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(patient_id): Path<Uuid>,
|
|
) -> Result<Json<ApiResponse<PointsAccountResp>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let result = points_service::get_account(&state, ctx.tenant_id, patient_id).await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
pub async fn admin_list_patient_transactions<S>(
|
|
State(state): State<HealthState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(patient_id): Path<Uuid>,
|
|
Query(params): Query<PaginationParams>,
|
|
) -> Result<Json<ApiResponse<PaginatedResponse<PointsTransactionResp>>>, AppError>
|
|
where
|
|
HealthState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "health.points.list")?;
|
|
let page = params.page.unwrap_or(1);
|
|
let page_size = params.page_size.unwrap_or(20);
|
|
let result =
|
|
points_service::list_transactions(&state, ctx.tenant_id, patient_id, page, page_size)
|
|
.await?;
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 辅助:通过 user_id 解析 patient_id
|
|
// ---------------------------------------------------------------------------
|
|
|
|
async fn resolve_patient_id(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
user_id: Uuid,
|
|
) -> Result<Uuid, AppError> {
|
|
use crate::entity::patient;
|
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
|
let result: Option<patient::Model> = patient::Entity::find()
|
|
.filter(patient::Column::TenantId.eq(tenant_id))
|
|
.filter(patient::Column::UserId.eq(user_id))
|
|
.filter(patient::Column::DeletedAt.is_null())
|
|
.one(&state.db)
|
|
.await
|
|
.map_err(|e: sea_orm::DbErr| AppError::Internal(e.to_string()))?;
|
|
result
|
|
.map(|p| p.id)
|
|
.ok_or_else(|| AppError::NotFound("当前用户未关联患者档案".into()))
|
|
}
|