From 71d528f8273036ccd7d269369d9d13b18fe8750a Mon Sep 17 00:00:00 2001 From: Anantha Kumaran Date: Tue, 9 Aug 2022 17:53:18 +0530 Subject: [PATCH] render yearly summary card --- internal/server/investment.go | 95 +++++++++++++++++++++++++++++------ internal/utils/utils.go | 4 ++ web/src/investment.ts | 83 ++++++++++++++++++++++++++++-- web/src/utils.ts | 5 ++ web/static/index.html | 14 +++--- 5 files changed, 177 insertions(+), 24 deletions(-) diff --git a/internal/server/investment.go b/internal/server/investment.go index 3f59babb..f7805cc2 100644 --- a/internal/server/investment.go +++ b/internal/server/investment.go @@ -1,6 +1,9 @@ package server import ( + "strings" + "time" + "github.com/ananthakumaran/paisa/internal/model/posting" "github.com/ananthakumaran/paisa/internal/service" "github.com/ananthakumaran/paisa/internal/utils" @@ -8,44 +11,106 @@ import ( "github.com/samber/lo" log "github.com/sirupsen/logrus" "gorm.io/gorm" - "time" ) type YearlyCard struct { - StartDate time.Time `json:"start_date"` - EndDate time.Time `json:"end_date"` - Postings []posting.Posting `json:"postings"` + StartDate time.Time `json:"start_date"` + EndDate time.Time `json:"end_date"` + Postings []posting.Posting `json:"postings"` + GrossSalaryIncome float64 `json:"gross_salary_income"` + GrossOtherIncome float64 `json:"gross_other_income"` + NetTax float64 `json:"net_tax"` + NetIncome float64 `json:"net_income"` + NetInvestment float64 `json:"net_investment"` } func GetInvestment(db *gorm.DB) gin.H { var postings []posting.Posting - result := db.Where("account like ?", "Asset:%").Find(&postings) + var incomes []posting.Posting + var taxes []posting.Posting + result := db.Where("account like ? order by date asc", "Asset:%").Find(&postings) if result.Error != nil { log.Fatal(result.Error) } - postings = lo.Filter(postings, func(p posting.Posting, _ int) bool { return !service.IsInterest(db, p) }) - return gin.H{"postings": postings, "yearly_cards": computeYearlyCard(postings)} + + result = db.Where("account like ? order by date asc", "Income:%").Find(&incomes) + if result.Error != nil { + log.Fatal(result.Error) + } + + result = db.Where("account = ? order by date asc", "Tax").Find(&taxes) + if result.Error != nil { + log.Fatal(result.Error) + } + + var p posting.Posting + result = db.Order("date ASC").First(&p) + if result.Error != nil { + log.Fatal(result.Error) + } + + return gin.H{"postings": postings, "yearly_cards": computeYearlyCard(p.Date, postings, taxes, incomes)} } -func computeYearlyCard(postings []posting.Posting) []YearlyCard { +func computeYearlyCard(start time.Time, assets []posting.Posting, taxes []posting.Posting, incomes []posting.Posting) []YearlyCard { var yearlyCards []YearlyCard = make([]YearlyCard, 0) - if len(postings) == 0 { + if len(assets) == 0 { return yearlyCards } var p posting.Posting end := time.Now() - for start := utils.BeginningOfFinancialYear(postings[0].Date); start.Before(end); start = start.AddDate(1, 0, 0) { + for start = utils.BeginningOfFinancialYear(start); start.Before(end); start = start.AddDate(1, 0, 0) { yearEnd := utils.EndOfFinancialYear(start) - var currentMonthPostings []posting.Posting = make([]posting.Posting, 0) - for len(postings) > 0 && (postings[0].Date.Before(yearEnd) || postings[0].Date.Equal(start)) { - p, postings = postings[0], postings[1:] - currentMonthPostings = append(currentMonthPostings, p) + var currentYearPostings []posting.Posting = make([]posting.Posting, 0) + for len(assets) > 0 && utils.IsWithDate(assets[0].Date, start, yearEnd) { + p, assets = assets[0], assets[1:] + currentYearPostings = append(currentYearPostings, p) + } + + var currentYearTaxes []posting.Posting = make([]posting.Posting, 0) + for len(taxes) > 0 && utils.IsWithDate(taxes[0].Date, start, yearEnd) { + p, taxes = taxes[0], taxes[1:] + currentYearTaxes = append(currentYearTaxes, p) + } + + netTax := lo.SumBy(currentYearTaxes, func(p posting.Posting) float64 { return p.Amount }) + + var currentYearIncomes []posting.Posting = make([]posting.Posting, 0) + for len(incomes) > 0 && utils.IsWithDate(incomes[0].Date, start, yearEnd) { + p, incomes = incomes[0], incomes[1:] + currentYearIncomes = append(currentYearIncomes, p) } - yearlyCards = append(yearlyCards, YearlyCard{StartDate: start, EndDate: yearEnd, Postings: currentMonthPostings}) + grossSalaryIncome := lo.SumBy(currentYearIncomes, func(p posting.Posting) float64 { + if strings.HasPrefix(p.Account, "Income:Salary") { + return -p.Amount + } else { + return 0 + } + }) + grossOtherIncome := lo.SumBy(currentYearIncomes, func(p posting.Posting) float64 { + if !strings.HasPrefix(p.Account, "Income:Salary") { + return -p.Amount + } else { + return 0 + } + }) + + netInvestment := lo.SumBy(currentYearPostings, func(p posting.Posting) float64 { return p.Amount }) + + yearlyCards = append(yearlyCards, YearlyCard{ + StartDate: start, + EndDate: yearEnd, + Postings: currentYearPostings, + NetTax: netTax, + GrossSalaryIncome: grossSalaryIncome, + GrossOtherIncome: grossOtherIncome, + NetIncome: grossSalaryIncome + grossOtherIncome - netTax, + NetInvestment: netInvestment, + }) } return yearlyCards diff --git a/internal/utils/utils.go b/internal/utils/utils.go index c181de71..9672aa0d 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -35,3 +35,7 @@ func BeginningOfMonth(date time.Time) time.Time { func EndOfMonth(date time.Time) time.Time { return date.AddDate(0, 1, -date.Day()) } + +func IsWithDate(date time.Time, start time.Time, end time.Time) bool { + return (date.Equal(start) || date.After(start)) && (date.Before(end) || date.Equal(end)) +} diff --git a/web/src/investment.ts b/web/src/investment.ts index 961d1b60..548ff578 100644 --- a/web/src/investment.ts +++ b/web/src/investment.ts @@ -7,6 +7,7 @@ import { forEachMonth, formatCurrency, formatCurrencyCrude, + formatFloat, Posting, secondName, skipTicks, @@ -25,6 +26,13 @@ export default async function () { }); renderMonthlyInvestmentTimeline(postings); renderYearlyInvestmentTimeline(yearlyCards); + renderYearlyCards(yearlyCards); +} + +function financialYear(card: YearlyCard) { + return `${card.start_date_timestamp.format( + "YYYY" + )}-${card.end_date_timestamp.format("YYYY")}`; } function renderMonthlyInvestmentTimeline(postings: Posting[]) { @@ -273,9 +281,7 @@ function renderYearlyInvestmentTimeline(yearlyCards: YearlyCard[]) { points.push( _.merge( { - year: `${card.start_date_timestamp.format( - "YYYY" - )}-${card.end_date_timestamp.format("YYYY")}`, + year: financialYear(card), postings: postings }, defaultValues, @@ -384,3 +390,74 @@ function renderYearlyInvestmentTimeline(yearlyCards: YearlyCard[]) { svg.select(".legendOrdinal").call(legendOrdinal as any); } + +function renderYearlyCards(yearlyCards: YearlyCard[]) { + const id = "#d3-yearly-investment-cards"; + const root = d3.select(id); + + const card = root + .selectAll("div.column") + .data(_.reverse(yearlyCards)) + .enter() + .append("div") + .attr("class", "column is-4") + .append("div") + .attr("class", "card"); + + card + .append("header") + .attr("class", "card-header") + .append("p") + .attr("class", "card-header-title") + .text((c) => financialYear(c)); + + card + .append("div") + .attr("class", "card-content p-1") + .append("div") + .attr("class", "content") + .html((card) => { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Gross Salary Income${formatCurrency( + card.gross_salary_income + )}
Gross Other Income${formatCurrency( + card.gross_other_income + )}
Net Tax${formatCurrency( + card.net_tax + )}
Net Income${formatCurrency( + card.net_income + )}
Net Investment${formatCurrency( + card.net_investment + )}
Savings Rate${formatFloat( + (card.net_investment / card.net_income) * 100 + )}
+`; + }); +} diff --git a/web/src/utils.ts b/web/src/utils.ts index 28b3bdcd..3a765719 100644 --- a/web/src/utils.ts +++ b/web/src/utils.ts @@ -73,6 +73,11 @@ export interface YearlyCard { start_date: string; end_date: string; postings: Posting[]; + net_tax: number; + gross_salary_income: number; + gross_other_income: number; + net_income: number; + net_investment: number; start_date_timestamp: dayjs.Dayjs; end_date_timestamp: dayjs.Dayjs; diff --git a/web/static/index.html b/web/static/index.html index 6b59f419..cdb23ad3 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -108,15 +108,17 @@
- -
-
-
-
-
+
+ +
+

Financial Year Investment Timeline

+
+
+
+