Files
carvalho-finances/apps/api/internal/service/dashboard.go
T
Mlcavalho1andClaude Sonnet 4.6 79f4a62a1b feat(#36): receitas recorrentes com confirmação de salário até dia 5
- Migration 009: coluna `type` em recurring_expenses + tabela recurring_late
- API Go: tipo income/expense em CRUD; endpoints POST /confirm e /late
- Dashboard: campo pending_income_recurrings na resposta
- Frontend: RecurringView com seções RECEITAS e DESPESAS separadas
- HomeView: widget de confirmação (dia 1-5) e badge SALÁRIO PENDENTE (pós dia 5)
- Testes unitários: 4 novos casos (income covered, late, confirm, reject expense)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-27 15:00:28 -03:00

111 lines
2.7 KiB
Go

package service
import (
"context"
"financeiro-carvalho/internal/model"
)
type DashboardRepo interface {
MonthlySummary(ctx context.Context, month string) (income, expenses float64, err error)
ByCategory(ctx context.Context, month string) ([]model.CategoryTotal, error)
MonthlyEvolution(ctx context.Context, month string) ([]model.MonthEvolution, error)
RecentTransactions(ctx context.Context, month string) ([]model.RecentTransaction, error)
}
type PatrimonySource interface {
TotalPatrimony(ctx context.Context) (float64, error)
}
type DashboardService struct {
repo DashboardRepo
recurrSvc *RecurringService
patrimony PatrimonySource
}
func NewDashboardService(repo DashboardRepo, recurrSvc *RecurringService, patrimony PatrimonySource) *DashboardService {
return &DashboardService{repo: repo, recurrSvc: recurrSvc, patrimony: patrimony}
}
func (s *DashboardService) Get(ctx context.Context, month string) (*model.DashboardData, error) {
income, expenses, err := s.repo.MonthlySummary(ctx, month)
if err != nil {
return nil, err
}
byCategory, err := s.repo.ByCategory(ctx, month)
if err != nil {
return nil, err
}
evolution, err := s.repo.MonthlyEvolution(ctx, month)
if err != nil {
return nil, err
}
recent, err := s.repo.RecentTransactions(ctx, month)
if err != nil {
return nil, err
}
var savingsPct float64
if income > 0 {
savingsPct = (income - expenses) / income * 100
}
statuses, err := s.recurrSvc.MonthlyStatus(ctx, month)
if err != nil {
return nil, err
}
pending := 0
var pendingIncome []model.PendingIncome
for _, st := range statuses {
if st.Type == "income" {
if !st.Covered {
pendingIncome = append(pendingIncome, model.PendingIncome{
ID: st.ID,
Name: st.Name,
ExpectedAmount: st.ExpectedAmount,
DayOfMonth: st.DayOfMonth,
Late: st.Late,
})
}
} else {
if !st.Covered {
pending++
}
}
}
if pendingIncome == nil {
pendingIncome = []model.PendingIncome{}
}
if byCategory == nil {
byCategory = []model.CategoryTotal{}
}
if evolution == nil {
evolution = []model.MonthEvolution{}
}
if recent == nil {
recent = []model.RecentTransaction{}
}
patrimony, err := s.patrimony.TotalPatrimony(ctx)
if err != nil {
return nil, err
}
return &model.DashboardData{
Month: month,
TotalIncome: income,
TotalExpenses: expenses,
SavingsPct: savingsPct,
TotalPatrimony: patrimony,
ByCategory: byCategory,
MonthlyEvolution: evolution,
RecentTransactions: recent,
PendingRecurring: pending,
PendingIncomeRecurrings: pendingIncome,
}, nil
}