- Migration 010: colunas yield_type e last_yield_date em accounts + source='yield' nas transações
- CDIYieldService: busca taxas BCB (serie 12) e cria transação source=yield com rendimento acumulado
- Weekends/feriados sem taxa usam última taxa disponível (AC4)
- GET /api/accounts dispara cálculo CDI on-demand antes de retornar saldos
- AccountsView: seletor yield_type (none/cdi/variable) + badge CDI/VAR na listagem
- Transações source=yield aparecem no histórico com descrição 'Rendimento CDI — {período}'
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
87 lines
2.4 KiB
Go
87 lines
2.4 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"financeiro-carvalho/internal/model"
|
|
"financeiro-carvalho/internal/repository"
|
|
)
|
|
|
|
var (
|
|
ErrAccountEmptyName = errors.New("account name cannot be empty")
|
|
ErrAccountInvalidType = errors.New("account type must be checking, savings, investment, or credit")
|
|
)
|
|
|
|
var validAccountTypes = map[string]bool{
|
|
"checking": true, "savings": true, "investment": true, "credit": true,
|
|
}
|
|
|
|
type AccountRepo interface {
|
|
List(ctx context.Context) ([]model.Account, error)
|
|
GetByID(ctx context.Context, id int) (*model.Account, error)
|
|
Create(ctx context.Context, in model.AccountInput) (*model.Account, error)
|
|
Update(ctx context.Context, id int, in model.AccountInput) (*model.Account, error)
|
|
Delete(ctx context.Context, id int) error
|
|
TotalPatrimony(ctx context.Context) (float64, error)
|
|
}
|
|
|
|
type AccountService struct {
|
|
repo repository.AccountRepoWithYield
|
|
cdiSvc *CDIYieldService
|
|
}
|
|
|
|
func NewAccountService(repo repository.AccountRepoWithYield, cdiSvc *CDIYieldService) *AccountService {
|
|
return &AccountService{repo: repo, cdiSvc: cdiSvc}
|
|
}
|
|
|
|
func (s *AccountService) validate(in model.AccountInput) error {
|
|
if in.Name == "" {
|
|
return ErrAccountEmptyName
|
|
}
|
|
if !validAccountTypes[in.Type] {
|
|
return ErrAccountInvalidType
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// List returns accounts, applying CDI yield on-demand for cdi-type accounts.
|
|
func (s *AccountService) List(ctx context.Context) ([]model.Account, error) {
|
|
accounts, err := s.repo.List(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i := range accounts {
|
|
if accounts[i].YieldType == "cdi" {
|
|
// Apply CDI yield — errors are non-fatal (BCB API may be unavailable)
|
|
_ = s.cdiSvc.ApplyYield(ctx, &accounts[i])
|
|
}
|
|
}
|
|
|
|
// Re-fetch after yield transactions may have been created to get updated balances
|
|
return s.repo.List(ctx)
|
|
}
|
|
|
|
func (s *AccountService) Create(ctx context.Context, in model.AccountInput) (*model.Account, error) {
|
|
if err := s.validate(in); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.repo.Create(ctx, in)
|
|
}
|
|
|
|
func (s *AccountService) Update(ctx context.Context, id int, in model.AccountInput) (*model.Account, error) {
|
|
if err := s.validate(in); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.repo.Update(ctx, id, in)
|
|
}
|
|
|
|
func (s *AccountService) Delete(ctx context.Context, id int) error {
|
|
return s.repo.Delete(ctx, id)
|
|
}
|
|
|
|
func (s *AccountService) TotalPatrimony(ctx context.Context) (float64, error) {
|
|
return s.repo.TotalPatrimony(ctx)
|
|
}
|