From 105cae0565c9a30915036764e15244ed1dc46f5e Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 5 May 2026 15:13:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E6=B7=BB=E5=8A=A0=20ai=5Ftenant=5F?= =?UTF-8?q?configs=20=E8=BF=81=E7=A7=BB=20+=20Entity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持租户级 Provider 路由配置、月度 Token 预算、每日患者限制 unique 索引确保每租户一条配置,迁移号 000117 --- crates/erp-ai/src/entity/ai_tenant_config.rs | 27 +++++ crates/erp-ai/src/entity/mod.rs | 1 + crates/erp-server/migration/src/lib.rs | 2 + ...0260505_000117_create_ai_tenant_configs.rs | 107 ++++++++++++++++++ 4 files changed, 137 insertions(+) create mode 100644 crates/erp-ai/src/entity/ai_tenant_config.rs create mode 100644 crates/erp-server/migration/src/m20260505_000117_create_ai_tenant_configs.rs diff --git a/crates/erp-ai/src/entity/ai_tenant_config.rs b/crates/erp-ai/src/entity/ai_tenant_config.rs new file mode 100644 index 0000000..9f1c651 --- /dev/null +++ b/crates/erp-ai/src/entity/ai_tenant_config.rs @@ -0,0 +1,27 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "ai_tenant_configs")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub tenant_id: Uuid, + pub default_provider: String, + pub fallback_provider: Option, + pub analysis_type_overrides: Option, + pub monthly_token_budget: i64, + pub daily_patient_limit: i32, + pub is_enabled: bool, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, + pub created_by: Option, + pub updated_by: Option, + pub deleted_at: Option, + pub version_lock: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/erp-ai/src/entity/mod.rs b/crates/erp-ai/src/entity/mod.rs index ab9af5e..f31e3ac 100644 --- a/crates/erp-ai/src/entity/mod.rs +++ b/crates/erp-ai/src/entity/mod.rs @@ -2,4 +2,5 @@ pub mod ai_analysis; pub mod ai_prompt; pub mod ai_risk_threshold; pub mod ai_suggestion; +pub mod ai_tenant_config; pub mod ai_usage; diff --git a/crates/erp-server/migration/src/lib.rs b/crates/erp-server/migration/src/lib.rs index 744e9e4..bd6d000 100644 --- a/crates/erp-server/migration/src/lib.rs +++ b/crates/erp-server/migration/src/lib.rs @@ -116,6 +116,7 @@ mod m20260505_000113_create_ble_gateways; mod m20260505_000114_dialysis_record_add_workflow_instance; mod m20260505_000115_family_member_health_proxy; mod m20260505_000116_seed_missing_health_menus; +mod m20260505_000117_create_ai_tenant_configs; pub struct Migrator; @@ -239,6 +240,7 @@ impl MigratorTrait for Migrator { Box::new(m20260505_000114_dialysis_record_add_workflow_instance::Migration), Box::new(m20260505_000115_family_member_health_proxy::Migration), Box::new(m20260505_000116_seed_missing_health_menus::Migration), + Box::new(m20260505_000117_create_ai_tenant_configs::Migration), ] } } diff --git a/crates/erp-server/migration/src/m20260505_000117_create_ai_tenant_configs.rs b/crates/erp-server/migration/src/m20260505_000117_create_ai_tenant_configs.rs new file mode 100644 index 0000000..3cfb4ab --- /dev/null +++ b/crates/erp-server/migration/src/m20260505_000117_create_ai_tenant_configs.rs @@ -0,0 +1,107 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(AiTenantConfig::Table) + .if_not_exists() + .col(ColumnDef::new(AiTenantConfig::Id).uuid().not_null().primary_key()) + .col(ColumnDef::new(AiTenantConfig::TenantId).uuid().not_null()) + .col( + ColumnDef::new(AiTenantConfig::DefaultProvider) + .string_len(50) + .not_null() + .default("claude"), + ) + .col(ColumnDef::new(AiTenantConfig::FallbackProvider).string_len(50).null()) + .col(ColumnDef::new(AiTenantConfig::AnalysisTypeOverrides).json().null()) + .col( + ColumnDef::new(AiTenantConfig::MonthlyTokenBudget) + .big_integer() + .not_null() + .default(1_000_000), + ) + .col( + ColumnDef::new(AiTenantConfig::DailyPatientLimit) + .integer() + .not_null() + .default(50), + ) + .col( + ColumnDef::new(AiTenantConfig::IsEnabled) + .boolean() + .not_null() + .default(true), + ) + .col( + ColumnDef::new(AiTenantConfig::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(AiTenantConfig::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(AiTenantConfig::CreatedBy).uuid().null()) + .col(ColumnDef::new(AiTenantConfig::UpdatedBy).uuid().null()) + .col(ColumnDef::new(AiTenantConfig::DeletedAt).timestamp_with_time_zone().null()) + .col( + ColumnDef::new(AiTenantConfig::VersionLock) + .integer() + .not_null() + .default(1), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .unique() + .name("idx_ai_tenant_configs_tenant_unique") + .table(AiTenantConfig::Table) + .col(AiTenantConfig::TenantId) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(AiTenantConfig::Table).to_owned()) + .await?; + Ok(()) + } +} + +#[derive(DeriveIden)] +enum AiTenantConfig { + Table, + Id, + TenantId, + DefaultProvider, + FallbackProvider, + AnalysisTypeOverrides, + MonthlyTokenBudget, + DailyPatientLimit, + IsEnabled, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + VersionLock, +}