diff --git a/database/sql/schema.sql b/database/sql/schema.sql index 93e038a..c29cc15 100644 --- a/database/sql/schema.sql +++ b/database/sql/schema.sql @@ -167,6 +167,13 @@ CREATE TABLE IF NOT EXISTS `transaction_tags` ( CONSTRAINT `fk_transaction_tags_transaction` FOREIGN KEY (`transaction_id`) REFERENCES `transactions` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +-- Account Tags table (Many-to-Many) +CREATE TABLE IF NOT EXISTS `account_tags` ( + `account_id` bigint(20) unsigned NOT NULL, + `tag_id` bigint(20) unsigned NOT NULL, + PRIMARY KEY (`account_id`,`tag_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + -- Transaction Images table CREATE TABLE IF NOT EXISTS `transaction_images` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, @@ -553,6 +560,7 @@ CREATE TABLE IF NOT EXISTS `user_settings` ( CONSTRAINT `fk_user_settings_default_expense` FOREIGN KEY (`default_expense_account_id`) REFERENCES `accounts` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `fk_user_settings_default_income` FOREIGN KEY (`default_income_account_id`) REFERENCES `accounts` (`id`) ON DELETE SET NULL ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + -- Notifications table CREATE TABLE IF NOT EXISTS `notifications` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, diff --git a/internal/router/router.go b/internal/router/router.go index f4fdaa8..90f00d5 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -70,7 +70,7 @@ func Setup(db *gorm.DB, yunAPIClient *service.YunAPIClient, cfg *config.Config) categoryService := service.NewCategoryService(categoryRepo) tagService := service.NewTagService(tagRepo) classificationService := service.NewClassificationService(classificationRepo, categoryRepo) - transactionService := service.NewTransactionService(transactionRepo, accountRepo, categoryRepo, tagRepo, db) + transactionService := service.NewTransactionService(transactionRepo, accountRepo, categoryRepo, tagRepo, ledgerRepo, db) imageService := service.NewImageService(transactionImageRepo, transactionRepo, db, cfg.ImageUploadDir) recurringService := service.NewRecurringTransactionService(recurringRepo, transactionRepo, accountRepo, categoryRepo, allocationRuleRepo, allocationRecordRepo, piggyBankRepo, db) exchangeRateService := service.NewExchangeRateService(exchangeRateRepo) @@ -347,7 +347,7 @@ func SetupWithRedis(db *gorm.DB, yunAPIClient *service.YunAPIClient, redisClient categoryService := service.NewCategoryService(categoryRepo) tagService := service.NewTagService(tagRepo) classificationService := service.NewClassificationService(classificationRepo, categoryRepo) - transactionService := service.NewTransactionService(transactionRepo, accountRepo, categoryRepo, tagRepo, db) + transactionService := service.NewTransactionService(transactionRepo, accountRepo, categoryRepo, tagRepo, ledgerRepo, db) imageService := service.NewImageService(transactionImageRepo, transactionRepo, db, cfg.ImageUploadDir) recurringService := service.NewRecurringTransactionService(recurringRepo, transactionRepo, accountRepo, categoryRepo, allocationRuleRepo, allocationRecordRepo, piggyBankRepo, db) reportService := service.NewReportService(reportRepo, exchangeRateRepo) diff --git a/internal/service/transaction_service.go b/internal/service/transaction_service.go index b65a451..1656b74 100644 --- a/internal/service/transaction_service.go +++ b/internal/service/transaction_service.go @@ -39,6 +39,7 @@ type TransactionInput struct { ImagePath string `json:"image_path,omitempty"` ToAccountID *uint `json:"to_account_id,omitempty"` TagIDs []uint `json:"tag_ids,omitempty"` + LedgerID *uint `json:"ledger_id,omitempty"` } // TransactionListInput represents the input for listing transactions @@ -65,6 +66,7 @@ type TransactionService struct { accountRepo *repository.AccountRepository categoryRepo *repository.CategoryRepository tagRepo *repository.TagRepository + ledgerRepo *repository.LedgerRepository db *gorm.DB } @@ -74,6 +76,7 @@ func NewTransactionService( accountRepo *repository.AccountRepository, categoryRepo *repository.CategoryRepository, tagRepo *repository.TagRepository, + ledgerRepo *repository.LedgerRepository, db *gorm.DB, ) *TransactionService { return &TransactionService{ @@ -81,6 +84,7 @@ func NewTransactionService( accountRepo: accountRepo, categoryRepo: categoryRepo, tagRepo: tagRepo, + ledgerRepo: ledgerRepo, db: db, } } @@ -160,6 +164,23 @@ func (s *TransactionService) CreateTransaction(userID uint, input TransactionInp return nil, err } + // If LedgerID is not provided, use the default ledger + var ledgerID uint + if input.LedgerID == nil || *input.LedgerID == 0 { + defaultLedger, err := s.ledgerRepo.GetDefault(userID) + if err != nil { + return nil, fmt.Errorf("failed to get default ledger: %w", err) + } + ledgerID = defaultLedger.ID + } else { + ledgerID = *input.LedgerID + // Verify ledger exists + _, err := s.ledgerRepo.GetByID(userID, ledgerID) + if err != nil { + return nil, fmt.Errorf("failed to verify ledger: %w", err) + } + } + // Execute within a database transaction var transaction *models.Transaction err := s.db.Transaction(func(tx *gorm.DB) error { @@ -216,6 +237,7 @@ func (s *TransactionService) CreateTransaction(userID uint, input TransactionInp Note: input.Note, ImagePath: input.ImagePath, ToAccountID: input.ToAccountID, + LedgerID: &ledgerID, } // Save transaction with tags @@ -414,6 +436,14 @@ func (s *TransactionService) UpdateTransaction(userID, id uint, input Transactio transaction.Note = input.Note transaction.ImagePath = input.ImagePath transaction.ToAccountID = input.ToAccountID + if input.LedgerID != nil && *input.LedgerID > 0 { + // Verify ledger exists + _, err := s.ledgerRepo.GetByID(userID, *input.LedgerID) + if err != nil { + return fmt.Errorf("failed to verify ledger: %w", err) + } + transaction.LedgerID = input.LedgerID + } if err := txTransactionRepo.UpdateWithTags(transaction, input.TagIDs); err != nil { return fmt.Errorf("failed to update transaction: %w", err)