From 3592b5555604e008bfa5b4e7166c5a1daee99d77 Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 5 May 2026 15:55:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai+db):=20=E7=9F=A5=E8=AF=86=E5=BA=93=203?= =?UTF-8?q?=20=E8=A1=A8=E8=BF=81=E7=A7=BB=20+=20Entity=20=E2=80=94=20rules?= =?UTF-8?q?/references/guides?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 Task 21: - ai_knowledge_rules: L1 规则表(条件表达式 + 动作文本) - ai_knowledge_references: L2 参考表(摘要 + pgvector 嵌入) - ai_knowledge_guides: L3 指南表(全文 + pgvector 嵌入) --- .../erp-ai/src/entity/ai_knowledge_guides.rs | 29 ++++++++ .../src/entity/ai_knowledge_references.rs | 30 ++++++++ .../erp-ai/src/entity/ai_knowledge_rules.rs | 27 +++++++ crates/erp-ai/src/entity/mod.rs | 3 + crates/erp-server/migration/src/lib.rs | 6 ++ ...260505_000120_create_ai_knowledge_rules.rs | 69 ++++++++++++++++++ ...5_000121_create_ai_knowledge_references.rs | 72 +++++++++++++++++++ ...60505_000122_create_ai_knowledge_guides.rs | 69 ++++++++++++++++++ 8 files changed, 305 insertions(+) create mode 100644 crates/erp-ai/src/entity/ai_knowledge_guides.rs create mode 100644 crates/erp-ai/src/entity/ai_knowledge_references.rs create mode 100644 crates/erp-ai/src/entity/ai_knowledge_rules.rs create mode 100644 crates/erp-server/migration/src/m20260505_000120_create_ai_knowledge_rules.rs create mode 100644 crates/erp-server/migration/src/m20260505_000121_create_ai_knowledge_references.rs create mode 100644 crates/erp-server/migration/src/m20260505_000122_create_ai_knowledge_guides.rs diff --git a/crates/erp-ai/src/entity/ai_knowledge_guides.rs b/crates/erp-ai/src/entity/ai_knowledge_guides.rs new file mode 100644 index 0000000..42bb4ac --- /dev/null +++ b/crates/erp-ai/src/entity/ai_knowledge_guides.rs @@ -0,0 +1,29 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "ai_knowledge_guides")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub tenant_id: Uuid, + pub title: String, + pub analysis_type: String, + pub content: String, + pub category: Option, + // pgvector 字段 — SeaORM 不原生支持 vector 类型,查询时用 raw SQL + #[sea_orm(ignore)] + pub embedding: Option>, + 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/ai_knowledge_references.rs b/crates/erp-ai/src/entity/ai_knowledge_references.rs new file mode 100644 index 0000000..e4597bc --- /dev/null +++ b/crates/erp-ai/src/entity/ai_knowledge_references.rs @@ -0,0 +1,30 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "ai_knowledge_references")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub tenant_id: Uuid, + pub title: String, + pub analysis_type: String, + pub source_name: String, + pub content_summary: String, + // pgvector 字段 — SeaORM 不原生支持 vector 类型,查询时用 raw SQL + #[sea_orm(ignore)] + pub embedding: Option>, + pub tags: Option, + 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/ai_knowledge_rules.rs b/crates/erp-ai/src/entity/ai_knowledge_rules.rs new file mode 100644 index 0000000..aa9cee3 --- /dev/null +++ b/crates/erp-ai/src/entity/ai_knowledge_rules.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_knowledge_rules")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub tenant_id: Uuid, + pub rule_name: String, + pub analysis_type: String, + pub condition_expr: String, + pub action_text: String, + pub priority: 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 ffca218..ee3c490 100644 --- a/crates/erp-ai/src/entity/mod.rs +++ b/crates/erp-ai/src/entity/mod.rs @@ -1,5 +1,8 @@ pub mod ai_analysis; pub mod ai_analysis_queue; +pub mod ai_knowledge_guides; +pub mod ai_knowledge_references; +pub mod ai_knowledge_rules; pub mod ai_prompt; pub mod ai_risk_threshold; pub mod ai_suggestion; diff --git a/crates/erp-server/migration/src/lib.rs b/crates/erp-server/migration/src/lib.rs index 3e43e66..00407c8 100644 --- a/crates/erp-server/migration/src/lib.rs +++ b/crates/erp-server/migration/src/lib.rs @@ -119,6 +119,9 @@ mod m20260505_000116_seed_missing_health_menus; mod m20260505_000117_create_ai_tenant_configs; mod m20260505_000118_create_ai_analysis_queue; mod m20260505_000119_enable_pgvector; +mod m20260505_000120_create_ai_knowledge_rules; +mod m20260505_000121_create_ai_knowledge_references; +mod m20260505_000122_create_ai_knowledge_guides; pub struct Migrator; @@ -245,6 +248,9 @@ impl MigratorTrait for Migrator { Box::new(m20260505_000117_create_ai_tenant_configs::Migration), Box::new(m20260505_000118_create_ai_analysis_queue::Migration), Box::new(m20260505_000119_enable_pgvector::Migration), + Box::new(m20260505_000120_create_ai_knowledge_rules::Migration), + Box::new(m20260505_000121_create_ai_knowledge_references::Migration), + Box::new(m20260505_000122_create_ai_knowledge_guides::Migration), ] } } diff --git a/crates/erp-server/migration/src/m20260505_000120_create_ai_knowledge_rules.rs b/crates/erp-server/migration/src/m20260505_000120_create_ai_knowledge_rules.rs new file mode 100644 index 0000000..95908c4 --- /dev/null +++ b/crates/erp-server/migration/src/m20260505_000120_create_ai_knowledge_rules.rs @@ -0,0 +1,69 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[derive(Iden)] +enum AiKnowledgeRules { + Table, + Id, + TenantId, + RuleName, + AnalysisType, + ConditionExpr, + ActionText, + Priority, + IsEnabled, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + VersionLock, +} + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(AiKnowledgeRules::Table) + .if_not_exists() + .col(ColumnDef::new(AiKnowledgeRules::Id).uuid().not_null().primary_key()) + .col(ColumnDef::new(AiKnowledgeRules::TenantId).uuid().not_null()) + .col(ColumnDef::new(AiKnowledgeRules::RuleName).string().not_null()) + .col(ColumnDef::new(AiKnowledgeRules::AnalysisType).string().not_null()) + .col(ColumnDef::new(AiKnowledgeRules::ConditionExpr).string().not_null()) + .col(ColumnDef::new(AiKnowledgeRules::ActionText).string().not_null()) + .col(ColumnDef::new(AiKnowledgeRules::Priority).integer().not_null().default(0)) + .col(ColumnDef::new(AiKnowledgeRules::IsEnabled).boolean().not_null().default(true)) + .col(ColumnDef::new(AiKnowledgeRules::CreatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp())) + .col(ColumnDef::new(AiKnowledgeRules::UpdatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp())) + .col(ColumnDef::new(AiKnowledgeRules::CreatedBy).uuid()) + .col(ColumnDef::new(AiKnowledgeRules::UpdatedBy).uuid()) + .col(ColumnDef::new(AiKnowledgeRules::DeletedAt).timestamp_with_time_zone()) + .col(ColumnDef::new(AiKnowledgeRules::VersionLock).integer().not_null().default(1)) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx_ai_knowledge_rules_tenant_type") + .table(AiKnowledgeRules::Table) + .col(AiKnowledgeRules::TenantId) + .col(AiKnowledgeRules::AnalysisType) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(AiKnowledgeRules::Table).to_owned()) + .await + } +} diff --git a/crates/erp-server/migration/src/m20260505_000121_create_ai_knowledge_references.rs b/crates/erp-server/migration/src/m20260505_000121_create_ai_knowledge_references.rs new file mode 100644 index 0000000..c770443 --- /dev/null +++ b/crates/erp-server/migration/src/m20260505_000121_create_ai_knowledge_references.rs @@ -0,0 +1,72 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[derive(Iden)] +enum AiKnowledgeReferences { + Table, + Id, + TenantId, + Title, + AnalysisType, + SourceName, + ContentSummary, + Embedding, + Tags, + IsEnabled, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + VersionLock, +} + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(AiKnowledgeReferences::Table) + .if_not_exists() + .col(ColumnDef::new(AiKnowledgeReferences::Id).uuid().not_null().primary_key()) + .col(ColumnDef::new(AiKnowledgeReferences::TenantId).uuid().not_null()) + .col(ColumnDef::new(AiKnowledgeReferences::Title).string().not_null()) + .col(ColumnDef::new(AiKnowledgeReferences::AnalysisType).string().not_null()) + .col(ColumnDef::new(AiKnowledgeReferences::SourceName).string().not_null()) + .col(ColumnDef::new(AiKnowledgeReferences::ContentSummary).text().not_null()) + // vector(1536) — 兼容 OpenAI text-embedding-ada-002 + .col(ColumnDef::new(AiKnowledgeReferences::Embedding).custom("vector")) + .col(ColumnDef::new(AiKnowledgeReferences::Tags).json()) + .col(ColumnDef::new(AiKnowledgeReferences::IsEnabled).boolean().not_null().default(true)) + .col(ColumnDef::new(AiKnowledgeReferences::CreatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp())) + .col(ColumnDef::new(AiKnowledgeReferences::UpdatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp())) + .col(ColumnDef::new(AiKnowledgeReferences::CreatedBy).uuid()) + .col(ColumnDef::new(AiKnowledgeReferences::UpdatedBy).uuid()) + .col(ColumnDef::new(AiKnowledgeReferences::DeletedAt).timestamp_with_time_zone()) + .col(ColumnDef::new(AiKnowledgeReferences::VersionLock).integer().not_null().default(1)) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx_ai_knowledge_refs_tenant_type") + .table(AiKnowledgeReferences::Table) + .col(AiKnowledgeReferences::TenantId) + .col(AiKnowledgeReferences::AnalysisType) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(AiKnowledgeReferences::Table).to_owned()) + .await + } +} diff --git a/crates/erp-server/migration/src/m20260505_000122_create_ai_knowledge_guides.rs b/crates/erp-server/migration/src/m20260505_000122_create_ai_knowledge_guides.rs new file mode 100644 index 0000000..6d35e58 --- /dev/null +++ b/crates/erp-server/migration/src/m20260505_000122_create_ai_knowledge_guides.rs @@ -0,0 +1,69 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[derive(Iden)] +enum AiKnowledgeGuides { + Table, + Id, + TenantId, + Title, + AnalysisType, + Content, + Category, + Embedding, + IsEnabled, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + VersionLock, +} + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(AiKnowledgeGuides::Table) + .if_not_exists() + .col(ColumnDef::new(AiKnowledgeGuides::Id).uuid().not_null().primary_key()) + .col(ColumnDef::new(AiKnowledgeGuides::TenantId).uuid().not_null()) + .col(ColumnDef::new(AiKnowledgeGuides::Title).string().not_null()) + .col(ColumnDef::new(AiKnowledgeGuides::AnalysisType).string().not_null()) + .col(ColumnDef::new(AiKnowledgeGuides::Content).text().not_null()) + .col(ColumnDef::new(AiKnowledgeGuides::Category).string()) + .col(ColumnDef::new(AiKnowledgeGuides::Embedding).custom("vector")) + .col(ColumnDef::new(AiKnowledgeGuides::IsEnabled).boolean().not_null().default(true)) + .col(ColumnDef::new(AiKnowledgeGuides::CreatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp())) + .col(ColumnDef::new(AiKnowledgeGuides::UpdatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp())) + .col(ColumnDef::new(AiKnowledgeGuides::CreatedBy).uuid()) + .col(ColumnDef::new(AiKnowledgeGuides::UpdatedBy).uuid()) + .col(ColumnDef::new(AiKnowledgeGuides::DeletedAt).timestamp_with_time_zone()) + .col(ColumnDef::new(AiKnowledgeGuides::VersionLock).integer().not_null().default(1)) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx_ai_knowledge_guides_tenant_type") + .table(AiKnowledgeGuides::Table) + .col(AiKnowledgeGuides::TenantId) + .col(AiKnowledgeGuides::AnalysisType) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(AiKnowledgeGuides::Table).to_owned()) + .await + } +}