package repository import ( "errors" "fmt" "accounting-app/internal/models" "gorm.io/gorm" ) // Common repository errors for allocation rules var ( ErrAllocationRuleNotFound = errors.New("allocation rule not found") ErrAllocationRuleInUse = errors.New("allocation rule is in use and cannot be deleted") ) // AllocationRuleRepository handles database operations for allocation rules type AllocationRuleRepository struct { db *gorm.DB } // NewAllocationRuleRepository creates a new AllocationRuleRepository instance func NewAllocationRuleRepository(db *gorm.DB) *AllocationRuleRepository { return &AllocationRuleRepository{db: db} } // Create creates a new allocation rule in the database func (r *AllocationRuleRepository) Create(rule *models.AllocationRule) error { if err := r.db.Create(rule).Error; err != nil { return fmt.Errorf("failed to create allocation rule: %w", err) } return nil } // GetByID retrieves an allocation rule by its ID func (r *AllocationRuleRepository) GetByID(userID uint, id uint) (*models.AllocationRule, error) { var rule models.AllocationRule if err := r.db.Preload("Targets").Preload("SourceAccount").Where("user_id = ?", userID).First(&rule, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrAllocationRuleNotFound } return nil, fmt.Errorf("failed to get allocation rule: %w", err) } return &rule, nil } // GetAll retrieves all allocation rules for a user func (r *AllocationRuleRepository) GetAll(userID uint) ([]models.AllocationRule, error) { var rules []models.AllocationRule if err := r.db.Preload("Targets").Preload("SourceAccount").Where("user_id = ?", userID).Order("created_at DESC").Find(&rules).Error; err != nil { return nil, fmt.Errorf("failed to get allocation rules: %w", err) } return rules, nil } // GetActive retrieves all active allocation rules for a user func (r *AllocationRuleRepository) GetActive(userID uint) ([]models.AllocationRule, error) { var rules []models.AllocationRule if err := r.db.Preload("Targets").Preload("SourceAccount").Where("user_id = ? AND is_active = ?", userID, true).Order("created_at DESC").Find(&rules).Error; err != nil { return nil, fmt.Errorf("failed to get active allocation rules: %w", err) } return rules, nil } // GetByTriggerType retrieves all allocation rules of a specific trigger type for a user func (r *AllocationRuleRepository) GetByTriggerType(userID uint, triggerType models.TriggerType) ([]models.AllocationRule, error) { var rules []models.AllocationRule if err := r.db.Preload("Targets").Preload("SourceAccount").Where("user_id = ? AND trigger_type = ?", userID, triggerType).Order("created_at DESC").Find(&rules).Error; err != nil { return nil, fmt.Errorf("failed to get allocation rules by trigger type: %w", err) } return rules, nil } // GetActiveByTriggerType retrieves all active allocation rules of a specific trigger type for a user func (r *AllocationRuleRepository) GetActiveByTriggerType(userID uint, triggerType models.TriggerType) ([]models.AllocationRule, error) { var rules []models.AllocationRule if err := r.db.Preload("Targets").Preload("SourceAccount").Where("user_id = ? AND trigger_type = ? AND is_active = ?", userID, triggerType, true).Order("created_at DESC").Find(&rules).Error; err != nil { return nil, fmt.Errorf("failed to get active allocation rules by trigger type: %w", err) } return rules, nil } // GetActiveByTriggerTypeAndAccount retrieves all active allocation rules of a specific trigger type // that match the given account (source_account_id is NULL or equals accountID) for a user func (r *AllocationRuleRepository) GetActiveByTriggerTypeAndAccount(userID uint, triggerType models.TriggerType, accountID uint) ([]models.AllocationRule, error) { var rules []models.AllocationRule if err := r.db.Preload("Targets").Preload("SourceAccount"). Where("user_id = ? AND trigger_type = ? AND is_active = ? AND (source_account_id IS NULL OR source_account_id = ?)", userID, triggerType, true, accountID). Order("created_at DESC").Find(&rules).Error; err != nil { return nil, fmt.Errorf("failed to get active allocation rules by trigger type and account: %w", err) } return rules, nil } // Update updates an existing allocation rule in the database func (r *AllocationRuleRepository) Update(userID uint, rule *models.AllocationRule) error { // First check if the rule exists and belongs to the user var existing models.AllocationRule if err := r.db.Where("id = ? AND user_id = ?", rule.ID, userID).First(&existing).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrAllocationRuleNotFound } return fmt.Errorf("failed to check allocation rule existence: %w", err) } // Update the rule if err := r.db.Save(rule).Error; err != nil { return fmt.Errorf("failed to update allocation rule: %w", err) } return nil } // Delete deletes an allocation rule by its ID func (r *AllocationRuleRepository) Delete(userID uint, id uint) error { // First check if the rule exists var rule models.AllocationRule if err := r.db.Where("user_id = ?", userID).First(&rule, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrAllocationRuleNotFound } return fmt.Errorf("failed to check allocation rule existence: %w", err) } // Delete all targets first if err := r.db.Where("rule_id = ?", id).Delete(&models.AllocationTarget{}).Error; err != nil { return fmt.Errorf("failed to delete allocation targets: %w", err) } // Delete the rule (soft delete due to gorm.DeletedAt field) if err := r.db.Delete(&rule).Error; err != nil { return fmt.Errorf("failed to delete allocation rule: %w", err) } return nil } // ExistsByID checks if an allocation rule with the given ID exists for a user func (r *AllocationRuleRepository) ExistsByID(userID, id uint) (bool, error) { var count int64 if err := r.db.Model(&models.AllocationRule{}).Where("user_id = ? AND id = ?", userID, id).Count(&count).Error; err != nil { return false, fmt.Errorf("failed to check allocation rule existence: %w", err) } return count > 0, nil } // CreateTarget creates a new allocation target func (r *AllocationRuleRepository) CreateTarget(target *models.AllocationTarget) error { if err := r.db.Create(target).Error; err != nil { return fmt.Errorf("failed to create allocation target: %w", err) } return nil } // UpdateTarget updates an existing allocation target func (r *AllocationRuleRepository) UpdateTarget(target *models.AllocationTarget) error { if err := r.db.Save(target).Error; err != nil { return fmt.Errorf("failed to update allocation target: %w", err) } return nil } // DeleteTarget deletes an allocation target by its ID func (r *AllocationRuleRepository) DeleteTarget(id uint) error { if err := r.db.Delete(&models.AllocationTarget{}, id).Error; err != nil { return fmt.Errorf("failed to delete allocation target: %w", err) } return nil } // DeleteTargetsByRuleID deletes all targets for a specific rule func (r *AllocationRuleRepository) DeleteTargetsByRuleID(ruleID uint) error { if err := r.db.Where("rule_id = ?", ruleID).Delete(&models.AllocationTarget{}).Error; err != nil { return fmt.Errorf("failed to delete allocation targets: %w", err) } return nil } // GetTargetsByRuleID retrieves all targets for a specific rule func (r *AllocationRuleRepository) GetTargetsByRuleID(ruleID uint) ([]models.AllocationTarget, error) { var targets []models.AllocationTarget if err := r.db.Where("rule_id = ?", ruleID).Find(&targets).Error; err != nil { return nil, fmt.Errorf("failed to get allocation targets: %w", err) } return targets, nil }