package repository import ( "errors" "fmt" "accounting-app/internal/models" "gorm.io/gorm" ) // Common ledger repository errors var ( ErrLedgerNotFound = errors.New("ledger not found") ErrLedgerInUse = errors.New("ledger is in use and cannot be deleted") ) // LedgerRepository handles database operations for ledgers type LedgerRepository struct { db *gorm.DB } // NewLedgerRepository creates a new LedgerRepository instance func NewLedgerRepository(db *gorm.DB) *LedgerRepository { return &LedgerRepository{db: db} } // Create creates a new ledger in the database func (r *LedgerRepository) Create(ledger *models.Ledger) error { if err := r.db.Create(ledger).Error; err != nil { return fmt.Errorf("failed to create ledger: %w", err) } return nil } // GetByID retrieves a ledger by its ID func (r *LedgerRepository) GetByID(userID uint, id uint) (*models.Ledger, error) { var ledger models.Ledger if err := r.db.Where("id = ? AND user_id = ?", id, userID).First(&ledger).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrLedgerNotFound } return nil, fmt.Errorf("failed to get ledger: %w", err) } return &ledger, nil } // GetAll retrieves all ledgers from the database (excluding soft-deleted) func (r *LedgerRepository) GetAll(userID uint) ([]models.Ledger, error) { var ledgers []models.Ledger if err := r.db.Where("user_id = ?", userID).Order("sort_order ASC, created_at DESC").Find(&ledgers).Error; err != nil { return nil, fmt.Errorf("failed to get ledgers: %w", err) } return ledgers, nil } // Update updates an existing ledger in the database func (r *LedgerRepository) Update(userID uint, ledger *models.Ledger) error { // First check if the ledger exists and belongs to the user var existing models.Ledger if err := r.db.Where("id = ? AND user_id = ?", ledger.ID, userID).First(&existing).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrLedgerNotFound } return fmt.Errorf("failed to check ledger existence: %w", err) } // Update the ledger if err := r.db.Save(ledger).Error; err != nil { return fmt.Errorf("failed to update ledger: %w", err) } return nil } // Delete soft-deletes a ledger by its ID func (r *LedgerRepository) Delete(userID uint, id uint) error { // First check if the ledger exists and belongs to the user var ledger models.Ledger if err := r.db.Where("id = ? AND user_id = ?", id, userID).First(&ledger).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrLedgerNotFound } return fmt.Errorf("failed to check ledger existence: %w", err) } // Soft delete the ledger if err := r.db.Delete(&ledger).Error; err != nil { return fmt.Errorf("failed to delete ledger: %w", err) } return nil } // Count returns the total number of ledgers (excluding soft-deleted) func (r *LedgerRepository) Count(userID uint) (int64, error) { var count int64 if err := r.db.Model(&models.Ledger{}).Where("user_id = ?", userID).Count(&count).Error; err != nil { return 0, fmt.Errorf("failed to count ledgers: %w", err) } return count, nil } // GetDefault retrieves the default ledger func (r *LedgerRepository) GetDefault(userID uint) (*models.Ledger, error) { var ledger models.Ledger if err := r.db.Where("user_id = ? AND is_default = ?", userID, true).First(&ledger).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrLedgerNotFound } return nil, fmt.Errorf("failed to get default ledger: %w", err) } return &ledger, nil } // SetDefault sets a ledger as the default ledger func (r *LedgerRepository) SetDefault(userID uint, id uint) error { // Start a transaction return r.db.Transaction(func(tx *gorm.DB) error { // First, unset all other default ledgers for this user if err := tx.Model(&models.Ledger{}).Where("user_id = ? AND is_default = ?", userID, true).Update("is_default", false).Error; err != nil { return fmt.Errorf("failed to unset default ledgers: %w", err) } // Then set the specified ledger as default (must belong to the user) result := tx.Model(&models.Ledger{}).Where("id = ? AND user_id = ?", id, userID).Update("is_default", true) if result.Error != nil { return fmt.Errorf("failed to set default ledger: %w", result.Error) } if result.RowsAffected == 0 { return ErrLedgerNotFound } return nil }) } // GetDeleted retrieves all soft-deleted ledgers // Feature: accounting-feature-upgrade // Validates: Requirements 3.9 // GetDeleted retrieves all soft-deleted ledgers func (r *LedgerRepository) GetDeleted(userID uint) ([]models.Ledger, error) { var ledgers []models.Ledger if err := r.db.Unscoped().Where("user_id = ? AND deleted_at IS NOT NULL", userID).Order("deleted_at DESC").Find(&ledgers).Error; err != nil { return nil, fmt.Errorf("failed to get deleted ledgers: %w", err) } return ledgers, nil } // Restore restores a soft-deleted ledger by its ID // Feature: accounting-feature-upgrade // Validates: Requirements 3.9 // Restore restores a soft-deleted ledger by its ID func (r *LedgerRepository) Restore(userID uint, id uint) error { // First check if the ledger exists and is deleted and belongs to the user var ledger models.Ledger if err := r.db.Unscoped().Where("id = ? AND user_id = ?", id, userID).First(&ledger).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrLedgerNotFound } return fmt.Errorf("failed to check ledger existence: %w", err) } // Check if the ledger is actually deleted if ledger.DeletedAt.Time.IsZero() { return fmt.Errorf("ledger is not deleted") } // Restore the ledger by setting deleted_at to NULL if err := r.db.Unscoped().Model(&ledger).Update("deleted_at", nil).Error; err != nil { return fmt.Errorf("failed to restore ledger: %w", err) } return nil }