Significant fixes and improvements to scheduling logic and backtracking
This commit is contained in:
5
go.mod
5
go.mod
@@ -7,4 +7,7 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
require (
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
)
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1,6 +1,8 @@
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
|
||||
246
main.go
246
main.go
@@ -67,26 +67,27 @@ type TransactionYaml struct {
|
||||
Date string `yaml:"date"`
|
||||
Delayable *bool `yaml:"delayable"`
|
||||
Frequency string `yaml:"frequency,omitempty"`
|
||||
Shift string `yaml:"shift`
|
||||
Shift string `yaml:"shift"`
|
||||
}
|
||||
|
||||
type TransactionLog struct {
|
||||
Transactions map[time.Time]*LogDay
|
||||
StartDate time.Time
|
||||
EndDate time.Time
|
||||
Iteration int
|
||||
LogDays map[time.Time]*LogDay
|
||||
Transactions map[string]*Transaction
|
||||
}
|
||||
|
||||
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 {
|
||||
for date, _ := range t.LogDays {
|
||||
dateSlice = append(dateSlice, date)
|
||||
}
|
||||
|
||||
@@ -104,7 +105,19 @@ func (t TransactionLog) last() time.Time {
|
||||
|
||||
}
|
||||
|
||||
func nextLogDay(transactionLog *TransactionLog, currentDate time.Time) LogDay {
|
||||
func (logDay LogDay) transactions() []*Transaction {
|
||||
transactions := make([]*Transaction, 0)
|
||||
|
||||
for _, t := range logDay.Transactions {
|
||||
transactions = append(transactions, t)
|
||||
}
|
||||
|
||||
sortTransactions(transactions)
|
||||
|
||||
return transactions
|
||||
}
|
||||
|
||||
func nextLogDay(transactionLog *TransactionLog, currentDate time.Time) *LogDay {
|
||||
var next time.Time
|
||||
for i, date := range transactionLog.dates() {
|
||||
if date == currentDate {
|
||||
@@ -113,10 +126,10 @@ func nextLogDay(transactionLog *TransactionLog, currentDate time.Time) LogDay {
|
||||
}
|
||||
}
|
||||
|
||||
return *transactionLog.Transactions[next]
|
||||
return transactionLog.LogDays[next]
|
||||
}
|
||||
|
||||
func previousLogDay(transactionLog *TransactionLog, currentDate time.Time) LogDay {
|
||||
func previousLogDay(transactionLog *TransactionLog, currentDate time.Time) *LogDay {
|
||||
var previous time.Time
|
||||
for _, date := range transactionLog.dates() {
|
||||
if date == currentDate {
|
||||
@@ -125,7 +138,7 @@ func previousLogDay(transactionLog *TransactionLog, currentDate time.Time) LogDa
|
||||
previous = date
|
||||
}
|
||||
|
||||
return *transactionLog.Transactions[previous]
|
||||
return transactionLog.LogDays[previous]
|
||||
}
|
||||
|
||||
func parseConfig(configYaml *ConfigYaml) Config {
|
||||
@@ -214,12 +227,12 @@ func parseDate(date string) (time.Time, bool) {
|
||||
|
||||
func preFetchDates(transactions []Transaction, transactionLog *TransactionLog) {
|
||||
for _, transaction := range transactions {
|
||||
if _, ok := transactionLog.Transactions[transaction.Date]; !ok && transaction.Amount != 0 {
|
||||
if _, ok := transactionLog.LogDays[transaction.Date]; !ok && transaction.Amount != 0 {
|
||||
logDay := LogDay{
|
||||
Date: transaction.Date,
|
||||
Transactions: make(map[string]*Transaction),
|
||||
}
|
||||
transactionLog.Transactions[transaction.Date] = &logDay
|
||||
transactionLog.LogDays[transaction.Date] = &logDay
|
||||
}
|
||||
}
|
||||
logDates := transactionLog.dates()
|
||||
@@ -254,27 +267,27 @@ func addTransaction(transactionLog *TransactionLog, transaction *Transaction) {
|
||||
dateList = []time.Time{transaction.Date}
|
||||
}
|
||||
for _, date := range dateList {
|
||||
if _, ok := transactionLog.Transactions[date]; !ok {
|
||||
if _, ok := transactionLog.LogDays[date]; !ok {
|
||||
logDay := LogDay{
|
||||
Date: date,
|
||||
Transactions: make(map[string]*Transaction),
|
||||
}
|
||||
transactionLog.Transactions[date] = &logDay
|
||||
transactionLog.LogDays[date] = &logDay
|
||||
}
|
||||
transactionID := uuid.NewString()
|
||||
transactionLog.Transactions[date].Transactions[transactionID] =
|
||||
&Transaction{
|
||||
Amount: transaction.Amount,
|
||||
Date: date,
|
||||
Delayable: transaction.Delayable,
|
||||
Frequency: "",
|
||||
HardDate: false,
|
||||
ID: transactionID,
|
||||
Late: false,
|
||||
Name: transaction.Name,
|
||||
OrigDate: date,
|
||||
Shift: transaction.Shift,
|
||||
}
|
||||
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,
|
||||
}
|
||||
transactionLog.LogDays[date].Transactions[t.ID] = &t
|
||||
transactionLog.Transactions[t.ID] = &t
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,36 +322,47 @@ func delayTransaction(transactionLog *TransactionLog, transaction *Transaction)
|
||||
transaction.Delayable = false
|
||||
}
|
||||
|
||||
if _, ok := transactionLog.Transactions[newDate]; !ok {
|
||||
if _, ok := transactionLog.LogDays[newDate]; !ok {
|
||||
logDay := LogDay{
|
||||
Date: newDate,
|
||||
Transactions: map[string]*Transaction{},
|
||||
}
|
||||
|
||||
transactionLog.Transactions[newDate] = &logDay
|
||||
transactionLog.LogDays[newDate] = &logDay
|
||||
}
|
||||
|
||||
delete(transactionLog.Transactions[transaction.Date].Transactions, transaction.ID)
|
||||
delete(transactionLog.LogDays[transaction.Date].Transactions, transaction.ID)
|
||||
transaction.Date = newDate
|
||||
transactionLog.Transactions[newDate].Transactions[transaction.ID] = transaction
|
||||
transactionLog.LogDays[newDate].Transactions[transaction.ID] = transaction
|
||||
|
||||
}
|
||||
|
||||
func executeTransaction(transactionLog *TransactionLog, accounts []Account, transaction *Transaction) bool {
|
||||
func executeTransaction(transactionLog *TransactionLog, transaction *Transaction) bool {
|
||||
logDay := transactionLog.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 {
|
||||
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
|
||||
}
|
||||
|
||||
logDay.EndingBalance = make([]Account, len(accounts))
|
||||
copy(logDay.EndingBalance, accounts)
|
||||
|
||||
return executed
|
||||
}
|
||||
|
||||
@@ -379,65 +403,91 @@ func calculateFrequency(frequency string, startDate time.Time, endDate time.Time
|
||||
return dateList
|
||||
}
|
||||
|
||||
func processTransactions(transactionLog *TransactionLog, accounts []Account) bool {
|
||||
func categorizeTransactions(transactions map[string]*Transaction) map[string][]string {
|
||||
var categorized = map[string][]string{
|
||||
"credits": make([]string, 0),
|
||||
"late": make([]string, 0),
|
||||
"standard": make([]string, 0),
|
||||
}
|
||||
|
||||
for _, t := range transactions {
|
||||
if t.Amount > 0 {
|
||||
categorized["credits"] = append(categorized["credits"], t.ID)
|
||||
} else if t.Late {
|
||||
categorized["late"] = append(categorized["late"], t.ID)
|
||||
} else {
|
||||
categorized["standard"] = append(categorized["standard"], t.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return categorized
|
||||
}
|
||||
|
||||
func processTransactionLog(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 {
|
||||
endBalance := accounts
|
||||
logrus.Debugf("\n\nITERATION %d\n\n", transactionLog.Iteration)
|
||||
if transactionLog.Iteration == 0 {
|
||||
for _, date := range transactionLog.dates() {
|
||||
logDay := transactionLog.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.Debugf("---\nStarting Balance %s: %f\n", date, logDay.StartingBalance[0].Balance)
|
||||
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)
|
||||
endBalance = logDay.EndingBalance
|
||||
}
|
||||
} else {
|
||||
for _, date := range transactionLog.dates() {
|
||||
logDay := transactionLog.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.Debugf("---\nStarting Balance %s: %f\n", date, logDay.StartingBalance[0].Balance)
|
||||
logrus.Debugln(categorized)
|
||||
if processTransactions(transactionLog, categorized["credits"]) {
|
||||
shifted = true
|
||||
}
|
||||
if processTransactions(transactionLog, categorized["late"]) {
|
||||
shifted = true
|
||||
}
|
||||
if processTransactions(transactionLog, 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)
|
||||
endBalance = logDay.EndingBalance
|
||||
if shifted {
|
||||
break
|
||||
}
|
||||
if len(logDay.Transactions) == 0 {
|
||||
delete(transactionLog.LogDays, logDay.Date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transactionLog.Iteration++
|
||||
return shifted
|
||||
}
|
||||
|
||||
func processTransactions(transactionLog *TransactionLog, transactionIDs []string) bool {
|
||||
shifted := false
|
||||
for _, id := range transactionIDs {
|
||||
transaction, ok := transactionLog.Transactions[id]
|
||||
if !ok {
|
||||
logrus.Fatalf("Transaction ID: %s not found in log\n", id)
|
||||
}
|
||||
if !executeTransaction(transactionLog, transaction) {
|
||||
shifted = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,7 +510,7 @@ func printOutput(logDay LogDay) {
|
||||
rear_spacer := strings.Repeat(" ", 65-len(outString))
|
||||
fmt.Printf("%s%s |\n", outString, rear_spacer)
|
||||
}
|
||||
for _, charge := range logDay.Executed {
|
||||
for _, charge := range logDay.transactions() {
|
||||
spacer := strings.Repeat(" ", 18-len(fmt.Sprintf("%.2f", math.Abs(charge.Amount))))
|
||||
outString := ""
|
||||
if charge.Amount <= 0 {
|
||||
@@ -494,7 +544,6 @@ func printOutput(logDay LogDay) {
|
||||
|
||||
func main() {
|
||||
yfile, err := os.ReadFile(os.Args[1])
|
||||
dates := make([]time.Time, 0)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -503,7 +552,8 @@ func main() {
|
||||
var configYaml ConfigYaml
|
||||
|
||||
transactionLog := TransactionLog{
|
||||
Transactions: make(map[time.Time]*LogDay),
|
||||
LogDays: make(map[time.Time]*LogDay),
|
||||
Transactions: make(map[string]*Transaction),
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(yfile, &configYaml)
|
||||
@@ -532,24 +582,16 @@ func main() {
|
||||
collectTransactions(config.Credit, &transactionLog)
|
||||
collectTransactions(config.Transactions, &transactionLog)
|
||||
|
||||
current := transactionLog.StartDate
|
||||
for current.Before(transactionLog.EndDate.AddDate(0, 0, 7)) {
|
||||
dates = append(dates, current)
|
||||
current = current.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
startingBalance := make([]Account, len(config.Accounts))
|
||||
shifted := true
|
||||
i := 0
|
||||
for shifted {
|
||||
copy(startingBalance, config.Accounts)
|
||||
//startingBalance := make([]Account, len(config.Accounts))
|
||||
//copy(startingBalance, config.Accounts)
|
||||
shifted = false
|
||||
shifted = processTransactions(&transactionLog, startingBalance)
|
||||
i++
|
||||
shifted = processTransactionLog(&transactionLog, config.Accounts)
|
||||
}
|
||||
|
||||
for _, date := range transactionLog.dates() {
|
||||
logDay := transactionLog.Transactions[date]
|
||||
logDay := transactionLog.LogDays[date]
|
||||
printOutput(*logDay)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user