[credit_card] show year wise spend
This commit is contained in:
parent
46b4820bce
commit
03d4fe2c68
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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": [
|
||||
|
@ -496,7 +501,8 @@
|
|||
"statement_end_day",
|
||||
"due_day",
|
||||
"network",
|
||||
"number"
|
||||
"number",
|
||||
"expiration_date"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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) })
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
@ -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)
|
||||
|
|
18
src/app.scss
18
src/app.scss
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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%
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -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">
|
||||
* * * * {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")} * * * * {creditCard.number}</span
|
||||
>
|
||||
</div>
|
||||
<div class="opacity-15">
|
||||
<CreditCardNetwork size={48} name={creditCard.network} />
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import * as d3 from "d3";
|
||||
import { formatCurrencyCrude, tooltip, formatCurrency } from "./utils";
|
||||
import _ from "lodash";
|
||||
import COLORS from "./colors";
|
||||
|
||||
export function renderYearlySpends(
|
||||
svgNode: SVGElement,
|
||||
yearlySpends: { [year: string]: { [month: string]: number } }
|
||||
) {
|
||||
const BAR_HEIGHT = 20;
|
||||
const svg = d3.select(svgNode),
|
||||
margin = { top: 15, right: 20, bottom: 20, left: 70 },
|
||||
width = svgNode.parentElement.clientWidth - margin.left - margin.right,
|
||||
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
const color = COLORS.expenses;
|
||||
|
||||
const height = BAR_HEIGHT * Object.keys(yearlySpends).length;
|
||||
svg.attr("height", height + margin.top + margin.bottom);
|
||||
|
||||
interface Point {
|
||||
year: string;
|
||||
value: number;
|
||||
breakdown: { [month: string]: number };
|
||||
}
|
||||
|
||||
const points: Point[] = _.chain(yearlySpends)
|
||||
.map((breakdown, year) => {
|
||||
const value = _.sum(_.values(breakdown));
|
||||
return { year, breakdown, value };
|
||||
})
|
||||
.sortBy((p) => p.year)
|
||||
.value();
|
||||
|
||||
const x = d3.scaleLinear().range([0, width]);
|
||||
const y = d3.scaleBand().range([height, 0]).paddingInner(0.2).paddingOuter(0);
|
||||
|
||||
y.domain(points.map((p) => p.year));
|
||||
x.domain([0, d3.max(points, (p: Point) => p.value)]);
|
||||
|
||||
g.append("g")
|
||||
.attr("class", "axis y")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.call(d3.axisBottom(x).tickSize(-height).tickFormat(formatCurrencyCrude));
|
||||
|
||||
g.append("g").attr("class", "axis y dark").call(d3.axisLeft(y));
|
||||
|
||||
g.append("g")
|
||||
.selectAll("rect")
|
||||
.data(points)
|
||||
.join("rect")
|
||||
.attr("stroke-opacity", 0.6)
|
||||
.attr("fill-opacity", 0.4)
|
||||
.attr("stroke", color)
|
||||
.attr("fill", color)
|
||||
.attr("data-tippy-content", (d) => {
|
||||
return tooltip(
|
||||
_.map(d.breakdown, (value, month) => {
|
||||
return [month, [formatCurrency(value), "has-text-right has-text-weight-bold"]];
|
||||
}),
|
||||
{ total: formatCurrency(d.value), header: d.year }
|
||||
);
|
||||
})
|
||||
.attr("x", x(0))
|
||||
.attr("y", function (d) {
|
||||
return y(d.year) + (y.bandwidth() - Math.min(y.bandwidth(), BAR_HEIGHT)) / 2;
|
||||
})
|
||||
.attr("width", function (d) {
|
||||
return x(d.value) - x(0);
|
||||
})
|
||||
.attr("height", y.bandwidth());
|
||||
}
|
|
@ -502,6 +502,8 @@ export interface CreditCardSummary {
|
|||
balance: number;
|
||||
bills: CreditCardBill[];
|
||||
creditLimit: number;
|
||||
expirationDate: dayjs.Dayjs;
|
||||
yearlySpends: { [year: string]: { [month: string]: number } };
|
||||
}
|
||||
|
||||
export interface GoalSummary {
|
||||
|
|
|
@ -1,30 +1,34 @@
|
|||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import COLORS from "$lib/colors";
|
||||
import BoxLabel from "$lib/components/BoxLabel.svelte";
|
||||
import CreditCardCard from "$lib/components/CreditCardCard.svelte";
|
||||
import DueDate from "$lib/components/DueDate.svelte";
|
||||
import LevelItem from "$lib/components/LevelItem.svelte";
|
||||
import TransactionCard from "$lib/components/TransactionCard.svelte";
|
||||
import { renderYearlySpends } from "$lib/credit_cards";
|
||||
import { iconify } from "$lib/icon";
|
||||
import {
|
||||
ajax,
|
||||
formatCurrency,
|
||||
formatPercentage,
|
||||
type CreditCardBill,
|
||||
type CreditCardSummary,
|
||||
formatPercentage
|
||||
type CreditCardSummary
|
||||
} from "$lib/utils";
|
||||
import { MasonryGrid } from "@egjs/svelte-grid";
|
||||
import _, { now } from "lodash";
|
||||
import { onMount } from "svelte";
|
||||
import type { PageData } from "./$types";
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
import { MasonryGrid } from "@egjs/svelte-grid";
|
||||
import TransactionCard from "$lib/components/TransactionCard.svelte";
|
||||
import LevelItem from "$lib/components/LevelItem.svelte";
|
||||
import COLORS from "$lib/colors";
|
||||
import { iconify } from "$lib/icon";
|
||||
import DueDate from "$lib/components/DueDate.svelte";
|
||||
let UntypedMasonryGrid = MasonryGrid as any;
|
||||
|
||||
export let data: PageData;
|
||||
let svg: SVGElement;
|
||||
|
||||
let creditCard: CreditCardSummary;
|
||||
let currentBill: CreditCardBill;
|
||||
let found = false;
|
||||
let small = true;
|
||||
let rendered = false;
|
||||
|
||||
function lastBill(creditCard: CreditCardSummary): CreditCardBill {
|
||||
return _.find(_.reverse(_.clone(creditCard.bills)), (b) => {
|
||||
|
@ -32,12 +36,18 @@
|
|||
});
|
||||
}
|
||||
|
||||
$: if (creditCard && svg && !rendered) {
|
||||
renderYearlySpends(svg, creditCard.yearlySpends);
|
||||
rendered = true;
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
({ creditCard, found } = await ajax("/api/credit_cards/:account", null, data));
|
||||
currentBill = lastBill(creditCard);
|
||||
if (!found) {
|
||||
redirect(307, `/liabilities/credit_cards`);
|
||||
return goto("/liabilities/credit_cards");
|
||||
}
|
||||
|
||||
currentBill = lastBill(creditCard);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -46,7 +56,7 @@
|
|||
<div class="columns flex-wrap">
|
||||
<div class="column is-3-widescreen is-4">
|
||||
{#if creditCard}
|
||||
<div class="flex mb-4">
|
||||
<div class="flex mb-12">
|
||||
<CreditCardCard {creditCard} />
|
||||
</div>
|
||||
|
||||
|
@ -84,6 +94,11 @@
|
|||
value={_.sumBy(creditCard.bills, (b) => b.transactions.length).toString()}
|
||||
/>
|
||||
</nav>
|
||||
|
||||
<div class="box px-3 py-0">
|
||||
<svg bind:this={svg} width="100%" />
|
||||
</div>
|
||||
<BoxLabel text="Year wise spends" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="column is-9-widescreen is-8">
|
||||
|
|
|
@ -249,6 +249,11 @@
|
|||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"expiration_date": {
|
||||
"description": "Expiration date of the card",
|
||||
"format": "date",
|
||||
"type": "string"
|
||||
},
|
||||
"network": {
|
||||
"description": "Network of the card",
|
||||
"enum": [
|
||||
|
@ -282,7 +287,8 @@
|
|||
"statement_end_day",
|
||||
"due_day",
|
||||
"network",
|
||||
"number"
|
||||
"number",
|
||||
"expiration_date"
|
||||
],
|
||||
"type": "object",
|
||||
"ui:header": "account"
|
||||
|
|
|
@ -249,6 +249,11 @@
|
|||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"expiration_date": {
|
||||
"description": "Expiration date of the card",
|
||||
"format": "date",
|
||||
"type": "string"
|
||||
},
|
||||
"network": {
|
||||
"description": "Network of the card",
|
||||
"enum": [
|
||||
|
@ -282,7 +287,8 @@
|
|||
"statement_end_day",
|
||||
"due_day",
|
||||
"network",
|
||||
"number"
|
||||
"number",
|
||||
"expiration_date"
|
||||
],
|
||||
"type": "object",
|
||||
"ui:header": "account"
|
||||
|
|
|
@ -257,6 +257,11 @@
|
|||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"expiration_date": {
|
||||
"description": "Expiration date of the card",
|
||||
"format": "date",
|
||||
"type": "string"
|
||||
},
|
||||
"network": {
|
||||
"description": "Network of the card",
|
||||
"enum": [
|
||||
|
@ -290,7 +295,8 @@
|
|||
"statement_end_day",
|
||||
"due_day",
|
||||
"network",
|
||||
"number"
|
||||
"number",
|
||||
"expiration_date"
|
||||
],
|
||||
"type": "object",
|
||||
"ui:header": "account"
|
||||
|
|
|
@ -256,6 +256,11 @@
|
|||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"expiration_date": {
|
||||
"description": "Expiration date of the card",
|
||||
"format": "date",
|
||||
"type": "string"
|
||||
},
|
||||
"network": {
|
||||
"description": "Network of the card",
|
||||
"enum": [
|
||||
|
@ -289,7 +294,8 @@
|
|||
"statement_end_day",
|
||||
"due_day",
|
||||
"network",
|
||||
"number"
|
||||
"number",
|
||||
"expiration_date"
|
||||
],
|
||||
"type": "object",
|
||||
"ui:header": "account"
|
||||
|
|
|
@ -256,6 +256,11 @@
|
|||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"expiration_date": {
|
||||
"description": "Expiration date of the card",
|
||||
"format": "date",
|
||||
"type": "string"
|
||||
},
|
||||
"network": {
|
||||
"description": "Network of the card",
|
||||
"enum": [
|
||||
|
@ -289,7 +294,8 @@
|
|||
"statement_end_day",
|
||||
"due_day",
|
||||
"network",
|
||||
"number"
|
||||
"number",
|
||||
"expiration_date"
|
||||
],
|
||||
"type": "object",
|
||||
"ui:header": "account"
|
||||
|
|
Loading…
Reference in New Issue