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, pub page_size: Option, } #[derive(Debug, Deserialize, IntoParams)] pub struct ProductTypeParam { pub product_type: Option, } #[derive(Debug, Deserialize, IntoParams)] pub struct AdminProductFilter { pub is_active: Option, } // --------------------------------------------------------------------------- // 患者端:积分账户 + 打卡 // --------------------------------------------------------------------------- pub async fn get_my_account( State(state): State, Extension(ctx): Extension, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Query(params): Query, ) -> Result>>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Query(params): Query, Query(page): Query, ) -> Result>>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(product_id): Path, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Query(params): Query, ) -> Result>>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Query(params): Query, ) -> Result>>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(event_id): Path, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, ) -> Result>>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(rule_id): Path, Json(wrapper): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(rule_id): Path, Json(wrapper): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Query(params): Query, Query(page): Query, Query(filter): Query, ) -> Result>>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(product_id): Path, Json(wrapper): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(product_id): Path, Json(wrapper): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Query(params): Query, ) -> Result>>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(event_id): Path, Json(wrapper): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(event_id): Path, Json(wrapper): Json, ) -> Result>, AppError> where HealthState: FromRef, 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, pub page_size: Option, pub status: Option, } pub async fn admin_list_events( State(state): State, Extension(ctx): Extension, Query(params): Query, ) -> Result>>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(event_id): Path, Json(req): Json, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(patient_id): Path, ) -> Result>, AppError> where HealthState: FromRef, 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( State(state): State, Extension(ctx): Extension, Path(patient_id): Path, Query(params): Query, ) -> Result>>, AppError> where HealthState: FromRef, 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 { use crate::entity::patient; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; let result: Option = 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())) }