package service import ( "context" "errors" "strings" "financeiro-carvalho/internal/model" "financeiro-carvalho/internal/repository" ) var ( ErrRecurringEmptyName = errors.New("name is required") ErrRecurringInvalidAmount = errors.New("expected_amount must be greater than zero") ErrRecurringInvalidDayOfMonth = errors.New("day_of_month must be between 1 and 31") ) type RecurringService struct { repo repository.RecurringRepository txRepo repository.ManualTransactionRepository } func NewRecurringService(repo repository.RecurringRepository, txRepo repository.ManualTransactionRepository) *RecurringService { return &RecurringService{repo: repo, txRepo: txRepo} } func (s *RecurringService) List(ctx context.Context) ([]model.RecurringExpense, error) { items, err := s.repo.List(ctx) if items == nil { return []model.RecurringExpense{}, err } return items, err } func (s *RecurringService) Create(ctx context.Context, in model.RecurringInput) (*model.RecurringExpense, error) { if err := validateRecurringInput(in); err != nil { return nil, err } in.Name = strings.TrimSpace(in.Name) return s.repo.Create(ctx, in) } func (s *RecurringService) Update(ctx context.Context, id int, in model.RecurringInput) (*model.RecurringExpense, error) { if err := validateRecurringInput(in); err != nil { return nil, err } in.Name = strings.TrimSpace(in.Name) return s.repo.Update(ctx, id, in) } func (s *RecurringService) Delete(ctx context.Context, id int) error { return s.repo.Delete(ctx, id) } // MonthlyStatus checks which active recurring expenses are covered/ignored for the given month (YYYY-MM). func (s *RecurringService) MonthlyStatus(ctx context.Context, month string) ([]model.RecurringStatus, error) { items, err := s.repo.List(ctx) if err != nil { return nil, err } var result []model.RecurringStatus for _, re := range items { if !re.Active { continue } ignored, reason, err := s.repo.IsIgnored(ctx, re.ID, month) if err != nil { return nil, err } covered := false if !ignored { covered, err = s.txRepo.HasMatchingTransaction(ctx, re.CategoryID, month, re.ExpectedAmount) if err != nil { return nil, err } } result = append(result, model.RecurringStatus{ RecurringExpense: re, Covered: covered || ignored, Ignored: ignored, Reason: reason, }) } return result, nil } func (s *RecurringService) Ignore(ctx context.Context, id int, month, reason string) error { return s.repo.Ignore(ctx, id, month, reason) } func (s *RecurringService) Unignore(ctx context.Context, id int, month string) error { return s.repo.Unignore(ctx, id, month) } func validateRecurringInput(in model.RecurringInput) error { if strings.TrimSpace(in.Name) == "" { return ErrRecurringEmptyName } if in.ExpectedAmount <= 0 { return ErrRecurringInvalidAmount } if in.DayOfMonth < 1 || in.DayOfMonth > 31 { return ErrRecurringInvalidDayOfMonth } return nil }