use axum::extract::{Extension, FromRef, Path, Query, State}; use axum::Json; use erp_core::rbac::require_permission; use erp_core::types::{ApiResponse, TenantContext}; use serde::Deserialize; use crate::dto::suggestion::SuggestionStatus; use crate::service::suggestion::SuggestionService; use crate::state::AiState; #[derive(Debug, Deserialize)] pub struct ListSuggestionsQuery { pub analysis_id: Option, pub status: Option, } pub async fn list_suggestions( State(state): State, Extension(ctx): Extension, Query(params): Query, ) -> Result>, erp_core::error::AppError> where AiState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "ai.suggestion.list")?; if let Some(analysis_id) = params.analysis_id { let items = SuggestionService::list_by_analysis( &state.db, ctx.tenant_id, analysis_id, ) .await?; Ok(Json(ApiResponse::ok(serde_json::json!({ "data": items, "total": items.len(), })))) } else { let items = SuggestionService::list_pending(&state.db, ctx.tenant_id).await?; Ok(Json(ApiResponse::ok(serde_json::json!({ "data": items, "total": items.len(), })))) } } #[derive(Debug, Deserialize)] pub struct ApproveBody { pub action: String, // "approve" or "reject" } pub async fn approve_suggestion( State(state): State, Extension(ctx): Extension, Path(id): Path, Json(body): Json, ) -> Result>, erp_core::error::AppError> where AiState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "ai.suggestion.manage")?; let new_status = match body.action.as_str() { "approve" => SuggestionStatus::Approved, "reject" => SuggestionStatus::Rejected, _ => { return Err(erp_core::error::AppError::Validation( "action 必须为 approve 或 reject".into(), )) } }; SuggestionService::update_status( &state.db, id, ctx.tenant_id, new_status, Some(ctx.user_id), ) .await?; Ok(Json(ApiResponse::ok(serde_json::json!({ "id": id, "status": new_status.as_str(), })))) } /// 获取 AI 建议的前后对比报告。 pub async fn get_comparison( State(state): State, Extension(ctx): Extension, Path(id): Path, ) -> Result>, erp_core::error::AppError> where AiState: FromRef, S: Clone + Send + Sync + 'static, { require_permission(&ctx, "ai.suggestion.list")?; use crate::entity::ai_suggestion; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; let suggestion = ai_suggestion::Entity::find_by_id(id) .one(&state.db) .await .map_err(|e| erp_core::error::AppError::Internal(e.to_string()))? .filter(|s| s.tenant_id == ctx.tenant_id && s.deleted_at.is_none()) .ok_or_else(|| erp_core::error::AppError::NotFound("建议不存在".into()))?; match &suggestion.baseline_snapshot { Some(bs) if !bs.is_null() => { let action_result = suggestion.action_result.as_ref().unwrap_or(&serde_json::Value::Null); Ok(Json(ApiResponse::ok(serde_json::json!({ "suggestion_id": id, "baseline": bs, "current": action_result, "comparison_available": !action_result.is_null(), })))) } _ => Ok(Json(ApiResponse::ok(serde_json::json!({ "suggestion_id": id, "comparison_available": false, "message": "该建议暂无 baseline 快照,无法生成对比报告", })))), } }