Refactored processing to permit multiple permutations prior to final print

This commit is contained in:
2022-10-02 16:46:20 -04:00
parent ee8816687b
commit b8baff3d5f

317
main.go
View File

@@ -9,6 +9,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -16,9 +17,9 @@ import (
type timeSlice []time.Time 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) 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] }
func (s timeSlice) Len() int { return len(s) }
type Account struct { type Account struct {
Name string `yaml:"name"` Name string `yaml:"name"`
@@ -53,9 +54,11 @@ type Transaction struct {
Delayable bool Delayable bool
Frequency string Frequency string
HardDate bool HardDate bool
ID string
Late bool Late bool
Name string Name string
OrigDate time.Time OrigDate time.Time
Shift string
} }
type TransactionYaml struct { type TransactionYaml struct {
@@ -64,15 +67,67 @@ type TransactionYaml struct {
Date string `yaml:"date"` Date string `yaml:"date"`
Delayable *bool `yaml:"delayable"` Delayable *bool `yaml:"delayable"`
Frequency string `yaml:"frequency,omitempty"` Frequency string `yaml:"frequency,omitempty"`
Shift string `yaml:"shift`
} }
type TransactionLog struct { type TransactionLog struct {
Transactions map[time.Time][]Transaction Transactions map[time.Time]*LogDay
Dates []time.Time
StartDate time.Time StartDate time.Time
EndDate 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 { func parseConfig(configYaml *ConfigYaml) Config {
config := Config{ config := Config{
Accounts: configYaml.Accounts, Accounts: configYaml.Accounts,
@@ -85,17 +140,22 @@ func parseConfig(configYaml *ConfigYaml) Config {
if transaction.Delayable == nil { if transaction.Delayable == nil {
transaction.Delayable = &[]bool{true}[0] transaction.Delayable = &[]bool{true}[0]
} }
if transaction.Shift == "" {
transaction.Shift = "late"
}
newTransaction := Transaction{ newTransaction := Transaction{
Amount: transaction.Amount, Amount: transaction.Amount,
Date: formalDate, Date: formalDate,
Delayable: *transaction.Delayable, Delayable: *transaction.Delayable,
OrigDate: formalDate,
HardDate: hardDate,
Frequency: transaction.Frequency, Frequency: transaction.Frequency,
HardDate: hardDate,
ID: uuid.NewString(),
Late: false, Late: false,
Name: transaction.Name, 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 { for _, transaction := range configYaml.Transactions {
@@ -103,17 +163,22 @@ func parseConfig(configYaml *ConfigYaml) Config {
if transaction.Delayable == nil { if transaction.Delayable == nil {
transaction.Delayable = &[]bool{true}[0] transaction.Delayable = &[]bool{true}[0]
} }
if transaction.Shift == "" {
transaction.Shift = "late"
}
newTransaction := Transaction{ newTransaction := Transaction{
Amount: transaction.Amount, Amount: transaction.Amount,
Date: formalDate, Date: formalDate,
Delayable: *transaction.Delayable, Delayable: *transaction.Delayable,
OrigDate: formalDate,
HardDate: hardDate,
Frequency: transaction.Frequency, Frequency: transaction.Frequency,
HardDate: hardDate,
ID: uuid.NewString(),
Late: false, Late: false,
Name: transaction.Name, Name: transaction.Name,
OrigDate: formalDate,
Shift: transaction.Shift,
} }
config.Credit = append(config.Credit, newTransaction) config.Transactions = append(config.Transactions, newTransaction)
} }
return config return config
@@ -149,14 +214,17 @@ func parseDate(date string) (time.Time, bool) {
func preFetchDates(transactions []Transaction, transactionLog *TransactionLog) { func preFetchDates(transactions []Transaction, transactionLog *TransactionLog) {
for _, transaction := range transactions { for _, transaction := range transactions {
if _, ok := transactionLog.Transactions[transaction.Date]; !ok { if _, ok := transactionLog.Transactions[transaction.Date]; !ok && transaction.Amount != 0 {
transactionLog.Transactions[transaction.Date] = make([]Transaction, 0) logDay := LogDay{
transactionLog.Dates = append(transactionLog.Dates, transaction.Date) Date: transaction.Date,
Transactions: make(map[string]*Transaction),
}
transactionLog.Transactions[transaction.Date] = &logDay
} }
} }
sort.Sort(timeSlice(transactionLog.Dates)) logDates := transactionLog.dates()
transactionLog.StartDate = transactionLog.Dates[0] transactionLog.StartDate = logDates[0]
transactionLog.EndDate = transactionLog.Dates[len(transactionLog.Dates)-1] transactionLog.EndDate = logDates[len(logDates)-1]
} }
func collectTransactions(transactions []Transaction, transactionLog *TransactionLog) { func collectTransactions(transactions []Transaction, transactionLog *TransactionLog) {
@@ -165,13 +233,9 @@ func collectTransactions(transactions []Transaction, transactionLog *Transaction
addTransaction(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 { sort.Slice(transactions, func(i, j int) bool {
if transactions[i].Amount < 0 && transactions[j].Amount < 0 { if transactions[i].Amount < 0 && transactions[j].Amount < 0 {
return math.Abs(transactions[i].Amount) > math.Abs(transactions[j].Amount) 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 { for _, date := range dateList {
if _, ok := transactionLog.Transactions[date]; !ok { if _, ok := transactionLog.Transactions[date]; !ok {
transactionLog.Dates = append(transactionLog.Dates, date) logDay := LogDay{
Date: date,
Transactions: make(map[string]*Transaction),
} }
transactionLog.Transactions[date] = append(transactionLog.Transactions[date], transactionLog.Transactions[date] = &logDay
Transaction{ }
Name: transaction.Name, transactionID := uuid.NewString()
transactionLog.Transactions[date].Transactions[transactionID] =
&Transaction{
Amount: transaction.Amount, Amount: transaction.Amount,
Date: date, Date: date,
Delayable: transaction.Delayable, Delayable: transaction.Delayable,
OrigDate: date,
HardDate: false,
Late: false,
Frequency: "", 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 newDate := transaction.Date
if transaction.Date.Weekday() == 0 || transaction.Date.Weekday() == 6 { if transaction.Late {
if transaction.Date.Weekday() == 0 { if transaction.Shift == "early" {
newDate = newDate.AddDate(0, 0, 1) 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 { } else {
newDate = newDate.AddDate(0, 0, 1)
}
}
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 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] 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 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 { 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 return dateList
} }
func printAllTransactions(transactionLog *TransactionLog) { func processTransactions(transactionLog *TransactionLog, accounts []Account) bool {
for _, d := range transactionLog.Dates { shifted := false
for _, t := range transactionLog.Transactions[d] { for _, date := range transactionLog.dates() {
fmt.Printf("Date: %s | Transaction: %s\n", t.Date, t.Name) 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
} }
} }
} }
func printOutput(date time.Time, initialBalances []Account, executedCharges []Transaction, accounts []Account) { return shifted
}
func printOutput(logDay LogDay) {
delim := strings.Repeat("=", 67) delim := strings.Repeat("=", 67)
divider := delim divider := delim
delim2 := strings.Repeat("-", 66) delim2 := strings.Repeat("-", 66)
divider2 := delim2 + "|" divider2 := delim2 + "|"
fmt.Println(divider) 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)) rear_spacer := strings.Repeat(" ", 65-len(outString))
fmt.Printf("%s%s |\n", outString, rear_spacer) 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))) 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(" ", 65-len(outString))
fmt.Printf("%s%s |\n", outString, rear_spacer) 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)))) spacer := strings.Repeat(" ", 18-len(fmt.Sprintf("%.2f", math.Abs(charge.Amount))))
outString := "" outString := ""
if charge.Amount <= 0 { 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")) late_tag := fmt.Sprintf("(%s - %s)", "Late", charge.OrigDate.Format("Jan 02 2006"))
rear_spacer := strings.Repeat(" ", 65-len(late_tag)-len(outString)) rear_spacer := strings.Repeat(" ", 65-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.After(charge.OrigDate) { } else if charge.Date != charge.OrigDate {
weekend_tag := fmt.Sprintf("(%s - %s)", "Weekend", charge.OrigDate.Format("Jan 02 2006")) weekend_tag := fmt.Sprintf("(%s - %s)", "Weekend", charge.OrigDate.Format("Jan 02 2006"))
rear_spacer := strings.Repeat(" ", 65-len(weekend_tag)-len(outString)) rear_spacer := strings.Repeat(" ", 65-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)
@@ -336,7 +483,7 @@ func printOutput(date time.Time, initialBalances []Account, executedCharges []Tr
} }
} }
fmt.Println(divider2) fmt.Println(divider2)
for _, v := range accounts { 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(" ", 65-len(outString))
@@ -356,8 +503,7 @@ func main() {
var configYaml ConfigYaml var configYaml ConfigYaml
transactionLog := TransactionLog{ transactionLog := TransactionLog{
Transactions: map[time.Time][]Transaction{}, Transactions: make(map[time.Time]*LogDay),
Dates: make([]time.Time, 0),
} }
err = yaml.Unmarshal(yfile, &configYaml) err = yaml.Unmarshal(yfile, &configYaml)
@@ -392,50 +538,19 @@ func main() {
current = current.AddDate(0, 0, 1) current = current.AddDate(0, 0, 1)
} }
for _, date := range dates { startingBalance := make([]Account, len(config.Accounts))
if _, ok := transactionLog.Transactions[date]; ok { shifted := true
dateCharges := transactionLog.Transactions[date] i := 0
chargeCount := len(dateCharges) for shifted {
executedCharges := make([]Transaction, 0) copy(startingBalance, config.Accounts)
credits := make([]Transaction, 0) shifted = false
late := make([]Transaction, 0) shifted = processTransactions(&transactionLog, startingBalance)
charges := make([]Transaction, 0) i++
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)
}
} }
for _, date := range transactionLog.dates() {
logDay := transactionLog.Transactions[date]
printOutput(*logDay)
} }
} }