Logic fixes and general cleanup

This commit is contained in:
2023-03-11 02:37:54 -05:00
parent 746b7ec657
commit 8a0ad44041

504
main.go
View File

@@ -5,7 +5,10 @@ import (
"log" "log"
"math" "math"
"os" "os"
"os/exec"
"regexp"
"sort" "sort"
"strconv"
"strings" "strings"
"time" "time"
@@ -21,6 +24,22 @@ func (s timeSlice) Len() int { return len(s) }
func (s timeSlice) Less(i, j int) bool { return s[i].Before(s[j]) } func (s timeSlice) Less(i, j int) bool { return s[i].Before(s[j]) }
func (s timeSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s timeSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
type transactionList []*Transaction
func (t transactionList) Len() int { return len(t) }
func (t transactionList) Less(i, j int) bool {
if t[i].Date.Before(t[j].Date) {
return true
}
if t[i].Amount < 0 && t[j].Amount < 0 {
return math.Abs(t[i].Amount) > math.Abs(t[j].Amount)
}
return t[i].Amount > t[j].Amount
}
func (t transactionList) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
type Account struct { type Account struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Balance float64 `yaml:"balance"` Balance float64 `yaml:"balance"`
@@ -36,8 +55,7 @@ type Charge struct {
type Config struct { type Config struct {
Accounts []Account Accounts []Account
Credit []Transaction Transactions transactionList
Transactions []Transaction
} }
type ConfigYaml struct { type ConfigYaml struct {
@@ -53,11 +71,10 @@ type Transaction struct {
Date time.Time Date time.Time
Delayable bool Delayable bool
Frequency string Frequency string
HardDate bool
ID string ID string
Late bool Late bool
Name string Name string
OrigDate time.Time OriginDate time.Time
Shift string Shift string
} }
@@ -65,7 +82,7 @@ type TransactionYaml struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Amount float64 `yaml:"amount"` Amount float64 `yaml:"amount"`
Date string `yaml:"date"` Date string `yaml:"date"`
Delayable *bool `yaml:"delayable"` Delayable string `yaml:"delayable"`
Frequency string `yaml:"frequency,omitempty"` Frequency string `yaml:"frequency,omitempty"`
Shift string `yaml:"shift"` Shift string `yaml:"shift"`
} }
@@ -85,9 +102,9 @@ type LogDay struct {
Transactions map[string]*Transaction Transactions map[string]*Transaction
} }
func (t TransactionLog) dates() timeSlice { func (tl *TransactionLog) dates() timeSlice {
dateSlice := make(timeSlice, 0) dateSlice := make(timeSlice, 0)
for date, _ := range t.LogDays { for date := range tl.LogDays {
dateSlice = append(dateSlice, date) dateSlice = append(dateSlice, date)
} }
@@ -102,17 +119,16 @@ func (t TransactionLog) first() time.Time {
func (t TransactionLog) last() time.Time { func (t TransactionLog) last() time.Time {
return t.dates()[t.dates().Len()-1] return t.dates()[t.dates().Len()-1]
} }
func (logDay LogDay) transactions() []*Transaction { func (logDay LogDay) transactions() transactionList {
transactions := make([]*Transaction, 0) transactions := transactionList{}
for _, t := range logDay.Transactions { for _, t := range logDay.Transactions {
transactions = append(transactions, t) transactions = append(transactions, t)
} }
sortTransactions(transactions) sort.Sort(transactions)
return transactions return transactions
} }
@@ -141,55 +157,75 @@ func previousLogDay(transactionLog *TransactionLog, currentDate time.Time) *LogD
return transactionLog.LogDays[previous] return transactionLog.LogDays[previous]
} }
func parseConfig(configYaml *ConfigYaml) Config { func isWeekend(date time.Time) bool {
config := Config{ if date.Weekday() == 0 || date.Weekday() == 6 {
return true
}
return false
}
func parseDelayable(delayableString string) (bool, error) {
if delayableString == "" {
return true, nil
}
delayable, err := strconv.ParseBool(delayableString)
if err != nil {
return false, err
}
return delayable, nil
}
func parseTransaction(transactionYaml TransactionYaml) (*Transaction, error) {
date, err := parseDate(transactionYaml.Date)
if err != nil {
return nil, err
}
if transactionYaml.Shift == "" {
transactionYaml.Shift = "late"
}
delayable, err := parseDelayable(transactionYaml.Delayable)
if err != nil {
return nil, err
}
transaction := &Transaction{
Amount: transactionYaml.Amount,
Date: date,
Delayable: delayable,
Frequency: transactionYaml.Frequency,
ID: uuid.NewString(),
Late: false,
Name: transactionYaml.Name,
OriginDate: date,
Shift: transactionYaml.Shift,
}
return transaction, nil
}
func parseConfig(configYaml ConfigYaml) *Config {
config := &Config{
Accounts: configYaml.Accounts, Accounts: configYaml.Accounts,
Credit: make([]Transaction, 0), Transactions: make([]*Transaction, 0),
Transactions: make([]Transaction, 0),
} }
for _, transaction := range configYaml.Credit { for _, transaction := range configYaml.Credit {
formalDate, hardDate := parseDate(transaction.Date, transaction.Frequency) newTransaction, err := parseTransaction(transaction)
if transaction.Delayable == nil { if err != nil {
transaction.Delayable = &[]bool{true}[0] logrus.Error(err)
} }
if transaction.Shift == "" { config.Transactions = append(config.Transactions, newTransaction)
transaction.Shift = "late"
}
newTransaction := Transaction{
Amount: transaction.Amount,
Date: formalDate,
Delayable: *transaction.Delayable,
Frequency: transaction.Frequency,
HardDate: hardDate,
ID: uuid.NewString(),
Late: false,
Name: transaction.Name,
OrigDate: formalDate,
Shift: transaction.Shift,
}
config.Credit = append(config.Credit, newTransaction)
} }
for _, transaction := range configYaml.Transactions { for _, transaction := range configYaml.Transactions {
formalDate, hardDate := parseDate(transaction.Date, transaction.Frequency) newTransaction, err := parseTransaction(transaction)
if transaction.Delayable == nil { if err != nil {
transaction.Delayable = &[]bool{true}[0] logrus.Error(err)
}
if transaction.Shift == "" {
transaction.Shift = "late"
}
newTransaction := Transaction{
Amount: transaction.Amount,
Date: formalDate,
Delayable: *transaction.Delayable,
Frequency: transaction.Frequency,
HardDate: hardDate,
ID: uuid.NewString(),
Late: false,
Name: transaction.Name,
OrigDate: formalDate,
Shift: transaction.Shift,
} }
config.Transactions = append(config.Transactions, newTransaction) config.Transactions = append(config.Transactions, newTransaction)
} }
@@ -197,125 +233,110 @@ func parseConfig(configYaml *ConfigYaml) Config {
return config return config
} }
func getFirstDate(date time.Time, frequency string) time.Time { func dateIsFixed(dateString string) (bool, error) {
var firstDate time.Time return regexp.MatchString("^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$", dateString)
current := time.Date(time.Now().Year(),
time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC)
if date.Before(current) && frequency != "" {
firstDate = calculateFrequency(frequency, date, date.AddDate(0, 1, 1), false)[0]
} else {
firstDate = date.AddDate(0, 1, 0)
}
return firstDate
} }
func parseDate(date, frequency string) (time.Time, bool) { func parseDate(dateString string) (time.Time, error) {
hardDate := true currentDay := currentDay()
base := time.Now().AddDate(0, 0, -time.Now().Day()+1)
current := time.Date(base.Year(), regMatch, err := regexp.MatchString("^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$", dateString)
base.Month(), base.Day(), 0, 0, 0, 0, time.UTC)
dateString := string(date)
formalDate, err := time.Parse("2006-1-2", dateString)
if err != nil { if err != nil {
hardDate = false return time.Time{}, err
if date == "first" {
formalDate = current
if formalDate.Before(time.Now()) {
formalDate = getFirstDate(formalDate, frequency)
} }
} else if date == "last" {
formalDate = lastOfMonth(current) if regMatch {
return time.Parse("2006-1-2", dateString)
}
if dateString == "first" {
if currentDay.Day() == 1 {
return currentDay, nil
} else { } else {
dateString = fmt.Sprintf("%d-%d-%s", return firstOfMonth(currentDay).AddDate(0, 1, 0), nil
time.Now().Year(), int(time.Now().Month()), date)
formalDate, _ = time.Parse("2006-1-2", dateString)
if formalDate.Before(time.Now()) {
formalDate = getFirstDate(formalDate, frequency)
} }
} }
if dateString == "last" {
return lastOfMonth(currentDay), nil
} }
return formalDate, hardDate
dateString = fmt.Sprintf("%d-%d-%s", currentDay.Year(), currentDay.Month(), dateString)
parsedDate, err := time.Parse("2006-1-2", dateString)
if err != nil {
return time.Time{}, err
}
if parsedDate.Before(currentDay) {
return parsedDate.AddDate(0, 1, 0), nil
}
return parsedDate, nil
} }
func preFetchDates(transactions []Transaction, transactionLog *TransactionLog) { func ingestTransactions(transactions []*Transaction, transactionLog *TransactionLog) {
for _, transaction := range transactions {
if _, ok := transactionLog.LogDays[transaction.Date]; !ok && transaction.Amount != 0 {
logDay := LogDay{
Date: transaction.Date,
Transactions: make(map[string]*Transaction),
}
transactionLog.LogDays[transaction.Date] = &logDay
}
}
logDates := transactionLog.dates()
transactionLog.StartDate = logDates[0]
transactionLog.EndDate = logDates[len(logDates)-1]
}
func collectTransactions(transactions []Transaction, transactionLog *TransactionLog) {
for _, transaction := range transactions { for _, transaction := range transactions {
if math.Abs(transaction.Amount) != 0 { if math.Abs(transaction.Amount) != 0 {
addTransaction(transactionLog, &transaction) transactionLog.addTransaction(transaction)
} }
} }
} }
func sortTransactions(transactions []*Transaction) { func (tl *TransactionLog) addTransaction(transaction *Transaction) {
sort.Slice(transactions, func(i, j int) bool { if transaction.Frequency == "" {
if transactions[i].Amount < 0 && transactions[j].Amount < 0 { tl.addDay(transaction.Date)
return math.Abs(transactions[i].Amount) > math.Abs(transactions[j].Amount) tl.LogDays[transaction.Date].Transactions[transaction.ID] = transaction
} else { tl.Transactions[transaction.ID] = transaction
return transactions[i].Amount > transactions[j].Amount
}
})
}
func addTransaction(transactionLog *TransactionLog, transaction *Transaction) { logrus.Debugf("Adding transaction %s (%s | %f) to %s",
var dateList []time.Time transaction.ID, transaction.Name, transaction.Amount, transaction.Date.String())
if transaction.Frequency != "" {
dateList = calculateFrequency(transaction.Frequency, return
transaction.Date, transactionLog.EndDate, transaction.HardDate)
} else {
dateList = []time.Time{transaction.Date}
} }
dateList := calculateFrequency(transaction.Frequency,
transaction.Date, tl.EndDate, transaction.Delayable)
for _, date := range dateList { for _, date := range dateList {
if _, ok := transactionLog.LogDays[date]; !ok { tl.addDay(date)
logDay := LogDay{
Date: date,
Transactions: make(map[string]*Transaction),
}
transactionLog.LogDays[date] = &logDay
}
t := Transaction{ t := Transaction{
Amount: transaction.Amount, Amount: transaction.Amount,
Date: date, Date: date,
Delayable: transaction.Delayable, Delayable: transaction.Delayable,
Frequency: "", Frequency: "",
HardDate: false,
ID: uuid.NewString(), ID: uuid.NewString(),
Late: false, Late: false,
Name: transaction.Name, Name: transaction.Name,
OrigDate: date, OriginDate: date,
Shift: transaction.Shift, Shift: transaction.Shift,
} }
transactionLog.LogDays[date].Transactions[t.ID] = &t tl.LogDays[date].Transactions[t.ID] = &t
transactionLog.Transactions[t.ID] = &t tl.Transactions[t.ID] = &t
}
}
func (tl *TransactionLog) addDay(date time.Time) {
if _, ok := tl.LogDays[date]; !ok {
logrus.Debugf("Adding Log Day %s", date.String())
tl.LogDays[date] = &LogDay{
Date: stripTime(date),
Transactions: make(map[string]*Transaction),
}
}
}
func (tl *TransactionLog) deleteDay(date time.Time) {
if _, ok := tl.LogDays[date]; ok {
logrus.Debugf("Deleting Log Day %s", date.String())
delete(tl.LogDays, date)
} }
} }
func calculateShift(transactionLog TransactionLog, transaction Transaction) time.Time { func calculateShift(transactionLog TransactionLog, transaction Transaction) time.Time {
newDate := transaction.Date newDate := transaction.Date
if transaction.Late {
if transaction.Shift == "early" {
newDate = newDate.AddDate(0, 0, -1)
} else {
newDate = newDate.AddDate(0, 0, 1)
}
}
for newDate.Weekday() == 0 || newDate.Weekday() == 6 { for isWeekend(newDate) {
if transaction.Shift == "early" { if transaction.Shift == "early" {
newDate = newDate.AddDate(0, 0, -1) newDate = newDate.AddDate(0, 0, -1)
} else { } else {
@@ -326,74 +347,87 @@ func calculateShift(transactionLog TransactionLog, transaction Transaction) time
return newDate return newDate
} }
func delayTransaction(transactionLog *TransactionLog, transaction *Transaction) { func (tl *TransactionLog) delayTransaction(transaction *Transaction) {
if transaction.Date.Weekday() != 0 && transaction.Date.Weekday() != 6 { newDate := calculateShift(*tl, *transaction)
transaction.Late = true
}
newDate := calculateShift(*transactionLog, *transaction) if newDate.Before(tl.first()) || newDate == transaction.Date {
if newDate.Before(transactionLog.first()) || newDate.After(transactionLog.last()) || newDate == transaction.Date {
transaction.Delayable = false transaction.Delayable = false
return
} }
if _, ok := transactionLog.LogDays[newDate]; !ok { if newDate.After(tl.last()) {
logDay := LogDay{ newDate = tl.last()
Date: newDate,
Transactions: map[string]*Transaction{},
} }
transactionLog.LogDays[newDate] = &logDay tl.addDay(newDate)
}
delete(tl.LogDays[transaction.Date].Transactions, transaction.ID)
delete(transactionLog.LogDays[transaction.Date].Transactions, transaction.ID)
transaction.Date = newDate transaction.Date = newDate
transactionLog.LogDays[newDate].Transactions[transaction.ID] = transaction tl.LogDays[newDate].Transactions[transaction.ID] = transaction
} }
func executeTransaction(transactionLog *TransactionLog, transaction *Transaction) bool { func (tl *TransactionLog) executeTransaction(transaction *Transaction) bool {
logDay := transactionLog.LogDays[transaction.Date] logDay := tl.LogDays[transaction.Date]
accounts := make([]Account, len(logDay.EndingBalance)) accounts := make([]Account, len(logDay.EndingBalance))
copy(accounts, logDay.EndingBalance) copy(accounts, logDay.EndingBalance)
executed := false
finalDate := transactionLog.EndDate if isWeekend(transaction.Date) && transaction.Delayable {
if transaction.Date.Weekday() == 0 || transaction.Date.Weekday() == 6 && transaction.Delayable {
logrus.Debugf("Delaying %s due to weekend scheduling\n", transaction.Name) logrus.Debugf("Delaying %s due to weekend scheduling\n", transaction.Name)
delayTransaction(transactionLog, transaction) tl.delayTransaction(transaction)
} else if transaction.Amount > 0 {
logrus.Debugf("Executing Credit: %s %s (%f)\n", logDay.Date, transaction.Name, transaction.Amount) return false
accounts[0].Balance = accounts[0].Balance + transaction.Amount
executed = true
} else if transaction.Amount < 0 && math.Abs(transaction.Amount) > (accounts[0].Balance-accounts[0].Minimum) && transaction.Date.Before(finalDate) && transaction.Delayable {
logrus.Debugf("Balance: %f | Minimum: %f\n", accounts[0].Balance, accounts[0].Minimum)
logrus.Debugf("Delaying %s: %f (%s) due to lack of funds\n", transaction.Name, transaction.Amount, transaction.Date)
delayTransaction(transactionLog, transaction)
} else if transaction.Amount < 0 {
logrus.Debugf("Executing Charge: %s %s (%f)\n", logDay.Date, transaction.Name, transaction.Amount)
accounts[0].Balance = accounts[0].Balance + transaction.Amount
executed = true
} }
if !isAffordable(transaction.Amount, accounts[0].Balance, accounts[0].Minimum) && transaction.Delayable {
logrus.Debugf("Balance: %f | Minimum: %f\n", accounts[0].Balance, accounts[0].Minimum)
logrus.Debugf("Delaying %s: %f (%s) due to lack of funds\n",
transaction.Name, transaction.Amount, transaction.Date)
tl.delayTransaction(transaction)
return false
}
logrus.Debugf("Executing: %s (%s | %s | %f)",
transaction.ID, logDay.Date.String(), transaction.Name, transaction.Amount)
accounts[0].Balance = accounts[0].Balance + transaction.Amount
logDay.EndingBalance = make([]Account, len(accounts)) logDay.EndingBalance = make([]Account, len(accounts))
copy(logDay.EndingBalance, accounts) copy(logDay.EndingBalance, accounts)
return executed return true
}
func isAffordable(charge, balance, minimum float64) bool {
if charge > 0 {
return true
}
return math.Abs(charge) > (balance - minimum)
}
func currentDay() time.Time {
return stripTime(time.Now())
} }
func firstOfMonth(t time.Time) time.Time { func firstOfMonth(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, time.UTC) return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, time.Now().Location())
} }
func lastOfMonth(t time.Time) time.Time { func lastOfMonth(t time.Time) time.Time {
return time.Date(t.Year(), t.Month()+1, 0, 0, 0, 0, 0, time.UTC) return time.Date(t.Year(), t.Month()+1, 0, 0, 0, 0, 0, time.Now().Location())
}
func stripTime(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.Now().Location())
} }
func calculateFrequency(frequency string, startDate time.Time, endDate time.Time, hardDate bool) []time.Time { func calculateFrequency(frequency string, startDate time.Time, endDate time.Time, hardDate bool) []time.Time {
base := time.Now() current := currentDay()
current := time.Date(base.Year(),
base.Month(), base.Day(), 0, 0, 0, 0, time.UTC)
initDate := startDate initDate := startDate
dateList := make([]time.Time, 0) dateList := make([]time.Time, 0)
for initDate.Before(endDate.AddDate(0, 0, 1)) { for initDate.Before(endDate.AddDate(0, 0, 1)) {
if !initDate.Before(current) || hardDate { if !initDate.Before(current) || hardDate {
dateList = append(dateList, initDate) dateList = append(dateList, initDate)
@@ -411,9 +445,10 @@ func calculateFrequency(frequency string, startDate time.Time, endDate time.Time
initDate = initDate.AddDate(0, 1, 0) initDate = initDate.AddDate(0, 1, 0)
} }
} else { } else {
break logrus.Error("Invalid Frequency")
} }
} }
return dateList return dateList
} }
@@ -437,69 +472,75 @@ func categorizeTransactions(transactions map[string]*Transaction) map[string][]s
return categorized return categorized
} }
func processTransactionLog(transactionLog *TransactionLog, accounts []Account) bool { func (tl *TransactionLog) processTransactions(accounts []Account) bool {
shifted := false shifted := false
endBalance := accounts endBalance := accounts
logrus.Debugf("\n\nITERATION %d\n\n", transactionLog.Iteration)
if transactionLog.Iteration == 0 { logrus.Debugf("\n\nITERATION %d\n\n", tl.Iteration)
for _, date := range transactionLog.dates() { if tl.Iteration == 0 {
logDay := transactionLog.LogDays[date] for _, date := range tl.dates() {
logrus.Debugf("Processing Log Day %s", date.String())
logDay := tl.LogDays[date]
logDay.StartingBalance = make([]Account, len(endBalance)) logDay.StartingBalance = make([]Account, len(endBalance))
logDay.EndingBalance = make([]Account, len(endBalance)) logDay.EndingBalance = make([]Account, len(endBalance))
copy(logDay.StartingBalance, endBalance) copy(logDay.StartingBalance, endBalance)
copy(logDay.EndingBalance, endBalance) copy(logDay.EndingBalance, endBalance)
categorized := categorizeTransactions(logDay.Transactions) categorized := categorizeTransactions(logDay.Transactions)
logrus.Debugln(categorized) logrus.Debugln(categorized)
if processTransactions(transactionLog, categorized["credits"]) { processTransactionsByID(tl, categorized["credits"])
shifted = true logrus.Debugf("Starting Balance %s: %f", date, logDay.StartingBalance[0].Balance)
} logrus.Debugf("Ending Balance %s: %f", date, logDay.EndingBalance[0].Balance)
logrus.Debugf("Starting Balance %s: %f\n", date, logDay.StartingBalance[0].Balance)
logrus.Debugf("Ending Balance %s: %f\n", date, logDay.EndingBalance[0].Balance)
endBalance = logDay.EndingBalance endBalance = logDay.EndingBalance
shifted = true
} }
} else { } else {
for _, date := range transactionLog.dates() { for _, date := range tl.dates() {
logDay := transactionLog.LogDays[date] logrus.Debugf("Processing Log Day %s", date.String())
logDay := tl.LogDays[date]
logDay.StartingBalance = make([]Account, len(endBalance)) logDay.StartingBalance = make([]Account, len(endBalance))
logDay.EndingBalance = make([]Account, len(endBalance)) logDay.EndingBalance = make([]Account, len(endBalance))
copy(logDay.StartingBalance, endBalance) copy(logDay.StartingBalance, endBalance)
copy(logDay.EndingBalance, endBalance) copy(logDay.EndingBalance, endBalance)
categorized := categorizeTransactions(logDay.Transactions) categorized := categorizeTransactions(logDay.Transactions)
logrus.Debugln(categorized) logrus.Debugln(categorized)
if processTransactions(transactionLog, categorized["credits"]) { logrus.Debugln(logDay.Transactions)
if processTransactionsByID(tl, categorized["credits"]) {
shifted = true shifted = true
} }
if processTransactions(transactionLog, categorized["late"]) { if processTransactionsByID(tl, categorized["late"]) {
shifted = true shifted = true
} }
if processTransactions(transactionLog, categorized["standard"]) { if processTransactionsByID(tl, categorized["standard"]) {
shifted = true shifted = true
} }
logrus.Debugf("Starting Balance %s: %f\n", date, logDay.StartingBalance[0].Balance) logrus.Debugf("Starting Balance %s: %f", date, logDay.StartingBalance[0].Balance)
logrus.Debugf("Ending Balance %s: %f\n", date, logDay.EndingBalance[0].Balance) logrus.Debugf("Ending Balance %s: %f", date, logDay.EndingBalance[0].Balance)
endBalance = logDay.EndingBalance endBalance = logDay.EndingBalance
if shifted { if shifted {
break break
} }
if len(logDay.Transactions) == 0 { if len(logDay.Transactions) == 0 {
delete(transactionLog.LogDays, logDay.Date) tl.deleteDay(date)
} }
} }
} }
transactionLog.Iteration++ tl.Iteration++
return shifted return shifted
} }
func processTransactions(transactionLog *TransactionLog, transactionIDs []string) bool { func processTransactionsByID(transactionLog *TransactionLog, transactionIDs []string) bool {
shifted := false shifted := false
for _, id := range transactionIDs { for _, id := range transactionIDs {
transaction, ok := transactionLog.Transactions[id] transaction, ok := transactionLog.Transactions[id]
logrus.Debugf("Processing %s (%f)", transaction.Name, transaction.Amount)
if !ok { if !ok {
logrus.Fatalf("Transaction ID: %s not found in log\n", id) logrus.Fatalf("Transaction ID: %s not found in log\n", id)
} }
if !executeTransaction(transactionLog, transaction) { logrus.Debugf("Processing %s (%s | %f)", id, transaction.Name, transaction.Amount)
if !transactionLog.executeTransaction(transaction) {
shifted = true shifted = true
} }
} }
@@ -508,19 +549,34 @@ func processTransactions(transactionLog *TransactionLog, transactionIDs []string
} }
func printOutput(logDay LogDay) { func printOutput(logDay LogDay) {
delim := strings.Repeat("=", 67) cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err := cmd.Output()
if err != nil {
logrus.Fatal(err)
}
widthStr := string(out[:len(out)-1])
sttyOut := strings.Split(widthStr, " ")
fullWidth, err := strconv.Atoi(sttyOut[1])
fullWidth = fullWidth / 2
width := fullWidth - 2
if err != nil {
logrus.Fatal(err)
}
delim := strings.Repeat("=", fullWidth)
divider := delim divider := delim
delim2 := strings.Repeat("-", 66) delim2 := strings.Repeat("-", fullWidth-1)
divider2 := delim2 + "|" divider2 := delim2 + "|"
fmt.Println(divider) fmt.Println(divider)
outString := fmt.Sprint(logDay.Date.Format("Jan 02 2006")) outString := fmt.Sprint(logDay.Date.Format("Jan 02 2006"))
rear_spacer := strings.Repeat(" ", 65-len(outString)) rear_spacer := strings.Repeat(" ", width-len(outString))
fmt.Printf("%s%s |\n", outString, rear_spacer) fmt.Printf("%s%s |\n", outString, rear_spacer)
for _, v := range logDay.StartingBalance { for _, v := range logDay.StartingBalance {
spacer := strings.Repeat(" ", 19-len(fmt.Sprintf("%.2f", v.Balance))) spacer := strings.Repeat(" ", 19-len(fmt.Sprintf("%.2f", v.Balance)))
outString := fmt.Sprintf("$%.2f%s| %s", v.Balance, spacer, v.Name) outString := fmt.Sprintf("$%.2f%s| %s", v.Balance, spacer, v.Name)
rear_spacer := strings.Repeat(" ", 65-len(outString)) rear_spacer := strings.Repeat(" ", width-len(outString))
fmt.Printf("%s%s |\n", outString, rear_spacer) fmt.Printf("%s%s |\n", outString, rear_spacer)
} }
for _, charge := range logDay.transactions() { for _, charge := range logDay.transactions() {
@@ -533,15 +589,15 @@ func printOutput(logDay LogDay) {
} }
outString = fmt.Sprintf("%s$%.2f%s| %s", outString, math.Abs(charge.Amount), spacer, charge.Name) outString = fmt.Sprintf("%s$%.2f%s| %s", outString, math.Abs(charge.Amount), spacer, charge.Name)
if charge.Late { if charge.Late {
late_tag := fmt.Sprintf("(%s - %s)", "Late", charge.OrigDate.Format("Jan 02 2006")) late_tag := fmt.Sprintf("(%s - %s)", "Late", charge.OriginDate.Format("Jan 02 2006"))
rear_spacer := strings.Repeat(" ", 65-len(late_tag)-len(outString)) rear_spacer := strings.Repeat(" ", width-len(late_tag)-len(outString))
fmt.Printf("%s%s%s |\n", outString, rear_spacer, late_tag) fmt.Printf("%s%s%s |\n", outString, rear_spacer, late_tag)
} else if charge.Date != charge.OrigDate { } else if charge.Date != charge.OriginDate {
weekend_tag := fmt.Sprintf("(%s - %s)", "Weekend", charge.OrigDate.Format("Jan 02 2006")) weekend_tag := fmt.Sprintf("(%s - %s)", "Weekend", charge.OriginDate.Format("Jan 02 2006"))
rear_spacer := strings.Repeat(" ", 65-len(weekend_tag)-len(outString)) rear_spacer := strings.Repeat(" ", width-len(weekend_tag)-len(outString))
fmt.Printf("%s%s%s |\n", outString, rear_spacer, weekend_tag) fmt.Printf("%s%s%s |\n", outString, rear_spacer, weekend_tag)
} else { } else {
rear_spacer := strings.Repeat(" ", 65-len(outString)) rear_spacer := strings.Repeat(" ", width-len(outString))
fmt.Printf("%s%s |\n", outString, rear_spacer) fmt.Printf("%s%s |\n", outString, rear_spacer)
} }
} }
@@ -549,7 +605,7 @@ func printOutput(logDay LogDay) {
for _, v := range logDay.EndingBalance { for _, v := range logDay.EndingBalance {
spacer := strings.Repeat(" ", 19-len(fmt.Sprintf("%.2f", v.Balance))) spacer := strings.Repeat(" ", 19-len(fmt.Sprintf("%.2f", v.Balance)))
outString := fmt.Sprintf("$%.2f%s| %s", v.Balance, spacer, v.Name) outString := fmt.Sprintf("$%.2f%s| %s", v.Balance, spacer, v.Name)
rear_spacer := strings.Repeat(" ", 65-len(outString)) rear_spacer := strings.Repeat(" ", width-len(outString))
fmt.Printf("%s%s |\n", outString, rear_spacer) fmt.Printf("%s%s |\n", outString, rear_spacer)
} }
fmt.Println(divider + "\n") fmt.Println(divider + "\n")
@@ -561,26 +617,21 @@ func main() {
} }
yfile, err := os.ReadFile(os.Args[1]) yfile, err := os.ReadFile(os.Args[1])
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
var configYaml ConfigYaml var configYaml ConfigYaml
transactionLog := TransactionLog{
LogDays: make(map[time.Time]*LogDay),
Transactions: make(map[string]*Transaction),
}
err = yaml.Unmarshal(yfile, &configYaml) err = yaml.Unmarshal(yfile, &configYaml)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
config := parseConfig(&configYaml) transactionLog := &TransactionLog{
preFetchDates(config.Credit, &transactionLog) LogDays: make(map[time.Time]*LogDay),
preFetchDates(config.Transactions, &transactionLog) Transactions: make(map[string]*Transaction),
}
if configYaml.Start != "" { if configYaml.Start != "" {
transactionLog.StartDate, err = time.Parse("2006-1-2", configYaml.Start) transactionLog.StartDate, err = time.Parse("2006-1-2", configYaml.Start)
@@ -596,13 +647,24 @@ func main() {
} }
} }
collectTransactions(config.Credit, &transactionLog) config := parseConfig(configYaml)
collectTransactions(config.Transactions, &transactionLog)
sort.Sort(config.Transactions)
if transactionLog.StartDate == (time.Time{}) {
transactionLog.StartDate = config.Transactions[0].Date
}
if transactionLog.EndDate == (time.Time{}) {
transactionLog.EndDate = config.Transactions[len(config.Transactions)-1].Date
}
ingestTransactions(config.Transactions, transactionLog)
shifted := true shifted := true
for shifted { for shifted {
shifted = false shifted = false
shifted = processTransactionLog(&transactionLog, config.Accounts) shifted = transactionLog.processTransactions(config.Accounts)
} }
for _, date := range transactionLog.dates() { for _, date := range transactionLog.dates() {