package service import ( "context" "financeiro-carvalho/internal/model" ) type DashboardRepo interface { MonthlySummary(ctx context.Context, month string) (income, expenses float64, err error) TotalTaxes(ctx context.Context, month string) (float64, 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 billSvc *CreditBillService pendingBillSvc *PendingBillService settingsSvc *SettingsService } func NewDashboardService(repo DashboardRepo, recurrSvc *RecurringService, patrimony PatrimonySource, billSvc *CreditBillService, pendingBillSvc *PendingBillService, settingsSvc *SettingsService) *DashboardService { return &DashboardService{repo: repo, recurrSvc: recurrSvc, patrimony: patrimony, billSvc: billSvc, pendingBillSvc: pendingBillSvc, settingsSvc: settingsSvc} } 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 } taxes, err := s.repo.TotalTaxes(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 } // savings_pct uses net income (gross - taxes) as denominator (Option B) var savingsPct float64 netIncome := income - taxes if netIncome > 0 { savingsPct = (income - expenses) / netIncome * 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 } currentBills, _ := s.billSvc.currentBillsForDashboard(ctx) if currentBills == nil { currentBills = []model.CreditBill{} } pendingBillImports, _ := s.pendingBillSvc.List(ctx) if pendingBillImports == nil { pendingBillImports = []model.PendingBillImport{} } settings, _ := s.settingsSvc.Get(ctx) goalPct := 40.0 if settings != nil { goalPct = settings.SavingsGoalPct } return &model.DashboardData{ Month: month, TotalIncome: income, TotalExpenses: expenses, SavingsPct: savingsPct, SavingsGoalPct: goalPct, TotalPatrimony: patrimony, ByCategory: byCategory, MonthlyEvolution: evolution, RecentTransactions: recent, PendingRecurring: pending, PendingIncomeRecurrings: pendingIncome, CurrentBills: currentBills, PendingBillImports: pendingBillImports, }, nil }