feat: show report on payment method
All checks were successful
Deploy / build-and-deploy (push) Successful in 2m37s
All checks were successful
Deploy / build-and-deploy (push) Successful in 2m37s
This commit is contained in:
parent
b5295d1c73
commit
047e392ac3
4 changed files with 70 additions and 0 deletions
|
|
@ -16,8 +16,16 @@ type MonthlySpending struct {
|
|||
Total decimal.Decimal `db:"total"`
|
||||
}
|
||||
|
||||
type PaymentMethodExpenseSummary struct {
|
||||
PaymentMethodID string `db:"payment_method_id"`
|
||||
PaymentMethodName string `db:"payment_method_name"`
|
||||
PaymentMethodType string `db:"payment_method_type"`
|
||||
TotalAmount decimal.Decimal `db:"total_amount"`
|
||||
}
|
||||
|
||||
type SpendingReport struct {
|
||||
ByTag []*TagExpenseSummary
|
||||
ByPaymentMethod []*PaymentMethodExpenseSummary
|
||||
DailySpending []*DailySpending
|
||||
MonthlySpending []*MonthlySpending
|
||||
TopExpenses []*ExpenseWithTagsAndMethod
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type ExpenseRepository interface {
|
|||
GetMonthlySpending(spaceID string, from, to time.Time) ([]*model.MonthlySpending, error)
|
||||
GetTopExpenses(spaceID string, from, to time.Time, limit int) ([]*model.Expense, error)
|
||||
GetIncomeVsExpenseSummary(spaceID string, from, to time.Time) (decimal.Decimal, decimal.Decimal, error)
|
||||
GetExpensesByPaymentMethod(spaceID string, from, to time.Time) ([]*model.PaymentMethodExpenseSummary, error)
|
||||
}
|
||||
|
||||
type expenseRepository struct {
|
||||
|
|
@ -324,3 +325,23 @@ func (r *expenseRepository) GetIncomeVsExpenseSummary(spaceID string, from, to t
|
|||
}
|
||||
return income, expenseTotal, nil
|
||||
}
|
||||
|
||||
func (r *expenseRepository) GetExpensesByPaymentMethod(spaceID string, from, to time.Time) ([]*model.PaymentMethodExpenseSummary, error) {
|
||||
var summaries []*model.PaymentMethodExpenseSummary
|
||||
query := `
|
||||
SELECT COALESCE(pm.id, 'cash') as payment_method_id,
|
||||
COALESCE(pm.name, 'Cash') as payment_method_name,
|
||||
COALESCE(pm.type, 'cash') as payment_method_type,
|
||||
SUM(CAST(e.amount AS DECIMAL)) as total_amount
|
||||
FROM expenses e
|
||||
LEFT JOIN payment_methods pm ON e.payment_method_id = pm.id
|
||||
WHERE e.space_id = $1 AND e.type = 'expense' AND e.date >= $2 AND e.date <= $3
|
||||
GROUP BY pm.id, pm.name, pm.type
|
||||
ORDER BY total_amount DESC;
|
||||
`
|
||||
err := r.db.Select(&summaries, query, spaceID, from, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return summaries, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ func (s *ReportService) GetSpendingReport(spaceID string, from, to time.Time) (*
|
|||
}
|
||||
}
|
||||
|
||||
byPaymentMethod, err := s.expenseRepo.GetExpensesByPaymentMethod(spaceID, from, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalIncome, totalExpenses, err := s.expenseRepo.GetIncomeVsExpenseSummary(spaceID, from, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -68,6 +73,7 @@ func (s *ReportService) GetSpendingReport(spaceID string, from, to time.Time) (*
|
|||
|
||||
return &model.SpendingReport{
|
||||
ByTag: byTag,
|
||||
ByPaymentMethod: byPaymentMethod,
|
||||
DailySpending: daily,
|
||||
MonthlySpending: monthly,
|
||||
TopExpenses: topWithTags,
|
||||
|
|
|
|||
|
|
@ -123,6 +123,41 @@ templ ReportCharts(spaceID string, report *model.SpendingReport, from, to time.T
|
|||
<p class="text-sm text-muted-foreground">No tagged expenses in this period.</p>
|
||||
</div>
|
||||
}
|
||||
// Spending by Payment Method (Doughnut chart)
|
||||
if len(report.ByPaymentMethod) > 0 {
|
||||
<div class="border rounded-lg p-4 bg-card text-card-foreground min-w-0 overflow-hidden">
|
||||
<h3 class="font-semibold mb-2">Spending by Payment Method</h3>
|
||||
{{
|
||||
pmLabels := make([]string, len(report.ByPaymentMethod))
|
||||
pmData := make([]float64, len(report.ByPaymentMethod))
|
||||
pmColors := make([]string, len(report.ByPaymentMethod))
|
||||
for i, pm := range report.ByPaymentMethod {
|
||||
pmLabels[i] = pm.PaymentMethodName
|
||||
pmData[i] = pm.TotalAmount.InexactFloat64()
|
||||
pmColors[i] = defaultChartColors[i%len(defaultChartColors)]
|
||||
}
|
||||
}}
|
||||
@chart.Chart(chart.Props{
|
||||
Variant: chart.VariantDoughnut,
|
||||
ShowLegend: true,
|
||||
Data: chart.Data{
|
||||
Labels: pmLabels,
|
||||
Datasets: []chart.Dataset{
|
||||
{
|
||||
Label: "Spending",
|
||||
Data: pmData,
|
||||
BackgroundColor: pmColors,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
</div>
|
||||
} else {
|
||||
<div class="border rounded-lg p-4 bg-card text-card-foreground min-w-0">
|
||||
<h3 class="font-semibold mb-2">Spending by Payment Method</h3>
|
||||
<p class="text-sm text-muted-foreground">No payment method data in this period.</p>
|
||||
</div>
|
||||
}
|
||||
// Spending Over Time (Bar chart)
|
||||
if len(report.DailySpending) > 0 || len(report.MonthlySpending) > 0 {
|
||||
<div class="border rounded-lg p-4 bg-card text-card-foreground min-w-0 overflow-hidden">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue