Skip to content

Commit

Permalink
add Schedule AL page
Browse files Browse the repository at this point in the history
  • Loading branch information
ananthakumaran committed Nov 20, 2022
1 parent 06d936b commit 0eb6be4
Show file tree
Hide file tree
Showing 15 changed files with 288 additions and 15 deletions.
8 changes: 8 additions & 0 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ allocation_targets:
target: 60
accounts:
- Assets:Equity:*
schedule_al:
- code: bank
accounts:
- Assets:Checking
- code: share
accounts:
- Assets:Equity:*
- Assets:Debt:*
commodities:
- name: NIFTY
type: mutualfund
Expand Down
4 changes: 3 additions & 1 deletion docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@
- [Accounts](accounts.md)
- [Commodities](commodities.md)
- [Allocation Targets](allocation-targets.md)
- [Tax Harvesting](tax-harvesting.md)
- [Tax](tax.md)
- [Tax Harvesting](tax-harvesting.md)
- [Schedule AL](schedule-al.md)
36 changes: 36 additions & 0 deletions docs/src/schedule-al.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Schedule AL

As per the Indian Income tax law, citizens are obligated to report
their entire Assets and Liabilities if the total income exceeds ₹50
lakh. Paisa helps with the computation of the amount.

| code | Section | Details |
|-----------|----------------|------------------------------------------------------------------------------|
| immovable | A (1) | Immovable Assets |
| metal | B (1) (i) | Jewellery, bullion etc. |
| art | B (1) (ii) | Archaeological collections, drawings, painting, sculpture or any work of art |
| vechicle | B (1) (iii) | Vehicles, yachts, boats and aircrafts |
| bank | B (1) (iv) (a) | Financial assets: Bank (including all deposits) |
| share | B (1) (iv) (b) | Financial assets: Shares and securities |
| insurance | B (1) (iv) (c) | Financial assets: Insurance policies |
| loan | B (1) (iv) (d) | Financial assets: Loans and advances given |
| cash | B (1) (iv) (e) | Financial assets: Cash in hand |
| liability | C (1) | Liabilities |


All you need to do is to specify the accounts that belong to each
section. Paisa will compute the total as on the last day of the
previous financial year.


```yaml
schedule_al:
- code: bank
accounts:
- Assets:Checking
- code: share
accounts:
- Assets:Equity:*
- Assets:Debt:*

```
4 changes: 4 additions & 0 deletions docs/src/tax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Tax

Paisa provides few features to help with tax filing and tax
optimization.
69 changes: 69 additions & 0 deletions internal/accounting/accounting.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package accounting
import (
"time"

"path/filepath"

"github.com/ananthakumaran/paisa/internal/model/posting"
"github.com/samber/lo"
log "github.com/sirupsen/logrus"
)

type Balance struct {
Expand All @@ -26,3 +30,68 @@ func Register(postings []posting.Posting) []Balance {
}
return balances
}

func FilterByGlob(postings []posting.Posting, accounts []string) []posting.Posting {
return lo.Filter(postings, func(p posting.Posting, _ int) bool {
return lo.SomeBy(accounts, func(accountGlob string) bool {
match, err := filepath.Match(accountGlob, p.Account)
if err != nil {
log.Fatal("Invalid account glob used for filtering", accountGlob, err)
}
return match
})
})
}

func FIFO(postings []posting.Posting) []posting.Posting {
var available []posting.Posting
for _, p := range postings {
if p.Commodity == "INR" {
if p.Amount > 0 {
available = append(available, p)
} else {
amount := -p.Amount
for amount > 0 && len(available) > 0 {
first := available[0]
if first.Amount > amount {
first.AddAmount(-amount)
available[0] = first
amount = 0
} else {
amount -= first.Amount
available = available[1:]
}
}
}
} else {
if p.Quantity > 0 {
available = append(available, p)
} else {
quantity := -p.Quantity
for quantity > 0 && len(available) > 0 {
first := available[0]
if first.Quantity > quantity {
first.AddQuantity(-quantity)
available[0] = first
quantity = 0
} else {
quantity -= first.Quantity
available = available[1:]
}
}
}
}
}

return available
}

