feat(#35): rendimento CDI automático em contas de investimento

- 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]>
This commit is contained in:
2026-05-27 15:06:05 -03:00
co-authored by Claude Sonnet 4.6
parent 79f4a62a1b
commit ee6cd9f37e
9 changed files with 249 additions and 27 deletions
+12 -5
View File
@@ -37,11 +37,18 @@ func (m *mockAccountRepo) Update(_ context.Context, id int, in model.AccountInpu
}
return nil, nil
}
func (m *mockAccountRepo) Delete(_ context.Context, _ int) error { return nil }
func (m *mockAccountRepo) TotalPatrimony(_ context.Context) (float64, error) { return 0, nil }
func (m *mockAccountRepo) Delete(_ context.Context, _ int) error { return nil }
func (m *mockAccountRepo) TotalPatrimony(_ context.Context) (float64, error) { return 0, nil }
func (m *mockAccountRepo) UpdateLastYieldDate(_ context.Context, _ int, _ string) error { return nil }
func newAccountSvc() *service.AccountService {
repo := &mockAccountRepo{}
cdiSvc := service.NewCDIYieldService(repo, &mockTxRepo{})
return service.NewAccountService(repo, cdiSvc)
}
func TestCreateAccount_EmptyName(t *testing.T) {
svc := service.NewAccountService(&mockAccountRepo{})
svc := newAccountSvc()
_, err := svc.Create(context.Background(), model.AccountInput{Name: "", Type: "checking", InitialBalance: 0})
if err != service.ErrAccountEmptyName {
t.Fatalf("expected ErrAccountEmptyName, got %v", err)
@@ -49,7 +56,7 @@ func TestCreateAccount_EmptyName(t *testing.T) {
}
func TestCreateAccount_InvalidType(t *testing.T) {
svc := service.NewAccountService(&mockAccountRepo{})
svc := newAccountSvc()
_, err := svc.Create(context.Background(), model.AccountInput{Name: "Nubank", Type: "bitcoin"})
if err != service.ErrAccountInvalidType {
t.Fatalf("expected ErrAccountInvalidType, got %v", err)
@@ -57,7 +64,7 @@ func TestCreateAccount_InvalidType(t *testing.T) {
}
func TestCreateAccount_OK(t *testing.T) {
svc := service.NewAccountService(&mockAccountRepo{})
svc := newAccountSvc()
a, err := svc.Create(context.Background(), model.AccountInput{Name: "Nubank", Type: "checking", InitialBalance: 1000})
if err != nil {
t.Fatal(err)