diff --git a/crates/erp-server/migration/src/lib.rs b/crates/erp-server/migration/src/lib.rs index 9680856..fab7b1f 100644 --- a/crates/erp-server/migration/src/lib.rs +++ b/crates/erp-server/migration/src/lib.rs @@ -1,12 +1,34 @@ pub use sea_orm_migration::prelude::*; mod m20260410_000001_create_tenant; +mod m20260411_000002_create_users; +mod m20260411_000003_create_user_credentials; +mod m20260411_000004_create_user_tokens; +mod m20260411_000005_create_roles; +mod m20260411_000006_create_permissions; +mod m20260411_000007_create_role_permissions; +mod m20260411_000008_create_user_roles; +mod m20260411_000009_create_organizations; +mod m20260411_000010_create_departments; +mod m20260411_000011_create_positions; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { - vec![Box::new(m20260410_000001_create_tenant::Migration)] + vec![ + Box::new(m20260410_000001_create_tenant::Migration), + Box::new(m20260411_000002_create_users::Migration), + Box::new(m20260411_000003_create_user_credentials::Migration), + Box::new(m20260411_000004_create_user_tokens::Migration), + Box::new(m20260411_000005_create_roles::Migration), + Box::new(m20260411_000006_create_permissions::Migration), + Box::new(m20260411_000007_create_role_permissions::Migration), + Box::new(m20260411_000008_create_user_roles::Migration), + Box::new(m20260411_000009_create_organizations::Migration), + Box::new(m20260411_000010_create_departments::Migration), + Box::new(m20260411_000011_create_positions::Migration), + ] } } diff --git a/crates/erp-server/migration/src/m20260411_000002_create_users.rs b/crates/erp-server/migration/src/m20260411_000002_create_users.rs new file mode 100644 index 0000000..ef4a8c2 --- /dev/null +++ b/crates/erp-server/migration/src/m20260411_000002_create_users.rs @@ -0,0 +1,109 @@ +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(Users::Table) + .if_not_exists() + .col( + ColumnDef::new(Users::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Users::TenantId).uuid().not_null()) + .col(ColumnDef::new(Users::Username).string().not_null()) + .col(ColumnDef::new(Users::Email).string().null()) + .col(ColumnDef::new(Users::Phone).string().null()) + .col(ColumnDef::new(Users::DisplayName).string().null()) + .col(ColumnDef::new(Users::AvatarUrl).string().null()) + .col( + ColumnDef::new(Users::Status) + .string() + .not_null() + .default("active"), + ) + .col( + ColumnDef::new(Users::LastLoginAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(Users::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Users::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(Users::CreatedBy).uuid().not_null()) + .col(ColumnDef::new(Users::UpdatedBy).uuid().not_null()) + .col( + ColumnDef::new(Users::DeletedAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(Users::Version) + .integer() + .not_null() + .default(1), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_users_tenant_id") + .table(Users::Table) + .col(Users::TenantId) + .to_owned(), + ) + .await?; + + manager.get_connection().execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + "CREATE UNIQUE INDEX idx_users_tenant_username ON users (tenant_id, username) WHERE deleted_at IS NULL".to_string(), + )).await.map_err(|e| DbErr::Custom(e.to_string()))?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Users::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Users { + Table, + Id, + TenantId, + Username, + Email, + Phone, + DisplayName, + AvatarUrl, + Status, + LastLoginAt, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} diff --git a/crates/erp-server/migration/src/m20260411_000003_create_user_credentials.rs b/crates/erp-server/migration/src/m20260411_000003_create_user_credentials.rs new file mode 100644 index 0000000..e062333 --- /dev/null +++ b/crates/erp-server/migration/src/m20260411_000003_create_user_credentials.rs @@ -0,0 +1,123 @@ +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(UserCredentials::Table) + .if_not_exists() + .col( + ColumnDef::new(UserCredentials::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(UserCredentials::TenantId).uuid().not_null()) + .col(ColumnDef::new(UserCredentials::UserId).uuid().not_null()) + .col( + ColumnDef::new(UserCredentials::CredentialType) + .string() + .not_null() + .default("password"), + ) + .col(ColumnDef::new(UserCredentials::CredentialData).json().null()) + .col( + ColumnDef::new(UserCredentials::Verified) + .boolean() + .not_null() + .default(false), + ) + .col( + ColumnDef::new(UserCredentials::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(UserCredentials::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(UserCredentials::CreatedBy).uuid().not_null()) + .col(ColumnDef::new(UserCredentials::UpdatedBy).uuid().not_null()) + .col( + ColumnDef::new(UserCredentials::DeletedAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(UserCredentials::Version) + .integer() + .not_null() + .default(1), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_user_credentials_user_id") + .from(UserCredentials::Table, UserCredentials::UserId) + .to(Users::Table, Users::Id) + .on_delete(ForeignKeyAction::Cascade) + .to_owned(), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_user_credentials_tenant_id") + .table(UserCredentials::Table) + .col(UserCredentials::TenantId) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_user_credentials_user_id") + .table(UserCredentials::Table) + .col(UserCredentials::UserId) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(UserCredentials::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum UserCredentials { + Table, + Id, + TenantId, + UserId, + CredentialType, + CredentialData, + Verified, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} + +#[derive(DeriveIden)] +enum Users { + Table, + Id, +} diff --git a/crates/erp-server/migration/src/m20260411_000004_create_user_tokens.rs b/crates/erp-server/migration/src/m20260411_000004_create_user_tokens.rs new file mode 100644 index 0000000..9fbda31 --- /dev/null +++ b/crates/erp-server/migration/src/m20260411_000004_create_user_tokens.rs @@ -0,0 +1,140 @@ +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(UserTokens::Table) + .if_not_exists() + .col( + ColumnDef::new(UserTokens::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(UserTokens::TenantId).uuid().not_null()) + .col(ColumnDef::new(UserTokens::UserId).uuid().not_null()) + .col( + ColumnDef::new(UserTokens::TokenHash) + .string() + .not_null() + .unique_key(), + ) + .col(ColumnDef::new(UserTokens::TokenType).string().not_null()) + .col( + ColumnDef::new(UserTokens::ExpiresAt) + .timestamp_with_time_zone() + .not_null(), + ) + .col( + ColumnDef::new(UserTokens::RevokedAt) + .timestamp_with_time_zone() + .null(), + ) + .col(ColumnDef::new(UserTokens::DeviceInfo).string().null()) + .col( + ColumnDef::new(UserTokens::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(UserTokens::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(UserTokens::CreatedBy).uuid().not_null()) + .col(ColumnDef::new(UserTokens::UpdatedBy).uuid().not_null()) + .col( + ColumnDef::new(UserTokens::DeletedAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(UserTokens::Version) + .integer() + .not_null() + .default(1), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_user_tokens_user_id") + .from(UserTokens::Table, UserTokens::UserId) + .to(Users::Table, Users::Id) + .on_delete(ForeignKeyAction::Cascade) + .to_owned(), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_user_tokens_tenant_id") + .table(UserTokens::Table) + .col(UserTokens::TenantId) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_user_tokens_user_id") + .table(UserTokens::Table) + .col(UserTokens::UserId) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_user_tokens_token_hash") + .table(UserTokens::Table) + .col(UserTokens::TokenHash) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(UserTokens::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum UserTokens { + Table, + Id, + TenantId, + UserId, + TokenHash, + TokenType, + ExpiresAt, + RevokedAt, + DeviceInfo, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} + +#[derive(DeriveIden)] +enum Users { + Table, + Id, +} diff --git a/crates/erp-server/migration/src/m20260411_000005_create_roles.rs b/crates/erp-server/migration/src/m20260411_000005_create_roles.rs new file mode 100644 index 0000000..98d5498 --- /dev/null +++ b/crates/erp-server/migration/src/m20260411_000005_create_roles.rs @@ -0,0 +1,106 @@ +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(Roles::Table) + .if_not_exists() + .col( + ColumnDef::new(Roles::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Roles::TenantId).uuid().not_null()) + .col(ColumnDef::new(Roles::Name).string().not_null()) + .col(ColumnDef::new(Roles::Code).string().not_null()) + .col(ColumnDef::new(Roles::Description).text().null()) + .col( + ColumnDef::new(Roles::IsSystem) + .boolean() + .not_null() + .default(false), + ) + .col( + ColumnDef::new(Roles::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Roles::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(Roles::CreatedBy).uuid().not_null()) + .col(ColumnDef::new(Roles::UpdatedBy).uuid().not_null()) + .col( + ColumnDef::new(Roles::DeletedAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(Roles::Version) + .integer() + .not_null() + .default(1), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_roles_tenant_id") + .table(Roles::Table) + .col(Roles::TenantId) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_roles_tenant_code") + .table(Roles::Table) + .col(Roles::TenantId) + .col(Roles::Code) + .unique() + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Roles::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Roles { + Table, + Id, + TenantId, + Name, + Code, + Description, + IsSystem, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} diff --git a/crates/erp-server/migration/src/m20260411_000006_create_permissions.rs b/crates/erp-server/migration/src/m20260411_000006_create_permissions.rs new file mode 100644 index 0000000..7eaad0f --- /dev/null +++ b/crates/erp-server/migration/src/m20260411_000006_create_permissions.rs @@ -0,0 +1,103 @@ +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(Permissions::Table) + .if_not_exists() + .col( + ColumnDef::new(Permissions::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Permissions::TenantId).uuid().not_null()) + .col(ColumnDef::new(Permissions::Code).string().not_null()) + .col(ColumnDef::new(Permissions::Name).string().not_null()) + .col(ColumnDef::new(Permissions::Resource).string().not_null()) + .col(ColumnDef::new(Permissions::Action).string().not_null()) + .col(ColumnDef::new(Permissions::Description).text().null()) + .col( + ColumnDef::new(Permissions::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Permissions::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(Permissions::CreatedBy).uuid().not_null()) + .col(ColumnDef::new(Permissions::UpdatedBy).uuid().not_null()) + .col( + ColumnDef::new(Permissions::DeletedAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(Permissions::Version) + .integer() + .not_null() + .default(1), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_permissions_tenant_id") + .table(Permissions::Table) + .col(Permissions::TenantId) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_permissions_tenant_code") + .table(Permissions::Table) + .col(Permissions::TenantId) + .col(Permissions::Code) + .unique() + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Permissions::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Permissions { + Table, + Id, + TenantId, + Code, + Name, + Resource, + Action, + Description, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} diff --git a/crates/erp-server/migration/src/m20260411_000007_create_role_permissions.rs b/crates/erp-server/migration/src/m20260411_000007_create_role_permissions.rs new file mode 100644 index 0000000..3781969 --- /dev/null +++ b/crates/erp-server/migration/src/m20260411_000007_create_role_permissions.rs @@ -0,0 +1,116 @@ +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(RolePermissions::Table) + .if_not_exists() + .col( + ColumnDef::new(RolePermissions::RoleId) + .uuid() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(RolePermissions::PermissionId) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(RolePermissions::TenantId).uuid().not_null()) + .col( + ColumnDef::new(RolePermissions::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(RolePermissions::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(RolePermissions::CreatedBy).uuid().not_null()) + .col(ColumnDef::new(RolePermissions::UpdatedBy).uuid().not_null()) + .col( + ColumnDef::new(RolePermissions::DeletedAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(RolePermissions::Version) + .integer() + .not_null() + .default(1), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_role_permissions_role_id") + .from(RolePermissions::Table, RolePermissions::RoleId) + .to(Roles::Table, Roles::Id) + .on_delete(ForeignKeyAction::Cascade) + .to_owned(), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_role_permissions_permission_id") + .from(RolePermissions::Table, RolePermissions::PermissionId) + .to(Permissions::Table, Permissions::Id) + .on_delete(ForeignKeyAction::Cascade) + .to_owned(), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_role_permissions_tenant_id") + .table(RolePermissions::Table) + .col(RolePermissions::TenantId) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(RolePermissions::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum RolePermissions { + Table, + RoleId, + PermissionId, + TenantId, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} + +#[derive(DeriveIden)] +enum Roles { + Table, + Id, +} + +#[derive(DeriveIden)] +enum Permissions { + Table, + Id, +} diff --git a/crates/erp-server/migration/src/m20260411_000008_create_user_roles.rs b/crates/erp-server/migration/src/m20260411_000008_create_user_roles.rs new file mode 100644 index 0000000..017bb78 --- /dev/null +++ b/crates/erp-server/migration/src/m20260411_000008_create_user_roles.rs @@ -0,0 +1,116 @@ +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(UserRoles::Table) + .if_not_exists() + .col( + ColumnDef::new(UserRoles::UserId) + .uuid() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(UserRoles::RoleId) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(UserRoles::TenantId).uuid().not_null()) + .col( + ColumnDef::new(UserRoles::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(UserRoles::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(UserRoles::CreatedBy).uuid().not_null()) + .col(ColumnDef::new(UserRoles::UpdatedBy).uuid().not_null()) + .col( + ColumnDef::new(UserRoles::DeletedAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(UserRoles::Version) + .integer() + .not_null() + .default(1), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_user_roles_user_id") + .from(UserRoles::Table, UserRoles::UserId) + .to(Users::Table, Users::Id) + .on_delete(ForeignKeyAction::Cascade) + .to_owned(), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_user_roles_role_id") + .from(UserRoles::Table, UserRoles::RoleId) + .to(Roles::Table, Roles::Id) + .on_delete(ForeignKeyAction::Cascade) + .to_owned(), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_user_roles_tenant_id") + .table(UserRoles::Table) + .col(UserRoles::TenantId) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(UserRoles::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum UserRoles { + Table, + UserId, + RoleId, + TenantId, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} + +#[derive(DeriveIden)] +enum Users { + Table, + Id, +} + +#[derive(DeriveIden)] +enum Roles { + Table, + Id, +} diff --git a/crates/erp-server/migration/src/m20260411_000009_create_organizations.rs b/crates/erp-server/migration/src/m20260411_000009_create_organizations.rs new file mode 100644 index 0000000..134e376 --- /dev/null +++ b/crates/erp-server/migration/src/m20260411_000009_create_organizations.rs @@ -0,0 +1,116 @@ +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(Organizations::Table) + .if_not_exists() + .col( + ColumnDef::new(Organizations::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Organizations::TenantId).uuid().not_null()) + .col(ColumnDef::new(Organizations::Name).string().not_null()) + .col(ColumnDef::new(Organizations::Code).string().null()) + .col(ColumnDef::new(Organizations::ParentId).uuid().null()) + .col(ColumnDef::new(Organizations::Path).string().null()) + .col( + ColumnDef::new(Organizations::Level) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(Organizations::SortOrder) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(Organizations::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Organizations::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(Organizations::CreatedBy).uuid().not_null()) + .col(ColumnDef::new(Organizations::UpdatedBy).uuid().not_null()) + .col( + ColumnDef::new(Organizations::DeletedAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(Organizations::Version) + .integer() + .not_null() + .default(1), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_organizations_parent_id") + .from(Organizations::Table, Organizations::ParentId) + .to(Organizations::Table, Organizations::Id) + .on_delete(ForeignKeyAction::Restrict) + .to_owned(), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_organizations_tenant_id") + .table(Organizations::Table) + .col(Organizations::TenantId) + .to_owned(), + ) + .await?; + + manager.get_connection().execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + "CREATE UNIQUE INDEX idx_organizations_tenant_code ON organizations (tenant_id, code) WHERE code IS NOT NULL".to_string(), + )).await.map_err(|e| DbErr::Custom(e.to_string()))?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Organizations::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Organizations { + Table, + Id, + TenantId, + Name, + Code, + ParentId, + Path, + Level, + SortOrder, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} diff --git a/crates/erp-server/migration/src/m20260411_000010_create_departments.rs b/crates/erp-server/migration/src/m20260411_000010_create_departments.rs new file mode 100644 index 0000000..86db99e --- /dev/null +++ b/crates/erp-server/migration/src/m20260411_000010_create_departments.rs @@ -0,0 +1,146 @@ +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(Departments::Table) + .if_not_exists() + .col( + ColumnDef::new(Departments::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Departments::TenantId).uuid().not_null()) + .col(ColumnDef::new(Departments::OrgId).uuid().not_null()) + .col(ColumnDef::new(Departments::Name).string().not_null()) + .col(ColumnDef::new(Departments::Code).string().null()) + .col(ColumnDef::new(Departments::ParentId).uuid().null()) + .col(ColumnDef::new(Departments::ManagerId).uuid().null()) + .col(ColumnDef::new(Departments::Path).string().null()) + .col( + ColumnDef::new(Departments::SortOrder) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(Departments::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Departments::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(Departments::CreatedBy).uuid().not_null()) + .col(ColumnDef::new(Departments::UpdatedBy).uuid().not_null()) + .col( + ColumnDef::new(Departments::DeletedAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(Departments::Version) + .integer() + .not_null() + .default(1), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_departments_org_id") + .from(Departments::Table, Departments::OrgId) + .to(Organizations::Table, Organizations::Id) + .on_delete(ForeignKeyAction::Restrict) + .to_owned(), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_departments_parent_id") + .from(Departments::Table, Departments::ParentId) + .to(Departments::Table, Departments::Id) + .on_delete(ForeignKeyAction::Restrict) + .to_owned(), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_departments_manager_id") + .from(Departments::Table, Departments::ManagerId) + .to(Users::Table, Users::Id) + .on_delete(ForeignKeyAction::SetNull) + .to_owned(), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_departments_tenant_id") + .table(Departments::Table) + .col(Departments::TenantId) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_departments_org_id") + .table(Departments::Table) + .col(Departments::OrgId) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Departments::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Departments { + Table, + Id, + TenantId, + OrgId, + Name, + Code, + ParentId, + ManagerId, + Path, + SortOrder, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} + +#[derive(DeriveIden)] +enum Organizations { + Table, + Id, +} + +#[derive(DeriveIden)] +enum Users { + Table, + Id, +} diff --git a/crates/erp-server/migration/src/m20260411_000011_create_positions.rs b/crates/erp-server/migration/src/m20260411_000011_create_positions.rs new file mode 100644 index 0000000..1f2733e --- /dev/null +++ b/crates/erp-server/migration/src/m20260411_000011_create_positions.rs @@ -0,0 +1,125 @@ +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(Positions::Table) + .if_not_exists() + .col( + ColumnDef::new(Positions::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Positions::TenantId).uuid().not_null()) + .col(ColumnDef::new(Positions::DeptId).uuid().not_null()) + .col(ColumnDef::new(Positions::Name).string().not_null()) + .col(ColumnDef::new(Positions::Code).string().null()) + .col( + ColumnDef::new(Positions::Level) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(Positions::SortOrder) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(Positions::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Positions::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(Positions::CreatedBy).uuid().not_null()) + .col(ColumnDef::new(Positions::UpdatedBy).uuid().not_null()) + .col( + ColumnDef::new(Positions::DeletedAt) + .timestamp_with_time_zone() + .null(), + ) + .col( + ColumnDef::new(Positions::Version) + .integer() + .not_null() + .default(1), + ) + .foreign_key( + &mut ForeignKey::create() + .name("fk_positions_dept_id") + .from(Positions::Table, Positions::DeptId) + .to(Departments::Table, Departments::Id) + .on_delete(ForeignKeyAction::Restrict) + .to_owned(), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_positions_tenant_id") + .table(Positions::Table) + .col(Positions::TenantId) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_positions_dept_id") + .table(Positions::Table) + .col(Positions::DeptId) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Positions::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Positions { + Table, + Id, + TenantId, + DeptId, + Name, + Code, + Level, + SortOrder, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} + +#[derive(DeriveIden)] +enum Departments { + Table, + Id, +}