From 3339a098ea5eb577f19980e87b5c48461bf01cf2 Mon Sep 17 00:00:00 2001 From: Anantha Kumaran Date: Sun, 21 Jan 2024 15:46:38 +0530 Subject: [PATCH] WIP: credit card --- internal/accounting/accounting.go | 10 ++ internal/config/config.go | 12 ++ internal/config/schema.json | 66 ++++++++- internal/server/credit_card.go | 131 ++++++++++++++++++ internal/server/server.go | 4 + src/app.scss | 30 ++++ src/dark.scss | 15 ++ src/lib/components/CreditCardNetwork.svelte | 119 ++++++++++++++++ src/lib/components/DueDate.svelte | 20 +++ src/lib/components/Navbar.svelte | 1 + src/lib/transaction_sequence.ts | 30 +--- src/lib/utils.ts | 55 +++++++- .../liabilities/credit_card/+page.svelte | 112 +++++++++++++++ src/routes/(app)/more/goals/+page.svelte | 16 +-- tests/fixture/eur-hledger/config.json | 79 ++++++++++- tests/fixture/eur/config.json | 79 ++++++++++- tests/fixture/inr-beancount/config.json | 79 ++++++++++- tests/fixture/inr-hledger/config.json | 79 ++++++++++- tests/fixture/inr/config.json | 79 ++++++++++- 19 files changed, 957 insertions(+), 59 deletions(-) create mode 100644 internal/server/credit_card.go create mode 100644 src/lib/components/CreditCardNetwork.svelte create mode 100644 src/lib/components/DueDate.svelte create mode 100644 src/routes/(app)/liabilities/credit_card/+page.svelte diff --git a/internal/accounting/accounting.go b/internal/accounting/accounting.go index c1b88464..9798de19 100644 --- a/internal/accounting/accounting.go +++ b/internal/accounting/accounting.go @@ -212,3 +212,13 @@ func GroupByAccount(posts []posting.Posting) map[string][]posting.Posting { return post.Account }) } + +func GroupByMonthlyBillingCycle(postsings []posting.Posting, billDate int) map[string][]posting.Posting { + return lo.GroupBy(postsings, func(p posting.Posting) string { + if p.Date.Day() >= billDate { + return utils.BeginningOfMonth(p.Date).AddDate(0, 1, 0).Format("2006-01") + } else { + return p.Date.Format("2006-01") + } + }) +} diff --git a/internal/config/config.go b/internal/config/config.go index 5f93ee8d..f913e274 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -115,6 +115,15 @@ type AllocationTarget struct { Accounts []string `json:"accounts" yaml:"accounts"` } +type CreditCard struct { + Account string `json:"account" yaml:"account"` + CreditLimit int `json:"credit_limit" yaml:"credit_limit"` + StatementEndDay int `json:"statement_end_day" yaml:"statement_end_day"` + DueDay int `json:"due_day" yaml:"due_day"` + Network string `json:"network" yaml:"network"` + Number string `json:"number" yaml:"number"` +} + type Config struct { JournalPath string `json:"journal_path" yaml:"journal_path"` DBPath string `json:"db_path" yaml:"db_path"` @@ -143,6 +152,8 @@ type Config struct { Goals Goals `json:"goals" yaml:"goals"` UserAccounts []UserAccount `json:"user_accounts" yaml:"user_accounts"` + + CreditCards []CreditCard `json:"credit_cards" yaml:"credit_cards"` } var config Config @@ -166,6 +177,7 @@ var defaultConfig = Config{ Accounts: []Account{}, Goals: Goals{Retirement: []RetirementGoal{}, Savings: []SavingsGoal{}}, UserAccounts: []UserAccount{}, + CreditCards: []CreditCard{}, } var itemsUniquePropertiesMeta = jsonschema.MustCompileString("itemsUniqueProperties.json", `{ diff --git a/internal/config/schema.json b/internal/config/schema.json index 8d988aa0..d66f5175 100644 --- a/internal/config/schema.json +++ b/internal/config/schema.json @@ -347,7 +347,7 @@ "properties": { "name": { "type": "string", - "description": "name of the commodity" + "description": "Name of the commodity" }, "type": { "type": "string", @@ -401,7 +401,7 @@ "properties": { "name": { "type": "string", - "description": "name of the template", + "description": "Name of the template", "minLength": 1 }, "content": { @@ -428,7 +428,7 @@ "properties": { "name": { "type": "string", - "description": "name of the account", + "description": "Name of the account", "minLength": 1 }, "icon": { @@ -440,6 +440,66 @@ "required": ["name"], "additionalProperties": false } + }, + "credit_cards": { + "type": "array", + "itemsUniqueProperties": ["account"], + "default": [ + { + "account": "Liabilities:CreditCard:Chase", + "credit_limit": 100000, + "statement_end_day": 28, + "due_day": 15 + } + ], + "items": { + "type": "object", + "ui:header": "account", + "properties": { + "account": { + "type": "string", + "description": "Name of the credit card account" + }, + "credit_limit": { + "type": "number", + "description": "Credit limit of the card", + "minimum": 1 + }, + "statement_end_day": { + "type": "integer", + "description": "Statement end day of the card", + "minimum": 1, + "maximum": 31 + }, + "due_day": { + "type": "integer", + "description": "Due day of the card", + "minimum": 1, + "maximum": 31 + }, + "network": { + "type": "string", + "description": "Network of the card", + "enum": ["visa", "mastercard", "dinersclub", "amex", "rupay", "jcb", "discover"] + }, + "number": { + "type": "string", + "description": "Last 4 digits of the card number", + "maxLength": 4, + "minLength": 4, + "pattern": "^[0-9]{4}$" + } + }, + "required": [ + "account", + "credit_limit", + "statement_end_day", + "due_day", + "network", + "number" + ], + "additionalProperties": false + } } }, "required": ["journal_path", "db_path"], diff --git a/internal/server/credit_card.go b/internal/server/credit_card.go new file mode 100644 index 00000000..92be823e --- /dev/null +++ b/internal/server/credit_card.go @@ -0,0 +1,131 @@ +package server + +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" + "github.com/gin-gonic/gin" + "github.com/shopspring/decimal" + log "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +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"` +} + +type CreditCardBill struct { + StatementStartDate time.Time `json:"statementStartDate"` + StatementEndDate time.Time `json:"statementEndDate"` + DueDate time.Time `json:"dueDate"` + PaidDate *time.Time `json:"paidDate"` + Credits decimal.Decimal `json:"credits"` + Debits decimal.Decimal `json:"debits"` + OpeningBalance decimal.Decimal `json:"openingBalance"` + ClosingBalance decimal.Decimal `json:"closingBalance"` + Postings []posting.Posting `json:"postings"` +} + +func GetCreditCards(db *gorm.DB) gin.H { + creditCards := []CreditCardSummary{} + + for _, creditCardConfig := range config.GetConfig().CreditCards { + ps := query.Init(db).Where("account = ?", creditCardConfig.Account).All() + creditCards = append(creditCards, buildCreditCard(creditCardConfig, ps)) + } + + return gin.H{"creditCards": creditCards} +} + +func buildCreditCard(creditCardConfig config.CreditCard, ps []posting.Posting) CreditCardSummary { + bills := computeBills(creditCardConfig, ps) + balance := decimal.Zero + if len(bills) > 0 { + balance = bills[len(bills)-1].ClosingBalance + } + return CreditCardSummary{ + Account: creditCardConfig.Account, + Network: creditCardConfig.Network, + Number: creditCardConfig.Number, + Balance: balance, + Bills: bills, + CreditLimit: decimal.NewFromInt(int64(creditCardConfig.CreditLimit)), + } +} + +func computeBills(creditCardConfig config.CreditCard, ps []posting.Posting) []CreditCardBill { + bills := []CreditCardBill{} + + grouped := accounting.GroupByMonthlyBillingCycle(ps, creditCardConfig.StatementEndDay) + + balance := decimal.Zero + unpaidBalance := decimal.Zero + unpaidBill := 0 + + for _, month := range utils.SortedKeys(grouped) { + statementEndDate, err := time.Parse("2006-01", month) + if err != nil { + log.Fatal(err) + } + + statementEndDate = statementEndDate.AddDate(0, 0, creditCardConfig.StatementEndDay-1) + statementStartDate := statementEndDate.AddDate(0, -1, -1) + + var dueDate time.Time + if creditCardConfig.StatementEndDay < creditCardConfig.DueDay { + dueDate = utils.BeginningOfMonth(statementEndDate).AddDate(0, 0, creditCardConfig.DueDay-1) + } else { + dueDate = utils.BeginningOfMonth(statementEndDate).AddDate(0, 1, creditCardConfig.DueDay-1) + } + + bill := CreditCardBill{ + StatementStartDate: statementStartDate, + StatementEndDate: statementEndDate, + DueDate: dueDate, + OpeningBalance: balance, + Postings: []posting.Posting{}, + } + + for _, p := range grouped[month] { + balance = balance.Add(p.Amount.Neg()) + + if p.Amount.IsPositive() { + bill.Credits = bill.Credits.Add(p.Amount) + if unpaidBalance.IsPositive() { + + unpaidBalance = unpaidBalance.Sub(p.Amount) + if unpaidBalance.LessThanOrEqual(decimal.Zero) { + + unpaidBalance = decimal.Zero + for i := unpaidBill; i < len(bills); i++ { + paidDate := p.Date + bills[i].PaidDate = &paidDate + } + unpaidBill = len(bills) + } + + } + } else { + bill.Debits = bill.Debits.Add(p.Amount.Neg()) + } + + bill.Postings = append(bill.Postings, p) + + } + + bill.ClosingBalance = balance + unpaidBalance = balance + bills = append(bills, bill) + } + + return bills +} diff --git a/internal/server/server.go b/internal/server/server.go index 72cfa80d..7fd68a63 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -363,6 +363,10 @@ func Build(db *gorm.DB, enableCompression bool) *gin.Engine { c.JSON(200, goal.GetGoalDetails(db, c.Param("type"), c.Param("name"))) }) + router.GET("/api/credit_cards", func(c *gin.Context) { + c.JSON(200, GetCreditCards(db)) + }) + router.NoRoute(func(c *gin.Context) { c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(web.Index)) }) diff --git a/src/app.scss b/src/app.scss index ebc9300e..a2858d06 100644 --- a/src/app.scss +++ b/src/app.scss @@ -1088,3 +1088,33 @@ textarea:invalid { div.is-hoverable:hover { background-color: $white-bis; } + +// credit card + +.credit-card-container { + display: grid; + gap: 18px; + grid-template-columns: repeat(auto-fill, minmax(19rem, 25rem)); +} + +.credit-card { + aspect-ratio: 3.375/2.125; + border-radius: 0.7rem; + display: flex; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3) !important; + background: linear-gradient( + 345deg, + $grey-lightest 0%, + $grey-lightest 60%, + $grey-lighter 60%, + $grey-lighter 85%, + $grey-light 85%, + $grey-light 95%, + $grey 95%, + $grey 100% + ); + + .chip { + color: $amber-700; + } +} diff --git a/src/dark.scss b/src/dark.scss index e040db2d..1913a59d 100644 --- a/src/dark.scss +++ b/src/dark.scss @@ -179,4 +179,19 @@ html[data-theme="dark"] { } } } + + .credit-card { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 1) !important; + background: linear-gradient( + 345deg, + $white 0%, + $white 60%, + $white-bis 60%, + $white-bis 85%, + $white-ter 85%, + $white-ter 95%, + $grey-lightest 95%, + $grey-lightest 100% + ); + } } diff --git a/src/lib/components/CreditCardNetwork.svelte b/src/lib/components/CreditCardNetwork.svelte new file mode 100644 index 00000000..c33edcbd --- /dev/null +++ b/src/lib/components/CreditCardNetwork.svelte @@ -0,0 +1,119 @@ + + +{#if name == "visa"} +
+ +
+{/if} + +{#if name == "mastercard"} +
+ +
+{/if} + +{#if name == "dinersclub"} +
+ +
+{/if} + +{#if name == "amex"} +
+ +
+{/if} + +{#if name == "rupay"} +
+ +
+{/if} + +{#if name == "jcb"} +
+ +
+{/if} + +{#if name == "discover"} +
+ +
+{/if} diff --git a/src/lib/components/DueDate.svelte b/src/lib/components/DueDate.svelte new file mode 100644 index 00000000..5d69b57c --- /dev/null +++ b/src/lib/components/DueDate.svelte @@ -0,0 +1,20 @@ + + + + + + + {#if paidDate} + cleared on {paidDate.format("DD MMM YYYY")} + {:else} + due {dueDate.fromNow()} + {/if} + diff --git a/src/lib/components/Navbar.svelte b/src/lib/components/Navbar.svelte index 9d551166..8da39f77 100644 --- a/src/lib/components/Navbar.svelte +++ b/src/lib/components/Navbar.svelte @@ -97,6 +97,7 @@ href: "/liabilities", children: [ { label: "Balance", href: "/balance" }, + { label: "Credit Card", href: "/credit_card", help: "credit-card" }, { label: "Repayment", href: "/repayment" }, { label: "Interest", href: "/interest" } ] diff --git a/src/lib/transaction_sequence.ts b/src/lib/transaction_sequence.ts index a14447ca..a223de48 100644 --- a/src/lib/transaction_sequence.ts +++ b/src/lib/transaction_sequence.ts @@ -5,7 +5,8 @@ import { transactionTotal, type Transaction, type TransactionSchedule, - type TransactionSequence + type TransactionSequence, + dueDateIcon } from "./utils"; import dayjs from "dayjs"; import { parse, type CronExprs } from "@datasert/cronjs-parser"; @@ -148,32 +149,7 @@ export function nextUnpaidSchedule(ts: TransactionSequence) { } export function scheduleIcon(schedule: TransactionSchedule) { - let icon = "fa-circle-check"; - let glyph = iconGlyph("fa6-solid:circle-check"); - let color = "has-text-success"; - let svgColor = "svg-text-success"; - - if (!schedule.actual) { - if (schedule.scheduled.isBefore(now(), "day")) { - color = "has-text-danger"; - icon = "fa-exclamation-triangle"; - glyph = iconGlyph("fa6-solid:triangle-exclamation"); - svgColor = "svg-text-danger"; - } else { - color = "has-text-grey"; - svgColor = "svg-text-grey"; - } - } else { - if (schedule.actual.isSameOrBefore(schedule.scheduled, "day")) { - color = "has-text-success"; - svgColor = "svg-text-success"; - } else { - color = "has-text-warning-dark"; - svgColor = "svg-text-warning-dark"; - } - } - - return { icon, color, svgColor, glyph }; + return dueDateIcon(schedule.scheduled, schedule.actual); } export function intervalText(ts: TransactionSequence) { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 6340feb1..25ef9da8 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -9,6 +9,7 @@ import { obscure } from "../persisted_store"; import { error } from "@sveltejs/kit"; import { goto } from "$app/navigation"; import chroma from "chroma-js"; +import { iconGlyph } from "./icon"; export interface AutoCompleteItem { label: string; @@ -481,6 +482,27 @@ export interface Log { msg: string; } +export interface CreditCardBill { + openingBalance: number; + closingBalance: number; + debits: number; + credits: number; + statementStartDate: dayjs.Dayjs; + statementEndDate: dayjs.Dayjs; + dueDate: dayjs.Dayjs; + paidDate: dayjs.Dayjs; + postings: Posting[]; +} + +export interface CreditCardSummary { + account: string; + network: string; + number: string; + balance: number; + bills: CreditCardBill[]; + creditLimit: number; +} + export interface GoalSummary { type: string; name: string; @@ -609,6 +631,8 @@ export function ajax(route: "/api/liabilities/interest"): Promise<{ interest_timeline_breakdown: Interest[]; }>; +export function ajax(route: "/api/credit_cards"): Promise<{ creditCards: CreditCardSummary[] }>; + export function ajax(route: "/api/goals"): Promise<{ goals: GoalSummary[] }>; export function ajax( route: "/api/goals/retirement/:name", @@ -764,7 +788,7 @@ export async function ajax( return JSON.parse(body, (key, value) => { if ( _.isString(value) && - /date|time|now/.test(key) && + /Date|date|time|now/.test(key) && /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$/.test( value ) @@ -1206,3 +1230,32 @@ export function asTransaction(p: Posting): Transaction { export function svgUrl(identifier: string) { return `url(${new URL("#" + identifier, window.location.toString())})`; } + +export function dueDateIcon(dueDate: dayjs.Dayjs, clearedDate: dayjs.Dayjs) { + let icon = "fa-circle-check"; + let glyph = iconGlyph("fa6-solid:circle-check"); + let color = "has-text-success"; + let svgColor = "svg-text-success"; + + if (!clearedDate) { + if (dueDate.isBefore(now(), "day")) { + color = "has-text-danger"; + icon = "fa-exclamation-triangle"; + glyph = iconGlyph("fa6-solid:triangle-exclamation"); + svgColor = "svg-text-danger"; + } else { + color = "has-text-grey"; + svgColor = "svg-text-grey"; + } + } else { + if (clearedDate.isSameOrBefore(dueDate, "day")) { + color = "has-text-success"; + svgColor = "svg-text-success"; + } else { + color = "has-text-warning-dark"; + svgColor = "svg-text-warning-dark"; + } + } + + return { icon, color, svgColor, glyph }; +} diff --git a/src/routes/(app)/liabilities/credit_card/+page.svelte b/src/routes/(app)/liabilities/credit_card/+page.svelte new file mode 100644 index 00000000..a0b04425 --- /dev/null +++ b/src/routes/(app)/liabilities/credit_card/+page.svelte @@ -0,0 +1,112 @@ + + +
+
+
+
+
+ {#each creditCards as creditCard} + {@const bill = lastBill(creditCard)} +
+
+
+ +
+
+ {iconText(creditCard.account)} + {restName(restName(creditCard.account))} +
+
+
+
+ {#if bill} +
+ Amount Due +
+
+ {formatCurrency(bill.closingBalance)} +
+
+ +
+ {/if} +
+
+
+ Balance +
+
+ {formatCurrency(creditCard.balance)} + {formatPercentage(creditCard.balance / creditCard.creditLimit)} of {formatCurrency( + creditCard.creditLimit + )} + +
+
+
+
+
+ * * * *   {creditCard.number} +
+
+ +
+
+
+ {/each} +
+
+
+
+
+ + Oops! You haven't configured any credit cards yet. Checkout the + docs page to get started. + +
+
+
+
diff --git a/src/routes/(app)/more/goals/+page.svelte b/src/routes/(app)/more/goals/+page.svelte index f98fbe45..02eb4f0d 100644 --- a/src/routes/(app)/more/goals/+page.svelte +++ b/src/routes/(app)/more/goals/+page.svelte @@ -63,14 +63,6 @@
-
-
- - Oops! You haven't configured any goals yet. Checkout the - docs page to get started. - -
-
{/each}
+
+
+ + Oops! You haven't configured any goals yet. Checkout the + docs page to get started. + +
+
diff --git a/tests/fixture/eur-hledger/config.json b/tests/fixture/eur-hledger/config.json index d5860bf3..a803a471 100644 --- a/tests/fixture/eur-hledger/config.json +++ b/tests/fixture/eur-hledger/config.json @@ -28,7 +28,8 @@ "retirement": [], "savings": [] }, - "user_accounts": [] + "user_accounts": [], + "credit_cards": [] }, "now": "2022-02-07T00:00:00Z", "schema": { @@ -53,7 +54,7 @@ "ui:widget": "icon" }, "name": { - "description": "name of the account", + "description": "Name of the account", "minLength": 1, "type": "string" } @@ -157,7 +158,7 @@ "type": "integer" }, "name": { - "description": "name of the commodity", + "description": "Name of the commodity", "type": "string" }, "price": { @@ -221,6 +222,76 @@ ], "type": "array" }, + "credit_cards": { + "default": [ + { + "account": "Liabilities:CreditCard:Chase", + "credit_limit": 100000, + "due_day": 15, + "statement_end_day": 28 + } + ], + "items": { + "additionalProperties": false, + "properties": { + "account": { + "description": "Name of the credit card account", + "type": "string" + }, + "credit_limit": { + "description": "Credit limit of the card", + "minimum": 1, + "type": "number" + }, + "due_day": { + "description": "Due day of the card", + "maximum": 31, + "minimum": 1, + "type": "integer" + }, + "network": { + "description": "Network of the card", + "enum": [ + "visa", + "mastercard", + "dinersclub", + "amex", + "rupay", + "jcb", + "discover" + ], + "type": "string" + }, + "number": { + "description": "Last 4 digits of the card number", + "maxLength": 4, + "minLength": 4, + "pattern": "^[0-9]{4}$", + "type": "string" + }, + "statement_end_day": { + "description": "Statement end day of the card", + "maximum": 31, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "account", + "credit_limit", + "statement_end_day", + "due_day", + "network", + "number" + ], + "type": "object", + "ui:header": "account" + }, + "itemsUniqueProperties": [ + "account" + ], + "type": "array" + }, "db_path": { "description": "Path to your database file. It can be absolute or relative to the configuration file. The database file will be created if it does not exist.", "type": "string" @@ -430,7 +501,7 @@ "ui:widget": "textarea" }, "name": { - "description": "name of the template", + "description": "Name of the template", "minLength": 1, "type": "string" } diff --git a/tests/fixture/eur/config.json b/tests/fixture/eur/config.json index 71584d77..e3161ea8 100644 --- a/tests/fixture/eur/config.json +++ b/tests/fixture/eur/config.json @@ -28,7 +28,8 @@ "retirement": [], "savings": [] }, - "user_accounts": [] + "user_accounts": [], + "credit_cards": [] }, "now": "2022-02-07T00:00:00Z", "schema": { @@ -53,7 +54,7 @@ "ui:widget": "icon" }, "name": { - "description": "name of the account", + "description": "Name of the account", "minLength": 1, "type": "string" } @@ -157,7 +158,7 @@ "type": "integer" }, "name": { - "description": "name of the commodity", + "description": "Name of the commodity", "type": "string" }, "price": { @@ -221,6 +222,76 @@ ], "type": "array" }, + "credit_cards": { + "default": [ + { + "account": "Liabilities:CreditCard:Chase", + "credit_limit": 100000, + "due_day": 15, + "statement_end_day": 28 + } + ], + "items": { + "additionalProperties": false, + "properties": { + "account": { + "description": "Name of the credit card account", + "type": "string" + }, + "credit_limit": { + "description": "Credit limit of the card", + "minimum": 1, + "type": "number" + }, + "due_day": { + "description": "Due day of the card", + "maximum": 31, + "minimum": 1, + "type": "integer" + }, + "network": { + "description": "Network of the card", + "enum": [ + "visa", + "mastercard", + "dinersclub", + "amex", + "rupay", + "jcb", + "discover" + ], + "type": "string" + }, + "number": { + "description": "Last 4 digits of the card number", + "maxLength": 4, + "minLength": 4, + "pattern": "^[0-9]{4}$", + "type": "string" + }, + "statement_end_day": { + "description": "Statement end day of the card", + "maximum": 31, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "account", + "credit_limit", + "statement_end_day", + "due_day", + "network", + "number" + ], + "type": "object", + "ui:header": "account" + }, + "itemsUniqueProperties": [ + "account" + ], + "type": "array" + }, "db_path": { "description": "Path to your database file. It can be absolute or relative to the configuration file. The database file will be created if it does not exist.", "type": "string" @@ -430,7 +501,7 @@ "ui:widget": "textarea" }, "name": { - "description": "name of the template", + "description": "Name of the template", "minLength": 1, "type": "string" } diff --git a/tests/fixture/inr-beancount/config.json b/tests/fixture/inr-beancount/config.json index b1cba277..0ab96460 100644 --- a/tests/fixture/inr-beancount/config.json +++ b/tests/fixture/inr-beancount/config.json @@ -36,7 +36,8 @@ "retirement": [], "savings": [] }, - "user_accounts": [] + "user_accounts": [], + "credit_cards": [] }, "now": "2022-02-07T00:00:00Z", "schema": { @@ -61,7 +62,7 @@ "ui:widget": "icon" }, "name": { - "description": "name of the account", + "description": "Name of the account", "minLength": 1, "type": "string" } @@ -165,7 +166,7 @@ "type": "integer" }, "name": { - "description": "name of the commodity", + "description": "Name of the commodity", "type": "string" }, "price": { @@ -229,6 +230,76 @@ ], "type": "array" }, + "credit_cards": { + "default": [ + { + "account": "Liabilities:CreditCard:Chase", + "credit_limit": 100000, + "due_day": 15, + "statement_end_day": 28 + } + ], + "items": { + "additionalProperties": false, + "properties": { + "account": { + "description": "Name of the credit card account", + "type": "string" + }, + "credit_limit": { + "description": "Credit limit of the card", + "minimum": 1, + "type": "number" + }, + "due_day": { + "description": "Due day of the card", + "maximum": 31, + "minimum": 1, + "type": "integer" + }, + "network": { + "description": "Network of the card", + "enum": [ + "visa", + "mastercard", + "dinersclub", + "amex", + "rupay", + "jcb", + "discover" + ], + "type": "string" + }, + "number": { + "description": "Last 4 digits of the card number", + "maxLength": 4, + "minLength": 4, + "pattern": "^[0-9]{4}$", + "type": "string" + }, + "statement_end_day": { + "description": "Statement end day of the card", + "maximum": 31, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "account", + "credit_limit", + "statement_end_day", + "due_day", + "network", + "number" + ], + "type": "object", + "ui:header": "account" + }, + "itemsUniqueProperties": [ + "account" + ], + "type": "array" + }, "db_path": { "description": "Path to your database file. It can be absolute or relative to the configuration file. The database file will be created if it does not exist.", "type": "string" @@ -438,7 +509,7 @@ "ui:widget": "textarea" }, "name": { - "description": "name of the template", + "description": "Name of the template", "minLength": 1, "type": "string" } diff --git a/tests/fixture/inr-hledger/config.json b/tests/fixture/inr-hledger/config.json index 80d56e99..dcd6e0d7 100644 --- a/tests/fixture/inr-hledger/config.json +++ b/tests/fixture/inr-hledger/config.json @@ -35,7 +35,8 @@ "retirement": [], "savings": [] }, - "user_accounts": [] + "user_accounts": [], + "credit_cards": [] }, "now": "2022-02-07T00:00:00Z", "schema": { @@ -60,7 +61,7 @@ "ui:widget": "icon" }, "name": { - "description": "name of the account", + "description": "Name of the account", "minLength": 1, "type": "string" } @@ -164,7 +165,7 @@ "type": "integer" }, "name": { - "description": "name of the commodity", + "description": "Name of the commodity", "type": "string" }, "price": { @@ -228,6 +229,76 @@ ], "type": "array" }, + "credit_cards": { + "default": [ + { + "account": "Liabilities:CreditCard:Chase", + "credit_limit": 100000, + "due_day": 15, + "statement_end_day": 28 + } + ], + "items": { + "additionalProperties": false, + "properties": { + "account": { + "description": "Name of the credit card account", + "type": "string" + }, + "credit_limit": { + "description": "Credit limit of the card", + "minimum": 1, + "type": "number" + }, + "due_day": { + "description": "Due day of the card", + "maximum": 31, + "minimum": 1, + "type": "integer" + }, + "network": { + "description": "Network of the card", + "enum": [ + "visa", + "mastercard", + "dinersclub", + "amex", + "rupay", + "jcb", + "discover" + ], + "type": "string" + }, + "number": { + "description": "Last 4 digits of the card number", + "maxLength": 4, + "minLength": 4, + "pattern": "^[0-9]{4}$", + "type": "string" + }, + "statement_end_day": { + "description": "Statement end day of the card", + "maximum": 31, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "account", + "credit_limit", + "statement_end_day", + "due_day", + "network", + "number" + ], + "type": "object", + "ui:header": "account" + }, + "itemsUniqueProperties": [ + "account" + ], + "type": "array" + }, "db_path": { "description": "Path to your database file. It can be absolute or relative to the configuration file. The database file will be created if it does not exist.", "type": "string" @@ -437,7 +508,7 @@ "ui:widget": "textarea" }, "name": { - "description": "name of the template", + "description": "Name of the template", "minLength": 1, "type": "string" } diff --git a/tests/fixture/inr/config.json b/tests/fixture/inr/config.json index f92fa7d8..37b8cf19 100644 --- a/tests/fixture/inr/config.json +++ b/tests/fixture/inr/config.json @@ -35,7 +35,8 @@ "retirement": [], "savings": [] }, - "user_accounts": [] + "user_accounts": [], + "credit_cards": [] }, "now": "2022-02-07T00:00:00Z", "schema": { @@ -60,7 +61,7 @@ "ui:widget": "icon" }, "name": { - "description": "name of the account", + "description": "Name of the account", "minLength": 1, "type": "string" } @@ -164,7 +165,7 @@ "type": "integer" }, "name": { - "description": "name of the commodity", + "description": "Name of the commodity", "type": "string" }, "price": { @@ -228,6 +229,76 @@ ], "type": "array" }, + "credit_cards": { + "default": [ + { + "account": "Liabilities:CreditCard:Chase", + "credit_limit": 100000, + "due_day": 15, + "statement_end_day": 28 + } + ], + "items": { + "additionalProperties": false, + "properties": { + "account": { + "description": "Name of the credit card account", + "type": "string" + }, + "credit_limit": { + "description": "Credit limit of the card", + "minimum": 1, + "type": "number" + }, + "due_day": { + "description": "Due day of the card", + "maximum": 31, + "minimum": 1, + "type": "integer" + }, + "network": { + "description": "Network of the card", + "enum": [ + "visa", + "mastercard", + "dinersclub", + "amex", + "rupay", + "jcb", + "discover" + ], + "type": "string" + }, + "number": { + "description": "Last 4 digits of the card number", + "maxLength": 4, + "minLength": 4, + "pattern": "^[0-9]{4}$", + "type": "string" + }, + "statement_end_day": { + "description": "Statement end day of the card", + "maximum": 31, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "account", + "credit_limit", + "statement_end_day", + "due_day", + "network", + "number" + ], + "type": "object", + "ui:header": "account" + }, + "itemsUniqueProperties": [ + "account" + ], + "type": "array" + }, "db_path": { "description": "Path to your database file. It can be absolute or relative to the configuration file. The database file will be created if it does not exist.", "type": "string" @@ -437,7 +508,7 @@ "ui:widget": "textarea" }, "name": { - "description": "name of the template", + "description": "Name of the template", "minLength": 1, "type": "string" }