Skip to content

Commit

Permalink
[credit_card] show year wise spend
Browse files Browse the repository at this point in the history
  • Loading branch information
ananthakumaran committed Jan 27, 2024
1 parent 46b4820 commit 03d4fe2
Show file tree
Hide file tree
Showing 19 changed files with 288 additions and 43 deletions.
2 changes: 2 additions & 0 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,6 @@ credit_cards:
# Required, the network of the card
number: "0007"
# Required, the last 4 digits of the card number
expiration_date: "2029-05-01"
# Required, the expiration date of the card
```
2 changes: 2 additions & 0 deletions docs/reference/credit-cards.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ credit_cards:
due_day: 20 #(4)!
network: visa #(5)!
number: "0007" #(6)!
expiration_date: "2029-05-01" #(7)!
```
1. Account name
Expand All @@ -25,6 +26,7 @@ credit_cards:
4. The day of the month when the payment is due
5. The network of the card
6. The last 4 digits of the card number
7. The expiration date of the card
The above configuration can be done from the `More > Configuration`
page. Expand the `Credit Cards` section and click
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type CreditCard struct {
DueDay int `json:"due_day" yaml:"due_day"`
Network string `json:"network" yaml:"network"`
Number string `json:"number" yaml:"number"`
ExpirationDate string `json:"expiration_date" yaml:"expiration_date"`
}

