package service import ( "context" "errors" "strings" "financeiro-carvalho/internal/model" "financeiro-carvalho/internal/repository" ) var ( ErrDefaultCategory = errors.New("default categories cannot be deleted") ErrHasTransactions = errors.New("category has linked transactions") ErrEmptyName = errors.New("name is required") ErrInvalidColor = errors.New("color must be a valid hex color (e.g. #FF5733)") ) type CategoryService struct { repo repository.CategoryRepository } func NewCategoryService(repo repository.CategoryRepository) *CategoryService { return &CategoryService{repo: repo} } func (s *CategoryService) List(ctx context.Context) ([]model.Category, error) { return s.repo.List(ctx) } func (s *CategoryService) Create(ctx context.Context, in model.CategoryInput) (*model.Category, error) { if err := validateInput(in); err != nil { return nil, err } return s.repo.Create(ctx, strings.TrimSpace(in.Name), in.Color, in.IsTax) } func (s *CategoryService) Update(ctx context.Context, id int, in model.CategoryInput) (*model.Category, error) { if err := validateInput(in); err != nil { return nil, err } return s.repo.Update(ctx, id, strings.TrimSpace(in.Name), in.Color, in.IsTax) } func (s *CategoryService) Delete(ctx context.Context, id int) error { cat, err := s.repo.GetByID(ctx, id) if err != nil { return err } if cat.IsDefault { return ErrDefaultCategory } hasTx, err := s.repo.HasTransactions(ctx, id) if err != nil { return err } if hasTx { return ErrHasTransactions } return s.repo.Delete(ctx, id) } func validateInput(in model.CategoryInput) error { if strings.TrimSpace(in.Name) == "" { return ErrEmptyName } if !isValidHexColor(in.Color) { return ErrInvalidColor } return nil } func isValidHexColor(c string) bool { if len(c) != 7 || c[0] != '#' { return false } for _, ch := range c[1:] { if !((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) { return false } } return true }