use sea_orm_migration::{prelude::*, schema::*}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { // Create tag table manager .create_table( TableCreateStatement::new() .table("tag") .if_not_exists() .col(pk_auto("id").not_null()) .col(string("name").not_null().unique_key()) .col(string("color_code").not_null()) .col(string("created_at").not_null()) .col(string("updated_at").not_null()) .to_owned(), ) .await?; // Create indexes manager .create_index( Index::create() .name("idx-tag-name") .table("tag") .col("name") .to_owned(), ) .await?; // Create category table manager .create_table( TableCreateStatement::new() .table("category") .if_not_exists() .col(pk_auto("id").not_null()) .col( integer("parent_id") .null() .comment("the parent category id, can be null for root categories"), ) .col(string("name").not_null()) .col(string("color_code").not_null()) .col(string("created_at").not_null()) .col(string("updated_at").not_null()) .foreign_key( ForeignKey::create() .name("fk-category-parent_id") .from("category", "parent_id") .to("category", "id") .on_delete(ForeignKeyAction::NoAction) .on_update(ForeignKeyAction::Restrict), ) .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx-category-name_parent_id") .table("category") .col("name") .col("parent_id") .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx-category-name") .table("category") .col("name") .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx-category-parent_id") .table("category") .col("parent_id") .to_owned(), ) .await?; // Create transaction table manager .create_table( TableCreateStatement::new() .table("transaction") .if_not_exists() .col(pk_auto("id").not_null()) // for debt transaction, amount is negative; for credit transaction, amount is positive. The actual effect on the account balance depends on the account type. For example, a $100 income transaction will have amount = 100, transaction_type = STANDARD, and it will increase the balance of an asset account but decrease the balance of a debt account. A $50 expense transaction will have amount = -50, transaction_type = STANDARD, and it will decrease the balance of an asset account but increase the balance of a debt account. A $200 transfer from account A to account B will have two transactions: one with amount = -200, transaction_type = STANDARD for account A, and another with amount = 200, transaction_type = STANDARD for account B. An opening balance transaction will have transaction_type = OPENING_BALANCE, and its amount can be positive or negative depending on the initial balance of the account. .col(string("amount").not_null().comment("the amount of this transaction in the original currency. Positive for assets increase or debts decrease, negative for assets decrease or debts increase")) .col(string("transaction_type").not_null().comment("One of: STANDARD, RECONCILIATION, OPENING_BALANCE")) .col(string("currency_code").not_null()) .col(string("exchange_rate").null()) .col(string("transaction_date").not_null()) .col(string("metadata").null()) .col(string("created_at").not_null()) .col(string("updated_at").not_null()) // fks .col(integer("account_id").not_null().comment("the account this transaction belongs to")) // default null .col(integer("from_account_id").null().comment("the account this transaction is from, can be null for income and expense transactions")) // transaction category .col(integer("category_id").null().comment("the category of this transaction, can be null")) // Add foreign key constraint to account .foreign_key( ForeignKey::create() .name("fk-transaction-account") .from("transaction", "account_id") .to("account", "id") .on_delete(ForeignKeyAction::NoAction) .on_update(ForeignKeyAction::Restrict), ) .foreign_key( ForeignKey::create() .name("fk-transaction-from_account") .from("transaction", "from_account_id") .to("account", "id") .on_delete(ForeignKeyAction::NoAction) .on_update(ForeignKeyAction::Restrict), ) .foreign_key( ForeignKey::create() .name("fk-transaction-category") .from("transaction", "category_id") .to("category", "id") .on_delete(ForeignKeyAction::NoAction) .on_update(ForeignKeyAction::Restrict), ) .to_owned(), ) .await?; // Create transaction_tag table manager .create_table( TableCreateStatement::new() .table("transaction_tag") .if_not_exists() .col(integer("transaction_id").not_null()) .col(integer("tag_id").not_null()) .primary_key( Index::create() .name("pk-transaction_tag") .col("transaction_id") .col("tag_id"), ) .foreign_key( ForeignKey::create() .name("fk-transaction_tag-transaction") .from("transaction_tag", "transaction_id") .to("transaction", "id") .on_delete(ForeignKeyAction::NoAction) .on_update(ForeignKeyAction::Restrict), ) .foreign_key( ForeignKey::create() .name("fk-transaction_tag-tag") .from("transaction_tag", "tag_id") .to("tag", "id") .on_delete(ForeignKeyAction::NoAction) .on_update(ForeignKeyAction::Restrict), ) .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx-transaction-account_id") .table("transaction") .col("account_id") .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx-transaction-from_account_id") .table("transaction") .col("from_account_id") .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx-transaction-category_id") .table("transaction") .col("category_id") .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx-transaction-transaction_date") .table("transaction") .col("transaction_date") .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx-transaction-transaction_type") .table("transaction") .col("transaction_type") .to_owned(), ) .await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table( TableDropStatement::new() .table("transaction_tag") .to_owned(), ) .await?; manager .drop_table(TableDropStatement::new().table("transaction").to_owned()) .await?; manager .drop_table(TableDropStatement::new().table("category").to_owned()) .await?; manager .drop_table(TableDropStatement::new().table("tag").to_owned()) .await?; Ok(()) } }