From 8a0ad44041a980daac8491a7e3b8b5250943bf6a Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Sat, 11 Mar 2023 02:37:54 -0500 Subject: [PATCH] Logic fixes and general cleanup --- main.go | 538 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 300 insertions(+), 238 deletions(-) diff --git a/main.go b/main.go index 0d82e20..8ef7385 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,10 @@ import ( "log" "math" "os" + "os/exec" + "regexp" "sort" + "strconv" "strings" "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) 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 { Name string `yaml:"name"` Balance float64 `yaml:"balance"` @@ -36,8 +55,7 @@ type Charge struct { type Config struct { Accounts []Account - Credit []Transaction - Transactions []Transaction + Transactions transactionList } type ConfigYaml struct { @@ -49,23 +67,22 @@ type ConfigYaml struct { } type Transaction struct { - Amount float64 - Date time.Time - Delayable bool - Frequency string - HardDate bool - ID string - Late bool - Name string - OrigDate time.Time - Shift string + Amount float64 + Date time.Time + Delayable bool + Frequency string + ID string + Late bool + Name string + OriginDate time.Time + Shift string } type TransactionYaml struct { Name string `yaml:"name"` Amount float64 `yaml:"amount"` Date string `yaml:"date"` - Delayable *bool `yaml:"delayable"` + Delayable string `yaml:"delayable"` Frequency string `yaml:"frequency,omitempty"` Shift string `yaml:"shift"` } @@ -85,9 +102,9 @@ type LogDay struct { Transactions map[string]*Transaction } -func (t TransactionLog) dates() timeSlice { +func (tl *TransactionLog) dates() timeSlice { dateSlice := make(timeSlice, 0) - for date, _ := range t.LogDays { + for date := range tl.LogDays { dateSlice = append(dateSlice, date) } @@ -102,17 +119,16 @@ func (t TransactionLog) first() time.Time { func (t TransactionLog) last() time.Time { return t.dates()[t.dates().Len()-1] - } -func (logDay LogDay) transactions() []*Transaction { - transactions := make([]*Transaction, 0) +func (logDay LogDay) transactions() transactionList { + transactions := transactionList{} for _, t := range logDay.Transactions { transactions = append(transactions, t) } - sortTransactions(transactions) + sort.Sort(transactions) return transactions } @@ -141,55 +157,75 @@ func previousLogDay(transactionLog *TransactionLog, currentDate time.Time) *LogD return transactionLog.LogDays[previous] } -func parseConfig(configYaml *ConfigYaml) Config { - config := Config{ +func isWeekend(date time.Time) bool { + 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, - Credit: make([]Transaction, 0), - Transactions: make([]Transaction, 0), + Transactions: make([]*Transaction, 0), } for _, transaction := range configYaml.Credit { - formalDate, hardDate := parseDate(transaction.Date, transaction.Frequency) - if transaction.Delayable == nil { - transaction.Delayable = &[]bool{true}[0] + newTransaction, err := parseTransaction(transaction) + if err != nil { + 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.Credit = append(config.Credit, newTransaction) + config.Transactions = append(config.Transactions, newTransaction) } for _, transaction := range configYaml.Transactions { - formalDate, hardDate := parseDate(transaction.Date, transaction.Frequency) - if transaction.Delayable == nil { - transaction.Delayable = &[]bool{true}[0] - } - 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, + newTransaction, err := parseTransaction(transaction) + if err != nil { + logrus.Error(err) } config.Transactions = append(config.Transactions, newTransaction) } @@ -197,125 +233,110 @@ func parseConfig(configYaml *ConfigYaml) Config { return config } -func getFirstDate(date time.Time, frequency string) time.Time { - var firstDate time.Time - - 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 dateIsFixed(dateString string) (bool, error) { + return regexp.MatchString("^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$", dateString) } -func parseDate(date, frequency string) (time.Time, bool) { - hardDate := true - base := time.Now().AddDate(0, 0, -time.Now().Day()+1) - current := time.Date(base.Year(), - base.Month(), base.Day(), 0, 0, 0, 0, time.UTC) - dateString := string(date) - formalDate, err := time.Parse("2006-1-2", dateString) +func parseDate(dateString string) (time.Time, error) { + currentDay := currentDay() + + regMatch, err := regexp.MatchString("^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$", dateString) if err != nil { - hardDate = false - if date == "first" { - formalDate = current - if formalDate.Before(time.Now()) { - formalDate = getFirstDate(formalDate, frequency) - } - } else if date == "last" { - formalDate = lastOfMonth(current) + return time.Time{}, err + } + + if regMatch { + return time.Parse("2006-1-2", dateString) + } + + if dateString == "first" { + if currentDay.Day() == 1 { + return currentDay, nil } else { - dateString = fmt.Sprintf("%d-%d-%s", - time.Now().Year(), int(time.Now().Month()), date) - formalDate, _ = time.Parse("2006-1-2", dateString) - if formalDate.Before(time.Now()) { - formalDate = getFirstDate(formalDate, frequency) - } + return firstOfMonth(currentDay).AddDate(0, 1, 0), nil } } - return formalDate, hardDate -} -func preFetchDates(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 - } + if dateString == "last" { + return lastOfMonth(currentDay), nil } - logDates := transactionLog.dates() - transactionLog.StartDate = logDates[0] - transactionLog.EndDate = logDates[len(logDates)-1] + + 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 collectTransactions(transactions []Transaction, transactionLog *TransactionLog) { +func ingestTransactions(transactions []*Transaction, transactionLog *TransactionLog) { for _, transaction := range transactions { if math.Abs(transaction.Amount) != 0 { - addTransaction(transactionLog, &transaction) + transactionLog.addTransaction(transaction) } } } -func sortTransactions(transactions []*Transaction) { - sort.Slice(transactions, func(i, j int) bool { - if transactions[i].Amount < 0 && transactions[j].Amount < 0 { - return math.Abs(transactions[i].Amount) > math.Abs(transactions[j].Amount) - } else { - return transactions[i].Amount > transactions[j].Amount - } - }) -} +func (tl *TransactionLog) addTransaction(transaction *Transaction) { + if transaction.Frequency == "" { + tl.addDay(transaction.Date) + tl.LogDays[transaction.Date].Transactions[transaction.ID] = transaction + tl.Transactions[transaction.ID] = transaction -func addTransaction(transactionLog *TransactionLog, transaction *Transaction) { - var dateList []time.Time - if transaction.Frequency != "" { - dateList = calculateFrequency(transaction.Frequency, - transaction.Date, transactionLog.EndDate, transaction.HardDate) - } else { - dateList = []time.Time{transaction.Date} + logrus.Debugf("Adding transaction %s (%s | %f) to %s", + transaction.ID, transaction.Name, transaction.Amount, transaction.Date.String()) + + return } + + dateList := calculateFrequency(transaction.Frequency, + transaction.Date, tl.EndDate, transaction.Delayable) + for _, date := range dateList { - if _, ok := transactionLog.LogDays[date]; !ok { - logDay := LogDay{ - Date: date, - Transactions: make(map[string]*Transaction), - } - transactionLog.LogDays[date] = &logDay - } + tl.addDay(date) t := Transaction{ - Amount: transaction.Amount, - Date: date, - Delayable: transaction.Delayable, - Frequency: "", - HardDate: false, - ID: uuid.NewString(), - Late: false, - Name: transaction.Name, - OrigDate: date, - Shift: transaction.Shift, + Amount: transaction.Amount, + Date: date, + Delayable: transaction.Delayable, + Frequency: "", + ID: uuid.NewString(), + Late: false, + Name: transaction.Name, + OriginDate: date, + Shift: transaction.Shift, } - transactionLog.LogDays[date].Transactions[t.ID] = &t - transactionLog.Transactions[t.ID] = &t + tl.LogDays[date].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 { 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" { newDate = newDate.AddDate(0, 0, -1) } else { @@ -326,74 +347,87 @@ func calculateShift(transactionLog TransactionLog, transaction Transaction) time return newDate } -func delayTransaction(transactionLog *TransactionLog, transaction *Transaction) { - if transaction.Date.Weekday() != 0 && transaction.Date.Weekday() != 6 { - transaction.Late = true - } +func (tl *TransactionLog) delayTransaction(transaction *Transaction) { + newDate := calculateShift(*tl, *transaction) - newDate := calculateShift(*transactionLog, *transaction) - if newDate.Before(transactionLog.first()) || newDate.After(transactionLog.last()) || newDate == transaction.Date { + if newDate.Before(tl.first()) || newDate == transaction.Date { transaction.Delayable = false + return } - if _, ok := transactionLog.LogDays[newDate]; !ok { - logDay := LogDay{ - Date: newDate, - Transactions: map[string]*Transaction{}, - } - - transactionLog.LogDays[newDate] = &logDay + if newDate.After(tl.last()) { + newDate = tl.last() } - delete(transactionLog.LogDays[transaction.Date].Transactions, transaction.ID) + tl.addDay(newDate) + + delete(tl.LogDays[transaction.Date].Transactions, transaction.ID) + transaction.Date = newDate - transactionLog.LogDays[newDate].Transactions[transaction.ID] = transaction - + tl.LogDays[newDate].Transactions[transaction.ID] = transaction } -func executeTransaction(transactionLog *TransactionLog, transaction *Transaction) bool { - logDay := transactionLog.LogDays[transaction.Date] +func (tl *TransactionLog) executeTransaction(transaction *Transaction) bool { + logDay := tl.LogDays[transaction.Date] accounts := make([]Account, len(logDay.EndingBalance)) copy(accounts, logDay.EndingBalance) - executed := false - finalDate := transactionLog.EndDate - if transaction.Date.Weekday() == 0 || transaction.Date.Weekday() == 6 && transaction.Delayable { + + if isWeekend(transaction.Date) && transaction.Delayable { logrus.Debugf("Delaying %s due to weekend scheduling\n", transaction.Name) - delayTransaction(transactionLog, transaction) - } else if transaction.Amount > 0 { - logrus.Debugf("Executing Credit: %s %s (%f)\n", logDay.Date, transaction.Name, transaction.Amount) - 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 + tl.delayTransaction(transaction) + + return false } + 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)) 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 { - 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 { - 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 { - base := time.Now() - current := time.Date(base.Year(), - base.Month(), base.Day(), 0, 0, 0, 0, time.UTC) + current := currentDay() initDate := startDate dateList := make([]time.Time, 0) + for initDate.Before(endDate.AddDate(0, 0, 1)) { if !initDate.Before(current) || hardDate { dateList = append(dateList, initDate) @@ -411,9 +445,10 @@ func calculateFrequency(frequency string, startDate time.Time, endDate time.Time initDate = initDate.AddDate(0, 1, 0) } } else { - break + logrus.Error("Invalid Frequency") } } + return dateList } @@ -437,69 +472,75 @@ func categorizeTransactions(transactions map[string]*Transaction) map[string][]s return categorized } -func processTransactionLog(transactionLog *TransactionLog, accounts []Account) bool { +func (tl *TransactionLog) processTransactions(accounts []Account) bool { shifted := false endBalance := accounts - logrus.Debugf("\n\nITERATION %d\n\n", transactionLog.Iteration) - if transactionLog.Iteration == 0 { - for _, date := range transactionLog.dates() { - logDay := transactionLog.LogDays[date] + + logrus.Debugf("\n\nITERATION %d\n\n", tl.Iteration) + if tl.Iteration == 0 { + for _, date := range tl.dates() { + logrus.Debugf("Processing Log Day %s", date.String()) + logDay := tl.LogDays[date] logDay.StartingBalance = make([]Account, len(endBalance)) logDay.EndingBalance = make([]Account, len(endBalance)) copy(logDay.StartingBalance, endBalance) copy(logDay.EndingBalance, endBalance) categorized := categorizeTransactions(logDay.Transactions) logrus.Debugln(categorized) - if processTransactions(transactionLog, categorized["credits"]) { - shifted = true - } - logrus.Debugf("Starting Balance %s: %f\n", date, logDay.StartingBalance[0].Balance) - logrus.Debugf("Ending Balance %s: %f\n", date, logDay.EndingBalance[0].Balance) + processTransactionsByID(tl, categorized["credits"]) + logrus.Debugf("Starting Balance %s: %f", date, logDay.StartingBalance[0].Balance) + logrus.Debugf("Ending Balance %s: %f", date, logDay.EndingBalance[0].Balance) endBalance = logDay.EndingBalance + + shifted = true } } else { - for _, date := range transactionLog.dates() { - 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.EndingBalance = make([]Account, len(endBalance)) copy(logDay.StartingBalance, endBalance) copy(logDay.EndingBalance, endBalance) categorized := categorizeTransactions(logDay.Transactions) logrus.Debugln(categorized) - if processTransactions(transactionLog, categorized["credits"]) { + logrus.Debugln(logDay.Transactions) + if processTransactionsByID(tl, categorized["credits"]) { shifted = true } - if processTransactions(transactionLog, categorized["late"]) { + if processTransactionsByID(tl, categorized["late"]) { shifted = true } - if processTransactions(transactionLog, categorized["standard"]) { + if processTransactionsByID(tl, categorized["standard"]) { shifted = true } - logrus.Debugf("Starting Balance %s: %f\n", date, logDay.StartingBalance[0].Balance) - logrus.Debugf("Ending Balance %s: %f\n", date, logDay.EndingBalance[0].Balance) + logrus.Debugf("Starting Balance %s: %f", date, logDay.StartingBalance[0].Balance) + logrus.Debugf("Ending Balance %s: %f", date, logDay.EndingBalance[0].Balance) endBalance = logDay.EndingBalance if shifted { break } if len(logDay.Transactions) == 0 { - delete(transactionLog.LogDays, logDay.Date) + tl.deleteDay(date) } + } } - transactionLog.Iteration++ + tl.Iteration++ return shifted } -func processTransactions(transactionLog *TransactionLog, transactionIDs []string) bool { +func processTransactionsByID(transactionLog *TransactionLog, transactionIDs []string) bool { shifted := false + for _, id := range transactionIDs { transaction, ok := transactionLog.Transactions[id] - logrus.Debugf("Processing %s (%f)", transaction.Name, transaction.Amount) if !ok { 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 } } @@ -508,19 +549,34 @@ func processTransactions(transactionLog *TransactionLog, transactionIDs []string } 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 - delim2 := strings.Repeat("-", 66) + delim2 := strings.Repeat("-", fullWidth-1) divider2 := delim2 + "|" fmt.Println(divider) 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) for _, v := range logDay.StartingBalance { spacer := strings.Repeat(" ", 19-len(fmt.Sprintf("%.2f", v.Balance))) 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) } 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) if charge.Late { - late_tag := fmt.Sprintf("(%s - %s)", "Late", charge.OrigDate.Format("Jan 02 2006")) - rear_spacer := strings.Repeat(" ", 65-len(late_tag)-len(outString)) + late_tag := fmt.Sprintf("(%s - %s)", "Late", charge.OriginDate.Format("Jan 02 2006")) + rear_spacer := strings.Repeat(" ", width-len(late_tag)-len(outString)) fmt.Printf("%s%s%s |\n", outString, rear_spacer, late_tag) - } else if charge.Date != charge.OrigDate { - weekend_tag := fmt.Sprintf("(%s - %s)", "Weekend", charge.OrigDate.Format("Jan 02 2006")) - rear_spacer := strings.Repeat(" ", 65-len(weekend_tag)-len(outString)) + } else if charge.Date != charge.OriginDate { + weekend_tag := fmt.Sprintf("(%s - %s)", "Weekend", charge.OriginDate.Format("Jan 02 2006")) + rear_spacer := strings.Repeat(" ", width-len(weekend_tag)-len(outString)) fmt.Printf("%s%s%s |\n", outString, rear_spacer, weekend_tag) } else { - rear_spacer := strings.Repeat(" ", 65-len(outString)) + rear_spacer := strings.Repeat(" ", width-len(outString)) fmt.Printf("%s%s |\n", outString, rear_spacer) } } @@ -549,7 +605,7 @@ func printOutput(logDay LogDay) { for _, v := range logDay.EndingBalance { spacer := strings.Repeat(" ", 19-len(fmt.Sprintf("%.2f", v.Balance))) 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.Println(divider + "\n") @@ -561,26 +617,21 @@ func main() { } yfile, err := os.ReadFile(os.Args[1]) - if err != nil { log.Fatal(err) } var configYaml ConfigYaml - transactionLog := TransactionLog{ - LogDays: make(map[time.Time]*LogDay), - Transactions: make(map[string]*Transaction), - } - err = yaml.Unmarshal(yfile, &configYaml) if err != nil { log.Fatal(err) } - config := parseConfig(&configYaml) - preFetchDates(config.Credit, &transactionLog) - preFetchDates(config.Transactions, &transactionLog) + transactionLog := &TransactionLog{ + LogDays: make(map[time.Time]*LogDay), + Transactions: make(map[string]*Transaction), + } if configYaml.Start != "" { transactionLog.StartDate, err = time.Parse("2006-1-2", configYaml.Start) @@ -596,13 +647,24 @@ func main() { } } - collectTransactions(config.Credit, &transactionLog) - collectTransactions(config.Transactions, &transactionLog) + config := parseConfig(configYaml) + + 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 for shifted { shifted = false - shifted = processTransactionLog(&transactionLog, config.Accounts) + shifted = transactionLog.processTransactions(config.Accounts) } for _, date := range transactionLog.dates() {