Add delayed reason
This commit is contained in:
50
example.yaml
Normal file
50
example.yaml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
end: 2025-05-01
|
||||||
|
accounts:
|
||||||
|
- name: Capital One
|
||||||
|
balance: 4500
|
||||||
|
minimum: 500
|
||||||
|
credit:
|
||||||
|
- name: Amazon Store Card
|
||||||
|
amount: -438.35
|
||||||
|
date: 2023-03-28
|
||||||
|
- name: Amex Platinum
|
||||||
|
amount: -3263.42
|
||||||
|
date: 2023-04-19
|
||||||
|
- name: Apple Card
|
||||||
|
amount: -80.07
|
||||||
|
date: 2023-04-03
|
||||||
|
- name: Chase
|
||||||
|
amount: -0
|
||||||
|
date: 2023-03-16
|
||||||
|
- name: Citi
|
||||||
|
amount: -2307.84
|
||||||
|
date: 2023-04-06
|
||||||
|
- name: Discover
|
||||||
|
amount: -197.90
|
||||||
|
date: 2023-04-03
|
||||||
|
transactions:
|
||||||
|
- name: Bills
|
||||||
|
amount: -1690.00
|
||||||
|
date: first
|
||||||
|
frequency: monthly
|
||||||
|
delayable: false
|
||||||
|
- name: Car Insurance
|
||||||
|
amount: -140.92
|
||||||
|
date: first
|
||||||
|
frequency: monthly
|
||||||
|
delayable: false
|
||||||
|
- name: Savings
|
||||||
|
amount: -1000
|
||||||
|
date: 14
|
||||||
|
frequency: monthly
|
||||||
|
delayable: false
|
||||||
|
- name: Brokerage
|
||||||
|
amount: -1000
|
||||||
|
date: 2023-04-30
|
||||||
|
frequency: monthly
|
||||||
|
delayable: true
|
||||||
|
- name: Paycheck
|
||||||
|
amount: 3000
|
||||||
|
date: 15
|
||||||
|
frequency: biweekly
|
||||||
|
shift: early
|
||||||
6
go.mod
6
go.mod
@@ -1,11 +1,11 @@
|
|||||||
module github.com/samuelattwood/cashflow
|
module github.com/samuelattwood/cashflow
|
||||||
|
|
||||||
go 1.20
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/sys v0.9.0 // indirect
|
require golang.org/x/sys v0.32.0 // indirect
|
||||||
|
|||||||
11
go.sum
11
go.sum
@@ -1,21 +1,18 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
|
||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
58
main.go
58
main.go
@@ -20,8 +20,12 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
maxWidth = 100
|
maxWidth = 100
|
||||||
|
DelayWeekend DelayReason = "Weekend"
|
||||||
|
DelayInsufficient DelayReason = "Insufficient Funds"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DelayReason string
|
||||||
|
|
||||||
type timeSlice []time.Time
|
type timeSlice []time.Time
|
||||||
|
|
||||||
func (s timeSlice) Len() int { return len(s) }
|
func (s timeSlice) Len() int { return len(s) }
|
||||||
@@ -50,6 +54,12 @@ type Account struct {
|
|||||||
Minimum float64 `yaml:"minimum"`
|
Minimum float64 `yaml:"minimum"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AccountYaml struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Balance string `yaml:"balance"`
|
||||||
|
Minimum string `yaml:"minimum"`
|
||||||
|
}
|
||||||
|
|
||||||
type Charge struct {
|
type Charge struct {
|
||||||
Name string
|
Name string
|
||||||
Late bool
|
Late bool
|
||||||
@@ -65,7 +75,7 @@ type Config struct {
|
|||||||
type ConfigYaml struct {
|
type ConfigYaml struct {
|
||||||
Start string
|
Start string
|
||||||
End string
|
End string
|
||||||
Accounts []Account
|
Accounts []AccountYaml
|
||||||
Credit []TransactionYaml
|
Credit []TransactionYaml
|
||||||
Transactions []TransactionYaml
|
Transactions []TransactionYaml
|
||||||
}
|
}
|
||||||
@@ -77,6 +87,7 @@ type Transaction struct {
|
|||||||
Frequency string
|
Frequency string
|
||||||
ID string
|
ID string
|
||||||
Delayed bool
|
Delayed bool
|
||||||
|
DelayedReason DelayReason
|
||||||
Name string
|
Name string
|
||||||
OriginDate time.Time
|
OriginDate time.Time
|
||||||
Shift string
|
Shift string
|
||||||
@@ -228,8 +239,33 @@ func parseTransaction(transactionYaml TransactionYaml) (*Transaction, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseConfig(configYaml ConfigYaml) *Config {
|
func parseConfig(configYaml ConfigYaml) *Config {
|
||||||
|
accounts := make([]Account, 0)
|
||||||
|
for _, account := range configYaml.Accounts {
|
||||||
|
balanceStr := strings.ReplaceAll(account.Balance, "$", "")
|
||||||
|
balanceStr = strings.ReplaceAll(balanceStr, ",", "")
|
||||||
|
balance, err := strconv.ParseFloat(balanceStr, 32)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
var minimum float64 = 0
|
||||||
|
if account.Minimum != "" {
|
||||||
|
minimumStr := strings.ReplaceAll(account.Minimum, "$", "")
|
||||||
|
minimumStr = strings.ReplaceAll(minimumStr, ",", "")
|
||||||
|
minimum, err = strconv.ParseFloat(minimumStr, 32)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts = append(accounts, Account{
|
||||||
|
Name: account.Name,
|
||||||
|
Balance: balance,
|
||||||
|
Minimum: minimum,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
config := &Config{
|
config := &Config{
|
||||||
Accounts: configYaml.Accounts,
|
Accounts: accounts,
|
||||||
Transactions: make([]*Transaction, 0),
|
Transactions: make([]*Transaction, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,6 +404,12 @@ func calculateShift(transactionLog TransactionLog, transaction Transaction) time
|
|||||||
newDate = newDate.AddDate(0, 0, add)
|
newDate = newDate.AddDate(0, 0, add)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if newDate.Before(transactionLog.first()) || newDate.After(transactionLog.last()) {
|
||||||
|
for isWeekend(newDate) {
|
||||||
|
newDate = newDate.AddDate(0, 0, add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return newDate
|
return newDate
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +433,7 @@ func (tl *TransactionLog) delayTransaction(transaction *Transaction) {
|
|||||||
newDate = tl.last()
|
newDate = tl.last()
|
||||||
}
|
}
|
||||||
|
|
||||||
if isWeekend(newDate) {
|
for isWeekend(newDate) {
|
||||||
newDate = newDate.AddDate(0, 0, 1)
|
newDate = newDate.AddDate(0, 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,6 +445,9 @@ func (tl *TransactionLog) delayTransaction(transaction *Transaction) {
|
|||||||
tl.LogDays[newDate].Transactions[transaction.ID] = transaction
|
tl.LogDays[newDate].Transactions[transaction.ID] = transaction
|
||||||
|
|
||||||
transaction.Delayed = true
|
transaction.Delayed = true
|
||||||
|
if transaction.DelayedReason == "" {
|
||||||
|
transaction.DelayedReason = DelayInsufficient
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,6 +459,7 @@ func (tl *TransactionLog) executeTransaction(transaction *Transaction) bool {
|
|||||||
if isWeekend(transaction.Date) && transaction.Delayable {
|
if isWeekend(transaction.Date) && transaction.Delayable {
|
||||||
logrus.Debugf("Delaying %s due to weekend scheduling\n", transaction.Name)
|
logrus.Debugf("Delaying %s due to weekend scheduling\n", transaction.Name)
|
||||||
tl.delayTransaction(transaction)
|
tl.delayTransaction(transaction)
|
||||||
|
transaction.DelayedReason = DelayWeekend
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -658,10 +704,8 @@ func printOutput(logDay LogDay) {
|
|||||||
prefix = "+"
|
prefix = "+"
|
||||||
}
|
}
|
||||||
|
|
||||||
if charge.Delayed {
|
if charge.DelayedReason != "" {
|
||||||
suffix = fmt.Sprintf("(%s - %s) |", "Late", charge.OriginDate.Format("Jan 02 2006"))
|
suffix = fmt.Sprintf("(%s: %s - %s) |", "Delayed", charge.DelayedReason, charge.OriginDate.Format("Jan 02 2006"))
|
||||||
} else if charge.Date != charge.OriginDate {
|
|
||||||
suffix = fmt.Sprintf("(%s - %s) |", "Weekend", charge.OriginDate.Format("Jan 02 2006"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
printTransactionLine(charge.Amount, charge.Name, prefix, suffix, fullWidth)
|
printTransactionLine(charge.Amount, charge.Name, prefix, suffix, fullWidth)
|
||||||
|
|||||||
Reference in New Issue
Block a user