Skip to content

Commit

Permalink
render yearly summary card
Browse files Browse the repository at this point in the history
  • Loading branch information
ananthakumaran committed Aug 9, 2022
1 parent 101e486 commit 71d528f
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 24 deletions.
95 changes: 80 additions & 15 deletions internal/server/investment.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,116 @@
package server

import (
"strings"
"time"

"github.com/ananthakumaran/paisa/internal/model/posting"
"github.com/ananthakumaran/paisa/internal/service"
"github.com/ananthakumaran/paisa/internal/utils"
"github.com/gin-gonic/gin"
"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
Expand Down
4 changes: 4 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
83 changes: 80 additions & 3 deletions web/src/investment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
forEachMonth,
formatCurrency,
formatCurrencyCrude,
formatFloat,
Posting,
secondName,
skipTicks,
Expand All @@ -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[]) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 `
<table class="table is-narrow is-fullwidth is-size-7">
<tbody>
<tr>
<td>Gross Salary Income</td>
<td class='has-text-right has-text-weigh-bold'>${formatCurrency(
card.gross_salary_income
)}</td>
</tr>
<tr>
<td>Gross Other Income</td>
<td class='has-text-right has-text-weigh-bold'>${formatCurrency(
card.gross_other_income
)}</td>
</tr>
<tr>
<td>Net Tax</td>
<td class='has-text-right has-text-weigh-bold'>${formatCurrency(
card.net_tax
)}</td>
</tr>
<tr>
<td>Net Income</td>
<td class='has-text-right has-text-weigh-bold'>${formatCurrency(
card.net_income
)}</td>
</tr>
<tr>
<td>Net Investment</td>
<td class='has-text-right has-text-weigh-bold'>${formatCurrency(
card.net_investment
)}</td>
</tr>
<tr>
<td>Savings Rate</td>
<td class='has-text-right has-text-weigh-bold'>${formatFloat(
(card.net_investment / card.net_income) * 100
)}</td>
</tr>
</tbody>
</table>
`;
});
}
5 changes: 5 additions & 0 deletions web/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 8 additions & 6 deletions web/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,17 @@
<div class="container is-fluid">
<div class="columns">
<div class="column is-6">
<svg id="d3-yearly-investment-timeline" width="100%"></svg>
</div>
</div>
<div class="columns">
<div class="column is-6 has-text-centered">
<div>
<div class="p-3">
<svg id="d3-yearly-investment-timeline" width="100%"></svg>
</div>
<div class="p-3 has-text-centered">
<p class="heading">Financial Year Investment Timeline</p>
</div>
</div>
<div class="column is-6">
<div class="columns is-mobile is-flex-wrap-wrap" id="d3-yearly-investment-cards">
</div>
</div>
</div>
</div>
</section>
Expand Down

0 comments on commit 71d528f

Please sign in to comment.