diff --git a/internal/server/income_statement.go b/internal/server/income_statement.go index cd511d4e..00860a1f 100644 --- a/internal/server/income_statement.go +++ b/internal/server/income_statement.go @@ -36,7 +36,8 @@ type RunningBalance struct { func GetIncomeStatement(db *gorm.DB) gin.H { postings := query.Init(db).All() statements := computeStatement(db, postings) - return gin.H{"yearly": statements} + monthlyStatements := computeMonthlyStatement(db, postings) + return gin.H{"yearly": statements, "monthly": monthlyStatements} } func computeStatement(db *gorm.DB, postings []posting.Posting) map[string]IncomeStatement { @@ -132,6 +133,123 @@ func computeStatement(db *gorm.DB, postings []posting.Posting) map[string]Income return statements } +func computeMonthlyStatement(db *gorm.DB, postings []posting.Posting) map[string]map[string]IncomeStatement { + monthlyStatements := make(map[string]map[string]IncomeStatement) + + grouped := utils.GroupByFY(postings) + fys := lo.Keys(grouped) + sort.Strings(fys) + + for _, fy := range fys { + fyPostings := grouped[fy] + monthlyStatements[fy] = make(map[string]IncomeStatement) + + // Group postings by month within the financial year + monthlyGrouped := utils.GroupByMonth(fyPostings) + + // Get all months in the financial year + start, end := utils.ParseFY(fy) + current := start + + runnings := make(map[string]RunningBalance) + startingBalance := decimal.Zero + + for current.Before(end) || current.Equal(end) { + monthKey := current.Format("2006-01") + incomeStatement := IncomeStatement{} + incomeStatement.Date = current + incomeStatement.StartingBalance = startingBalance + incomeStatement.Income = make(map[string]decimal.Decimal) + incomeStatement.Interest = make(map[string]decimal.Decimal) + incomeStatement.Equity = make(map[string]decimal.Decimal) + incomeStatement.Pnl = make(map[string]decimal.Decimal) + incomeStatement.Liabilities = make(map[string]decimal.Decimal) + incomeStatement.Tax = make(map[string]decimal.Decimal) + incomeStatement.Expenses = make(map[string]decimal.Decimal) + + monthPostings := monthlyGrouped[monthKey] + + for _, p := range monthPostings { + category := utils.FirstName(p.Account) + + switch category { + case "Income": + if service.IsCapitalGains(p) { + sourceAccount := service.CapitalGainsSourceAccount(p.Account) + r := runnings[sourceAccount] + if r.quantity == nil { + r.quantity = make(map[string]decimal.Decimal) + } + r.amount = r.amount.Add(p.Amount) + runnings[sourceAccount] = r + } else if strings.HasPrefix(p.Account, "Income:Interest") { + incomeStatement.Interest[p.Account] = incomeStatement.Interest[p.Account].Add(p.Amount) + } else { + incomeStatement.Income[p.Account] = incomeStatement.Income[p.Account].Add(p.Amount) + } + case "Equity": + incomeStatement.Equity[p.Account] = incomeStatement.Equity[p.Account].Add(p.Amount) + case "Expenses": + if strings.HasPrefix(p.Account, "Expenses:Tax") { + incomeStatement.Tax[p.Account] = incomeStatement.Tax[p.Account].Add(p.Amount) + } else { + incomeStatement.Expenses[p.Account] = incomeStatement.Expenses[p.Account].Add(p.Amount) + } + case "Liabilities": + incomeStatement.Liabilities[p.Account] = incomeStatement.Liabilities[p.Account].Add(p.Amount) + case "Assets": + r := runnings[p.Account] + if r.quantity == nil { + r.quantity = make(map[string]decimal.Decimal) + } + r.amount = r.amount.Add(p.Amount) + r.quantity[p.Commodity] = r.quantity[p.Commodity].Add(p.Quantity) + runnings[p.Account] = r + default: + // ignore + } + } + + // Calculate PnL for this month + monthEndDate := current.AddDate(0, 1, -1) // Last day of current month + if monthEndDate.After(end) { + monthEndDate = end + } + + for account, r := range runnings { + diff := r.amount.Neg() + for commodity, quantity := range r.quantity { + diff = diff.Add(service.GetPrice(db, commodity, quantity, monthEndDate)) + } + if !diff.IsZero() { + incomeStatement.Pnl[account] = diff + } + + r.amount = r.amount.Add(diff) + runnings[account] = r + } + + startingBalance = startingBalance. + Add(sumBalance(incomeStatement.Income).Neg()). + Add(sumBalance(incomeStatement.Interest).Neg()). + Add(sumBalance(incomeStatement.Equity).Neg()). + Add(sumBalance(incomeStatement.Tax).Neg()). + Add(sumBalance(incomeStatement.Expenses).Neg()). + Add(sumBalance(incomeStatement.Pnl)). + Add(sumBalance(incomeStatement.Liabilities).Neg()) + + incomeStatement.EndingBalance = startingBalance + + monthlyStatements[fy][monthKey] = incomeStatement + + // Move to next month + current = current.AddDate(0, 1, 0) + } + } + + return monthlyStatements +} + func sumBalance(breakdown map[string]decimal.Decimal) decimal.Decimal { total := decimal.Zero for k, v := range breakdown { diff --git a/src/lib/components/MultiSelectMonthPicker.svelte b/src/lib/components/MultiSelectMonthPicker.svelte new file mode 100644 index 00000000..b22566c4 --- /dev/null +++ b/src/lib/components/MultiSelectMonthPicker.svelte @@ -0,0 +1,272 @@ + + + + + + + diff --git a/src/lib/components/Navbar.svelte b/src/lib/components/Navbar.svelte index 96477bab..b3e4d09c 100644 --- a/src/lib/components/Navbar.svelte +++ b/src/lib/components/Navbar.svelte @@ -1,7 +1,15 @@