func CostBalance(postings []posting.Posting) float64 {
byAccount := lo.GroupBy(postings, func(p posting.Posting) string { return p.Account })
return lo.SumBy(lo.Values(byAccount), func(ps []posting.Posting) float64 {
return lo.SumBy(FIFO(ps), func(p posting.Posting) float64 {
return p.Amount
})
})

}
4 changes: 4 additions & 0 deletions internal/model/posting/posting.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func (p *Posting) Price() float64 {
return p.Amount / p.Quantity
}

func (p *Posting) AddAmount(amount float64) {
p.Amount += amount
}

func (p *Posting) AddQuantity(quantity float64) {
price := p.Price()
p.Quantity += quantity
Expand Down
4 changes: 2 additions & 2 deletions internal/query/posting.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (q *Query) NotLike(account string) *Query {

func (q *Query) All() []posting.Posting {
var postings []posting.Posting
result := q.context.Order("date " + q.order).Find(&postings)
result := q.context.Order("date " + q.order + ", amount desc").Find(&postings)
if result.Error != nil {
log.Fatal(result.Error)
}
Expand All @@ -51,7 +51,7 @@ func (q *Query) All() []posting.Posting {

func (q *Query) First() posting.Posting {
var posting posting.Posting
result := q.context.Order("date " + q.order).First(&posting)
result := q.context.Order("date " + q.order + ", amount desc").First(&posting)
if result.Error != nil {
log.Fatal(result.Error)
}
Expand Down
14 changes: 2 additions & 12 deletions internal/server/allocation.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package server

import (
"path/filepath"
"strings"
"time"

"github.com/samber/lo"
log "github.com/sirupsen/logrus"

"github.com/ananthakumaran/paisa/internal/accounting"
"github.com/ananthakumaran/paisa/internal/model/posting"
"github.com/ananthakumaran/paisa/internal/query"
"github.com/ananthakumaran/paisa/internal/service"
Expand Down Expand Up @@ -84,16 +83,7 @@ func computeAllocationTargets(postings []posting.Posting) []AllocationTarget {

func computeAllocationTarget(postings []posting.Posting, config AllocationTargetConfig, total float64) AllocationTarget {
date := time.Now()
postings = lo.Filter(postings, func(p posting.Posting, _ int) bool {
return lo.SomeBy(config.Accounts, func(accountGlob string) bool {
match, err := filepath.Match(accountGlob, p.Account)
if err != nil {
log.Fatal("Invalid account value used in target_allocations", accountGlob, err)
}
return match
})
})

postings = accounting.FilterByGlob(postings, config.Accounts)
aggregates := computeAggregate(postings, date)
currentTotal := lo.Reduce(postings, func(acc float64, p posting.Posting, _ int) float64 { return acc + p.MarketAmount }, 0.0)
return AllocationTarget{Name: config.Name, Target: config.Target, Current: (currentTotal / total) * 100, Aggregates: aggregates}
Expand Down
1 change: 1 addition & 0 deletions internal/server/harvest.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func computeCapitalGains(db *gorm.DB, account string, commodity c.Commodity, pos
if first.Quantity > quantity {
purchasePrice += quantity * first.Price()
first.AddQuantity(-quantity)
available[0] = first
quantity = 0
} else {
purchasePrice += quantity * first.Price()
Expand Down
78 changes: 78 additions & 0 deletions internal/server/schedule_al.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package server

import (
"time"

"github.com/ananthakumaran/paisa/internal/accounting"
"github.com/ananthakumaran/paisa/internal/model/posting"
"github.com/ananthakumaran/paisa/internal/query"
"github.com/ananthakumaran/paisa/internal/utils"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"github.com/spf13/viper"
"gorm.io/gorm"
)

type ScheduleALSection struct {
Code string `json:"code"`
Section string `json:"section"`
Details string `json:"details"`
}

var Sections []ScheduleALSection

func init() {
Sections = []ScheduleALSection{
{Code: "immovable", Section: "A (1)", Details: "Immovable Assets"},
{Code: "metal", Section: "B (1) (i)", Details: "Jewellery, bullion etc"},
{Code: "art", Section: "B (1) (ii)", Details: "Archaeological collections, drawings, painting, sculpture or any work of art"},
{Code: "vechicle", Section: "B (1) (ii)", Details: "Vehicles, yachts, boats and aircrafts"},
{Code: "bank", Section: "B (1) (iv) (a)", Details: "Financial assets: Bank (including all deposits)"},
{Code: "share", Section: "B (1) (iv) (b)", Details: "Financial assets: Shares and securities"},
{Code: "insurance", Section: "B (1) (iv) (c)", Details: "Financial assets: Insurance policies"},
{Code: "loan", Section: "B (1) (iv) (d)", Details: "Financial assets: Loans and advances given"},
{Code: "cash", Section: "B (1) (iv) (e)", Details: "Financial assets: Cash in hand"},
{Code: "liability", Section: "C (1)", Details: "Liabilities"},
}
}

type ScheduleALConfig struct {
Code string
Accounts []string
}

type ScheduleALEntry struct {
Section ScheduleALSection `json:"section"`
Amount float64 `json:"amount"`
}

func GetScheduleAL(db *gorm.DB) gin.H {
var configs []ScheduleALConfig
viper.UnmarshalKey("schedule_al", &configs)
now := time.Now()

postings := query.Init(db).Like("Assets:%").All()
time := utils.BeginningOfFinancialYear(now)
postings = lo.Filter(postings, func(p posting.Posting, _ int) bool { return p.Date.Before(time) })

entries := lo.Map(Sections, func(section ScheduleALSection, _ int) ScheduleALEntry {
config, found := lo.Find(configs, func(config ScheduleALConfig) bool {
return config.Code == section.Code
})

var amount float64

if found {
ps := accounting.FilterByGlob(postings, config.Accounts)
amount = accounting.CostBalance(ps)
} else {
amount = 0
}

return ScheduleALEntry{
Section: section,
Amount: amount,
}
})
return gin.H{"schedule_al_entries": entries, "date": time.AddDate(0, 0, -1)}
}
3 changes: 3 additions & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ func Listen(db *gorm.DB) {
router.GET("/api/harvest", func(c *gin.Context) {
c.JSON(200, GetHarvest(db))
})
router.GET("/api/schedule_al", func(c *gin.Context) {
c.JSON(200, GetScheduleAL(db))
})
router.GET("/api/diagnosis", func(c *gin.Context) {
c.JSON(200, GetDiagnosis(db))
})
Expand Down
2 changes: 2 additions & 0 deletions web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import income from "./income";
import expense from "./expense";
import price from "./price";
import harvest from "./harvest";
import scheduleAL from "./schedule_al";
import doctor from "./doctor";

const tabs = {
Expand All @@ -36,6 +37,7 @@ const tabs = {
expense: _.once(expense),
price: _.once(price),
harvest: _.once(harvest),
schedule_al: _.once(scheduleAL),
doctor: _.once(doctor)
};

Expand Down
31 changes: 31 additions & 0 deletions web/src/schedule_al.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as d3 from "d3";
import dayjs from "dayjs";
import { ajax, formatCurrency, ScheduleALEntry, setHtml } from "./utils";

export default async function () {
const { schedule_al_entries: scheduleALEntries, date: date } = await ajax(
"/api/schedule_al"
);

setHtml("schedule-al-date", dayjs(date).format("DD MMM YYYY"));
renderBreakdowns(scheduleALEntries);
}

function renderBreakdowns(scheduleALEntries: ScheduleALEntry[]) {
const tbody = d3.select(".d3-schedule-al");
const trs = tbody.selectAll("tr").data(Object.values(scheduleALEntries));

trs.exit().remove();
trs
.enter()
.append("tr")
.merge(trs as any)
.html((s) => {
return `
<td>${s.section.code}</td>
<td>${s.section.section}</td>
<td>${s.section.details}</td>
<td class='has-text-right'>${formatCurrency(s.amount)}</td>
`;
});
}
15 changes: 15 additions & 0 deletions web/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,24 @@ export interface Issue {
details: string;
}

export interface ScheduleALSection {
code: string;
section: string;
details: string;
}

export interface ScheduleALEntry {
section: ScheduleALSection;
amount: number;
}

export function ajax(
route: "/api/harvest"
): Promise<{ capital_gains: CapitalGain[] }>;
export function ajax(route: "/api/schedule_al"): Promise<{
schedule_al_entries: ScheduleALEntry[];
date: string;
}>;
export function ajax(route: "/api/diagnosis"): Promise<{ issues: Issue[] }>;
export function ajax(
route: "/api/investment"
Expand Down
Loading

0 comments on commit 0eb6be4

Please sign in to comment.