type Config struct {
Expand Down
8 changes: 7 additions & 1 deletion internal/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,11 @@
"maxLength": 4,
"minLength": 4,
"pattern": "^[0-9]{4}$"
},
"expiration_date": {
"type": "string",
"description": "Expiration date of the card",
"format": "date"
}
},
"required": [
Expand All @@ -496,7 +501,8 @@
"statement_end_day",
"due_day",
"network",
"number"
"number",
"expiration_date"
],
"additionalProperties": false
}
Expand Down
1 change: 1 addition & 0 deletions internal/generator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ credit_cards:
due_day: 20
network: visa
number: "0007"
expiration_date: "2029-05-01"
`
log.Info("Generating config file: ", configFilePath)
journalFilePath := filepath.Join(cwd, "main.ledger")
Expand Down
54 changes: 42 additions & 12 deletions internal/server/credit_card.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/ananthakumaran/paisa/internal/model/posting"
"github.com/ananthakumaran/paisa/internal/model/transaction"
"github.com/ananthakumaran/paisa/internal/query"
"github.com/ananthakumaran/paisa/internal/service"
"github.com/ananthakumaran/paisa/internal/utils"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
Expand All @@ -17,12 +18,14 @@ import (
)

type CreditCardSummary struct {
Account string `json:"account"`
Network string `json:"network"`
Number string `json:"number"`
Balance decimal.Decimal `json:"balance"`
Bills []CreditCardBill `json:"bills"`
CreditLimit decimal.Decimal `json:"creditLimit"`
Account string `json:"account"`
Network string `json:"network"`
Number string `json:"number"`
Balance decimal.Decimal `json:"balance"`
Bills []CreditCardBill `json:"bills"`
CreditLimit decimal.Decimal `json:"creditLimit"`
YearlySpends map[string]map[string]decimal.Decimal `json:"yearlySpends"`
ExpirationDate time.Time `json:"expirationDate"`
}

type CreditCardBill struct {
Expand Down Expand Up @@ -62,19 +65,46 @@ func GetCreditCard(db *gorm.DB, account string) gin.H {
return gin.H{"found": false}
}

func yearlySpends(db *gorm.DB, date time.Time, postings []posting.Posting) map[string]map[string]decimal.Decimal {
yearlySpends := make(map[string]map[string]decimal.Decimal)
for year, ps := range utils.GroupByYearCutoffAt(postings, date) {
spends := lo.Filter(ps, func(p posting.Posting, _ int) bool {
return p.Amount.IsNegative() || service.IsContraPostingRefund(db, p)
})

yearlySpends[year] = make(map[string]decimal.Decimal)
for month, ps := range utils.GroupByMonth(spends) {
yearlySpends[year][month] = accounting.CostSum(ps).Neg()
}
}
return yearlySpends
}

func buildCreditCard(db *gorm.DB, creditCardConfig config.CreditCard, ps []posting.Posting, includePostings bool) CreditCardSummary {
bills := computeBills(db, creditCardConfig, ps, includePostings)
balance := decimal.Zero
if len(bills) > 0 {
balance = bills[len(bills)-1].ClosingBalance
}

expirationDate, err := time.ParseInLocation("2006-01-02", creditCardConfig.ExpirationDate, time.Local)
if err != nil {
log.Fatal(err)
}

ys := make(map[string]map[string]decimal.Decimal)
if includePostings {
ys = yearlySpends(db, expirationDate, ps)
}
return CreditCardSummary{
Account: creditCardConfig.Account,
Network: creditCardConfig.Network,
Number: creditCardConfig.Number,
Balance: balance,
Bills: bills,
CreditLimit: decimal.NewFromInt(int64(creditCardConfig.CreditLimit)),
Account: creditCardConfig.Account,
Network: creditCardConfig.Network,
Number: creditCardConfig.Number,
Balance: balance,
Bills: bills,
CreditLimit: decimal.NewFromInt(int64(creditCardConfig.CreditLimit)),
YearlySpends: ys,
ExpirationDate: expirationDate,
}
}

Expand Down
22 changes: 22 additions & 0 deletions internal/service/interest.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ func IsCapitalGains(p posting.Posting) bool {
return false
}

func IsRefund(p posting.Posting) bool {
if utils.IsParent(p.Account, "Income:Refund") {
return true
}

return false
}

func IsStockSplit(db *gorm.DB, p posting.Posting) bool {
if utils.IsCurrency(p.Commodity) {
return false
Expand Down Expand Up @@ -95,6 +103,20 @@ func IsSellWithCapitalGains(db *gorm.DB, p posting.Posting) bool {
return false
}

func IsContraPostingRefund(db *gorm.DB, p posting.Posting) bool {
t, found := transaction.GetById(db, p.TransactionID)
if !found {
return false
}

for _, tp := range t.Postings {
if IsRefund(tp) {
return true
}
}
return false
}

func IsInterestRepayment(db *gorm.DB, p posting.Posting) bool {
irepaymentCache.Do(func() { loadInterestRepaymentCache(db) })

Expand Down
23 changes: 23 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ func FYHuman(date time.Time) string {
}
}

func YearHumanCutOffAt(date time.Time, cutoff time.Time) string {
if date.Month() < cutoff.Month() || date.Month() == cutoff.Month() && date.Day() < cutoff.Day() {
return fmt.Sprintf("%d - %d", date.Year()-1, date.Year()%100)
} else {
return fmt.Sprintf("%d - %d", date.Year(), (date.Year()+1)%100)
}
}

func ParseFY(fy string) (time.Time, time.Time) {
start, _ := time.Parse("2006", strings.Split(fy, " ")[0])
start = start.AddDate(0, int(config.GetConfig().FinancialYearStartingMonth-time.January), 0)
Expand Down Expand Up @@ -218,6 +226,21 @@ func GroupByFY[G GroupableByDate](groupables []G) map[string][]G {
return grouped
}

func GroupByYearCutoffAt[G GroupableByDate](groupables []G, date time.Time) map[string][]G {
grouped := make(map[string][]G)
for _, g := range groupables {
key := YearHumanCutOffAt(g.GroupDate(), date)
ps, ok := grouped[key]
if ok {
grouped[key] = append(ps, g)
} else {
grouped[key] = []G{g}
}

}
return grouped
}

func SumBy[C any](collection []C, iteratee func(item C) decimal.Decimal) decimal.Decimal {
return lo.Reduce(collection, func(acc decimal.Decimal, item C, _ int) decimal.Decimal {
return iteratee(item).Add(acc)
Expand Down
18 changes: 13 additions & 5 deletions src/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1093,7 +1093,7 @@ div.is-hoverable:hover {

.credit-card-container {
display: grid;
gap: 18px;
gap: 36px;
grid-template-columns: repeat(auto-fill, minmax(19rem, 25rem));
}

Expand All @@ -1104,20 +1104,28 @@ div.is-hoverable:hover {
flex: 1;
border-radius: 0.7rem;
display: flex;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3) !important;
border: 1px solid $grey-lightest;
box-shadow:
3px 3px 3px 0 rgba(0, 0, 0, 0.05),
10px 10px 10px 0 rgba(0, 0, 0, 0.15),
20px 20px 20px 0 rgba(0, 0, 0, 0.15) !important;
background: linear-gradient(
345deg,
$grey-lightest 0%,
$grey-lightest 60%,
$grey-lighter 60%,
$grey-lighter calc(60% + 1px),
$grey-lighter 85%,
$grey-light 85%,
$grey-light calc(85% + 1px),
$grey-light 95%,
$grey 95%,
$grey calc(95% + 1px),
$grey 100%
);

.chip {
color: $amber-700;
}

.nfc {
color: $black;
}
}
13 changes: 9 additions & 4 deletions src/dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,21 @@ html[data-theme="dark"] {
}

.credit-card {
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 1) !important;
border: none;
box-shadow:
3px 3px 3px 0 rgba(0, 0, 0, 0.7),
10px 10px 10px 0 rgba(0, 0, 0, 0.5),
20px 20px 20px 0 rgba(0, 0, 0, 0.4),
-1px -1px 1px 0 rgba(255, 255, 255, 0.2) !important;
background: linear-gradient(
345deg,
$white 0%,
$white 60%,
$white-bis 60%,
$white-bis calc(60% + 1px),
$white-bis 85%,
$white-ter 85%,
$white-ter calc(85% + 1px),
$white-ter 95%,
$grey-lightest 95%,
$grey-lightest calc(95% + 1px),
$grey-lightest 100%
);
}
Expand Down
34 changes: 30 additions & 4 deletions src/lib/components/CreditCardCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,33 @@

<div class="credit-card box p-3 m-0 flex-col justify-between">
<div class="is-flex justify-between has-text-weight-bold is-size-5">
<div style="margin: 35px 0 0 15px;" class="opacity-20 chip">
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24"
<div style="margin: 35px 0 0 15px;" class="flex items-center opacity-20">
<svg
class="chip"
xmlns="http://www.w3.org/2000/svg"
width="36"
height="36"
viewBox="0 0 24 24"
><path
fill="currentColor"
d="M10 4h10c1.11 0 2 .89 2 2v2h-3.41L16 10.59v4l-2 2V20h-4v-3.41l-2-2V9.41l2-2zm8 7.41V14h4v-4h-2.59zM6.59 8L8 6.59V4H4c-1.11 0-2 .89-2 2v2zM6 14v-4H2v4zm2 3.41L6.59 16H2v2c0 1.11.89 2 2 2h4zM17.41 16L16 17.41V20h4c1.11 0 2-.89 2-2v-2z"
/></svg
>
<svg
class="nfc ml-1"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
><path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 8.32a7.43 7.43 0 0 1 0 7.36m3.46-9.47a11.76 11.76 0 0 1 0 11.58M12.91 4.1a15.91 15.91 0 0 1 .01 15.8M16.37 2a20.16 20.16 0 0 1 0 20"
/></svg
>
</div>
<div>
<a
Expand Down Expand Up @@ -72,8 +92,14 @@
</div>
</div>
<div class="is-flex justify-between items-end">
<div class="opacity-25 has-text-weight-bold is-size-5">
* * * * &nbsp; {creditCard.number}
<div class="has-text-weight-bold is-size-5 inline-flex items-center">
<span class="opacity-40 inline-flex flex-col mr-2" style="font-size: 0.5rem; line-height: 1;">
<span>VALID</span>
<span>THRU</span>
</span>
<span class="opacity-30"
>{creditCard.expirationDate.format("MM / YY")} &nbsp; &nbsp; &nbsp; * * * * &nbsp; {creditCard.number}</span
>
</div>
<div class="opacity-15">
<CreditCardNetwork size={48} name={creditCard.network} />
Expand Down
Loading

0 comments on commit 03d4fe2

Please sign in to comment.