Skip to content

Commit

Permalink
[budget] add option to disable rollover
Browse files Browse the repository at this point in the history
see #84
  • Loading branch information
ananthakumaran committed Nov 3, 2023
1 parent 24b9fee commit 43b1093
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 7 deletions.
4 changes: 3 additions & 1 deletion docs/reference/budget.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ next month, assuming you haven't made any further transaction.

![Next month Budget](/images/budget-4.png)

You can see a new element in the UI called Rollover. This is basically
You can see a new element in the UI called Rollover[^1]. This is basically
the amount you have budgeted last month, but haven't spent. This will
automatically rollover to the next month. That's pretty much it.

Expand All @@ -126,3 +126,5 @@ To recap, there are just two things you need to do.
you get your salary.

2) Adjust your budget as you spend and make sure there is no deficit.

[^1]: If you prefer to not have rollover feature, it can be disabled in the [configuration](../reference/config.md) page.
6 changes: 6 additions & 0 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ locale: en-IN
# OPTIONAL, DEFAULT: 4
financial_year_starting_month: 4

## Budget
budget:
# Rollover unspent money to next month
# OPTIONAL, ENUM: yes, no DEFAULT: yes
rollover: "yes"

## Retirement
retirement:
# Safe Withdrawal Rate
Expand Down
14 changes: 14 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ const (
Unknown CommodityType = "unknown"
)

type BoolType string

const (
Yes BoolType = "yes"
No BoolType = "no"
)

type ImportTemplate struct {
Name string `json:"name" yaml:"name"`
Content string `json:"content" yaml:"content"`
Expand Down Expand Up @@ -74,6 +81,10 @@ type ScheduleAL struct {
Accounts []string `json:"accounts" yaml:"accounts"`
}

type Budget struct {
Rollover BoolType `json:"rollover" yaml:"rollover"`
}

type AllocationTarget struct {
Name string `json:"name" yaml:"name"`
Target float64 `json:"target" yaml:"target"`
Expand All @@ -92,6 +103,8 @@ type Config struct {

Retirement Retirement `json:"retirement" yaml:"retirement"`

Budget Budget `json:"budget" yaml:"budget"`

ScheduleALs []ScheduleAL `json:"schedule_al" yaml:"schedule_al"`

AllocationTargets []AllocationTarget `json:"allocation_targets" yaml:"allocation_targets"`
Expand All @@ -113,6 +126,7 @@ var defaultConfig = Config{
DisplayPrecision: 0,
Locale: "en-IN",
Retirement: Retirement{SWR: 4, Savings: []string{"Assets:*"}, Expenses: []string{"Expenses:*"}, YearlyExpenses: 0},
Budget: Budget{Rollover: Yes},
FinancialYearStartingMonth: 4,
ScheduleALs: []ScheduleAL{},
AllocationTargets: []AllocationTarget{},
Expand Down
13 changes: 13 additions & 0 deletions internal/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@
},
"additionalProperties": false
},
"budget": {
"description": "Budget configuration",
"type": "object",
"properties": {
"rollover": {
"ui:widget": "boolean",
"type": "string",
"description": "Rollover unspent money to next month",
"enum": ["", "yes", "no"]
}
},
"additionalProperties": false
},
"schedule_al": {
"description": "Schedule AL configuration",
"type": "array",
Expand Down
26 changes: 20 additions & 6 deletions internal/server/budget.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/ananthakumaran/paisa/internal/accounting"
"github.com/ananthakumaran/paisa/internal/config"
"github.com/ananthakumaran/paisa/internal/model/posting"
"github.com/ananthakumaran/paisa/internal/query"
"github.com/ananthakumaran/paisa/internal/utils"
Expand Down Expand Up @@ -59,6 +60,8 @@ func computeBudet(db *gorm.DB, forecastPostings, expensesPostings []posting.Post
budgetsByMonth := make(map[string]Budget)
balance := make(map[string]decimal.Decimal)

currentMonth := lo.Must(time.Parse("2006-01", utils.Now().Format("2006-01")))

if len(forecastPostings) > 0 {
start := utils.BeginningOfMonth(forecastPostings[0].Date)
end := utils.EndOfMonth(forecastPostings[len(forecastPostings)-1].Date)
Expand All @@ -84,7 +87,7 @@ func computeBudet(db *gorm.DB, forecastPostings, expensesPostings []posting.Post
es = []posting.Posting{}
}

budget := buildBudget(date, account, balance[account], fs, es)
budget := buildBudget(date, account, balance[account], fs, es, date.Before(currentMonth))
if budget.Available.IsPositive() {
balance[account] = budget.Available
} else {
Expand All @@ -110,8 +113,8 @@ func computeBudet(db *gorm.DB, forecastPostings, expensesPostings []posting.Post
return decimal.Zero
})

endOfMonthBalance := checkingBalance.Sub(availableThisMonth)
availableForBudgeting = endOfMonthBalance
availableForBudgeting = availableForBudgeting.Sub(availableThisMonth)
endOfMonthBalance := availableForBudgeting

budgetsByMonth[month] = Budget{
Date: date,
Expand All @@ -130,15 +133,26 @@ func computeBudet(db *gorm.DB, forecastPostings, expensesPostings []posting.Post
}
}

func buildBudget(date time.Time, account string, balance decimal.Decimal, forecasts []posting.Posting, expenses []posting.Posting) AccountBudget {
func buildBudget(date time.Time, account string, balance decimal.Decimal, forecasts []posting.Posting, expenses []posting.Posting, past bool) AccountBudget {
forecast := accounting.CostSum(forecasts)
actual := accounting.CostSum(expenses)

rollover := decimal.Zero
available := forecast.Sub(actual)
if past {
available = decimal.Zero
}
if config.GetConfig().Budget.Rollover == config.Yes {
rollover = balance
available = balance.Add(forecast.Sub(actual))
}

return AccountBudget{
Account: account,
Forecast: forecast,
Actual: actual,
Rollover: balance,
Available: balance.Add(forecast.Sub(actual)),
Rollover: rollover,
Available: available,
Date: date,
Expenses: expenses,
}
Expand Down
20 changes: 20 additions & 0 deletions src/lib/components/JsonSchemaForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,26 @@
</div>
</div>
</div>
{:else if schema["ui:widget"] == "boolean"}
<div class="field is-horizontal">
<div class="field-label is-small">
<label for="" data-tippy-content={documentation(schema)} class="label">{title}</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<label class="radio">
<input value="yes" bind:group={value} type="radio" name="yes" />
Yes
</label>
<label class="radio">
<input value="no" bind:group={value} type="radio" name="no" />
No
</label>
</div>
</div>
</div>
</div>
{:else if schema.type === "string" || _.isEqual(schema.type, ["string", "integer"])}
<div class="field is-horizontal">
<div class="field-label is-small">
Expand Down

0 comments on commit 43b1093

Please sign in to comment.