From b8baff3d5f6deb4f5ed0eb78806810da30585ccd Mon Sep 17 00:00:00 2001 From: Samuel Attwood Date: Sun, 2 Oct 2022 16:46:20 -0400 Subject: [PATCH] Refactored processing to permit multiple permutations prior to final print --- main.go | 323 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 219 insertions(+), 104 deletions(-) diff --git a/main.go b/main.go index 6b02b47..3e4eed3 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/google/uuid" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" @@ -16,9 +17,9 @@ import ( type timeSlice []time.Time +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] } -func (s timeSlice) Len() int { return len(s) } type Account struct { Name string `yaml:"name"` @@ -53,9 +54,11 @@ type Transaction struct { Delayable bool Frequency string HardDate bool + ID string Late bool Name string OrigDate time.Time + Shift string } type TransactionYaml struct { @@ -64,15 +67,67 @@ type TransactionYaml struct { Date string `yaml:"date"` Delayable *bool `yaml:"delayable"` Frequency string `yaml:"frequency,omitempty"` + Shift string `yaml:"shift` } type TransactionLog struct { - Transactions map[time.Time][]Transaction - Dates []time.Time + Transactions map[time.Time]*LogDay StartDate time.Time EndDate time.Time } +type LogDay struct { + Date time.Time + StartingBalance []Account + EndingBalance []Account + Executed []*Transaction + Transactions map[string]*Transaction +} + +func (t TransactionLog) dates() timeSlice { + dateSlice := make(timeSlice, 0) + for date, _ := range t.Transactions { + dateSlice = append(dateSlice, date) + } + + sort.Sort(dateSlice) + + return dateSlice +} + +func (t TransactionLog) first() time.Time { + return t.dates()[0] +} + +func (t TransactionLog) last() time.Time { + return t.dates()[t.dates().Len()-1] + +} + +func nextLogDay(transactionLog *TransactionLog, currentDate time.Time) LogDay { + var next time.Time + for i, date := range transactionLog.dates() { + if date == currentDate { + next = transactionLog.dates()[i+1] + break + } + } + + return *transactionLog.Transactions[next] +} + +func previousLogDay(transactionLog *TransactionLog, currentDate time.Time) LogDay { + var previous time.Time + for _, date := range transactionLog.dates() { + if date == currentDate { + break + } + previous = date + } + + return *transactionLog.Transactions[previous] +} + func parseConfig(configYaml *ConfigYaml) Config { config := Config{ Accounts: configYaml.Accounts, @@ -85,17 +140,22 @@ func parseConfig(configYaml *ConfigYaml) Config { 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, - OrigDate: formalDate, - HardDate: hardDate, 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.Credit = append(config.Credit, newTransaction) } for _, transaction := range configYaml.Transactions { @@ -103,17 +163,22 @@ func parseConfig(configYaml *ConfigYaml) Config { 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, - OrigDate: formalDate, - HardDate: hardDate, 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) } return config @@ -149,14 +214,17 @@ func parseDate(date string) (time.Time, bool) { func preFetchDates(transactions []Transaction, transactionLog *TransactionLog) { for _, transaction := range transactions { - if _, ok := transactionLog.Transactions[transaction.Date]; !ok { - transactionLog.Transactions[transaction.Date] = make([]Transaction, 0) - transactionLog.Dates = append(transactionLog.Dates, transaction.Date) + if _, ok := transactionLog.Transactions[transaction.Date]; !ok && transaction.Amount != 0 { + logDay := LogDay{ + Date: transaction.Date, + Transactions: make(map[string]*Transaction), + } + transactionLog.Transactions[transaction.Date] = &logDay } } - sort.Sort(timeSlice(transactionLog.Dates)) - transactionLog.StartDate = transactionLog.Dates[0] - transactionLog.EndDate = transactionLog.Dates[len(transactionLog.Dates)-1] + logDates := transactionLog.dates() + transactionLog.StartDate = logDates[0] + transactionLog.EndDate = logDates[len(logDates)-1] } func collectTransactions(transactions []Transaction, transactionLog *TransactionLog) { @@ -165,13 +233,9 @@ func collectTransactions(transactions []Transaction, transactionLog *Transaction addTransaction(transactionLog, &transaction) } } - sort.Sort(timeSlice(transactionLog.Dates)) - for _, date := range transactionLog.Dates { - sortTransactions(transactionLog.Transactions[date]) - } } -func sortTransactions(transactions []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) @@ -191,47 +255,73 @@ func addTransaction(transactionLog *TransactionLog, transaction *Transaction) { } for _, date := range dateList { if _, ok := transactionLog.Transactions[date]; !ok { - transactionLog.Dates = append(transactionLog.Dates, date) + logDay := LogDay{ + Date: date, + Transactions: make(map[string]*Transaction), + } + transactionLog.Transactions[date] = &logDay } - transactionLog.Transactions[date] = append(transactionLog.Transactions[date], - Transaction{ - Name: transaction.Name, + transactionID := uuid.NewString() + transactionLog.Transactions[date].Transactions[transactionID] = + &Transaction{ Amount: transaction.Amount, Date: date, Delayable: transaction.Delayable, - OrigDate: date, - HardDate: false, - Late: false, Frequency: "", - }) + HardDate: false, + ID: transactionID, + Late: false, + Name: transaction.Name, + OrigDate: date, + Shift: transaction.Shift, + } } } -func delayTransaction(transactionLog *TransactionLog, transaction *Transaction) { +func calculateShift(transactionLog TransactionLog, transaction Transaction) time.Time { newDate := transaction.Date - if transaction.Date.Weekday() == 0 || transaction.Date.Weekday() == 6 { - if transaction.Date.Weekday() == 0 { + if transaction.Late { + if transaction.Shift == "early" { + newDate = newDate.AddDate(0, 0, -1) + } else { newDate = newDate.AddDate(0, 0, 1) - } else if transaction.Date.Weekday() == 6 { - newDate = newDate.AddDate(0, 0, 2) - } - if _, ok := transactionLog.Transactions[newDate]; !ok { - transactionLog.Transactions[newDate] = make([]Transaction, 0) - } - } else { - transaction.Late = true - newDate = newDate.AddDate(0, 0, 1) - _, ok := transactionLog.Transactions[newDate] - for !ok { - if newDate == transactionLog.EndDate { - break - } - newDate = newDate.AddDate(0, 0, 1) - _, ok = transactionLog.Transactions[newDate] } } + + for newDate.Weekday() == 0 || newDate.Weekday() == 6 { + if transaction.Shift == "early" { + newDate = newDate.AddDate(0, 0, -1) + } else { + newDate = newDate.AddDate(0, 0, 1) + } + } + + return newDate +} + +func delayTransaction(transactionLog *TransactionLog, transaction *Transaction) { + if transaction.Date.Weekday() != 0 && transaction.Date.Weekday() != 6 { + transaction.Late = true + } + + newDate := calculateShift(*transactionLog, *transaction) + if newDate.Before(transactionLog.first()) || newDate.After(transactionLog.last()) || newDate == transaction.Date { + transaction.Delayable = false + } + + if _, ok := transactionLog.Transactions[newDate]; !ok { + logDay := LogDay{ + Date: newDate, + Transactions: map[string]*Transaction{}, + } + + transactionLog.Transactions[newDate] = &logDay + } + + delete(transactionLog.Transactions[transaction.Date].Transactions, transaction.ID) transaction.Date = newDate - transactionLog.Transactions[newDate] = append(transactionLog.Transactions[newDate], *transaction) + transactionLog.Transactions[newDate].Transactions[transaction.ID] = transaction + } func executeTransaction(transactionLog *TransactionLog, accounts []Account, transaction *Transaction) bool { @@ -289,31 +379,88 @@ func calculateFrequency(frequency string, startDate time.Time, endDate time.Time return dateList } -func printAllTransactions(transactionLog *TransactionLog) { - for _, d := range transactionLog.Dates { - for _, t := range transactionLog.Transactions[d] { - fmt.Printf("Date: %s | Transaction: %s\n", t.Date, t.Name) +func processTransactions(transactionLog *TransactionLog, accounts []Account) bool { + shifted := false + for _, date := range transactionLog.dates() { + if _, ok := transactionLog.Transactions[date]; ok { + logDay := transactionLog.Transactions[date] + sortedCharges := make([]*Transaction, 0) + + for _, charge := range logDay.Transactions { + sortedCharges = append(sortedCharges, charge) + } + + sortTransactions(sortedCharges) + + startCount := len(sortedCharges) + endCount := startCount + logDay.Executed = make([]*Transaction, 0) + credits := make([]*Transaction, 0) + late := make([]*Transaction, 0) + charges := make([]*Transaction, 0) + logDay.StartingBalance = make([]Account, len(accounts)) + copy(logDay.StartingBalance, accounts) + for _, charge := range sortedCharges { + if charge.Amount > 0 { + credits = append(credits, charge) + } else if charge.Late { + late = append(late, charge) + } else { + charges = append(charges, charge) + } + } + for _, charge := range credits { + if executeTransaction(transactionLog, accounts, charge) { + logDay.Executed = append(logDay.Executed, charge) + } else { + endCount-- + } + } + for _, charge := range late { + if executeTransaction(transactionLog, accounts, charge) { + logDay.Executed = append(logDay.Executed, charge) + } else { + endCount-- + } + } + for _, charge := range charges { + if executeTransaction(transactionLog, accounts, charge) { + logDay.Executed = append(logDay.Executed, charge) + } else { + endCount-- + } + } + logDay.EndingBalance = make([]Account, len(accounts)) + copy(logDay.EndingBalance, accounts) + if len(logDay.Transactions) == 0 { + delete(transactionLog.Transactions, date) + } + if endCount != startCount { + shifted = true + } } } + + return shifted } -func printOutput(date time.Time, initialBalances []Account, executedCharges []Transaction, accounts []Account) { +func printOutput(logDay LogDay) { delim := strings.Repeat("=", 67) divider := delim delim2 := strings.Repeat("-", 66) divider2 := delim2 + "|" fmt.Println(divider) - outString := fmt.Sprint(date.Format("Jan 02 2006")) + outString := fmt.Sprint(logDay.Date.Format("Jan 02 2006")) rear_spacer := strings.Repeat(" ", 65-len(outString)) fmt.Printf("%s%s |\n", outString, rear_spacer) - for _, v := range initialBalances { + 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)) fmt.Printf("%s%s |\n", outString, rear_spacer) } - for _, charge := range executedCharges { + for _, charge := range logDay.Executed { spacer := strings.Repeat(" ", 18-len(fmt.Sprintf("%.2f", math.Abs(charge.Amount)))) outString := "" if charge.Amount <= 0 { @@ -326,7 +473,7 @@ func printOutput(date time.Time, initialBalances []Account, executedCharges []Tr late_tag := fmt.Sprintf("(%s - %s)", "Late", charge.OrigDate.Format("Jan 02 2006")) rear_spacer := strings.Repeat(" ", 65-len(late_tag)-len(outString)) fmt.Printf("%s%s%s |\n", outString, rear_spacer, late_tag) - } else if charge.Date.After(charge.OrigDate) { + } 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)) fmt.Printf("%s%s%s |\n", outString, rear_spacer, weekend_tag) @@ -336,7 +483,7 @@ func printOutput(date time.Time, initialBalances []Account, executedCharges []Tr } } fmt.Println(divider2) - for _, v := range accounts { + 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)) @@ -356,8 +503,7 @@ func main() { var configYaml ConfigYaml transactionLog := TransactionLog{ - Transactions: map[time.Time][]Transaction{}, - Dates: make([]time.Time, 0), + Transactions: make(map[time.Time]*LogDay), } err = yaml.Unmarshal(yfile, &configYaml) @@ -392,50 +538,19 @@ func main() { current = current.AddDate(0, 0, 1) } - for _, date := range dates { - if _, ok := transactionLog.Transactions[date]; ok { - dateCharges := transactionLog.Transactions[date] - chargeCount := len(dateCharges) - executedCharges := make([]Transaction, 0) - credits := make([]Transaction, 0) - late := make([]Transaction, 0) - charges := make([]Transaction, 0) - initialBalances := make([]Account, len(config.Accounts)) - copy(initialBalances, config.Accounts) - for _, charge := range dateCharges { - if charge.Amount > 0 { - credits = append(credits, charge) - } else if charge.Late { - late = append(late, charge) - } else { - charges = append(charges, charge) - } - } - for _, charge := range credits { - if executeTransaction(&transactionLog, config.Accounts, &charge) { - executedCharges = append(executedCharges, charge) - } else { - chargeCount-- - } - } - for _, charge := range late { - if executeTransaction(&transactionLog, config.Accounts, &charge) { - executedCharges = append(executedCharges, charge) - } else { - chargeCount-- - } - } - for _, charge := range charges { - if executeTransaction(&transactionLog, config.Accounts, &charge) { - executedCharges = append(executedCharges, charge) - } else { - chargeCount-- - } - } - if chargeCount > 0 { - printOutput(date, initialBalances, executedCharges, config.Accounts) - } - } + startingBalance := make([]Account, len(config.Accounts)) + shifted := true + i := 0 + for shifted { + copy(startingBalance, config.Accounts) + shifted = false + shifted = processTransactions(&transactionLog, startingBalance) + i++ + } + + for _, date := range transactionLog.dates() { + logDay := transactionLog.Transactions[date] + printOutput(*logDay) } }