706 lines
17 KiB
Go
706 lines
17 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
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] }
|
|
|
|
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"`
|
|
Minimum float64 `yaml:"minimum"`
|
|
}
|
|
|
|
type Charge struct {
|
|
Name string
|
|
Late bool
|
|
Amount float64
|
|
Date time.Time
|
|
}
|
|
|
|
type Config struct {
|
|
Accounts []Account
|
|
Transactions transactionList
|
|
}
|
|
|
|
type ConfigYaml struct {
|
|
Start string
|
|
End string
|
|
Accounts []Account
|
|
Credit []TransactionYaml
|
|
Transactions []TransactionYaml
|
|
}
|
|
|
|
type Transaction struct {
|
|
Amount float64
|
|
Date time.Time
|
|
Delayable bool
|
|
Frequency string
|
|
ID string
|
|
Delayed bool
|
|
Name string
|
|
OriginDate time.Time
|
|
Shift string
|
|
}
|
|
|
|
type TransactionYaml struct {
|
|
Name string `yaml:"name"`
|
|
Amount float64 `yaml:"amount"`
|
|
Date string `yaml:"date"`
|
|
Delayable string `yaml:"delayable"`
|
|
Frequency string `yaml:"frequency,omitempty"`
|
|
Shift string `yaml:"shift"`
|
|
}
|
|
|
|
type TransactionLog struct {
|
|
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
|
|
Transactions map[string]*Transaction
|
|
}
|
|
|
|
func (tl *TransactionLog) dates() timeSlice {
|
|
dateSlice := make(timeSlice, 0)
|
|
for date := range tl.LogDays {
|
|
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 (logDay LogDay) transactions() transactionList {
|
|
transactions := transactionList{}
|
|
|
|
for _, t := range logDay.Transactions {
|
|
transactions = append(transactions, t)
|
|
}
|
|
|
|
sort.Sort(transactions)
|
|
|
|
return transactions
|
|
}
|
|
|
|
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.LogDays[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.LogDays[previous]
|
|
}
|
|
|
|
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: stripTime(date),
|
|
Delayable: delayable,
|
|
Frequency: transactionYaml.Frequency,
|
|
ID: uuid.NewString(),
|
|
Delayed: false,
|
|
Name: transactionYaml.Name,
|
|
OriginDate: date,
|
|
Shift: transactionYaml.Shift,
|
|
}
|
|
|
|
return transaction, nil
|
|
}
|
|
|
|
func parseConfig(configYaml ConfigYaml) *Config {
|
|
config := &Config{
|
|
Accounts: configYaml.Accounts,
|
|
Transactions: make([]*Transaction, 0),
|
|
}
|
|
|
|
for _, transaction := range configYaml.Credit {
|
|
newTransaction, err := parseTransaction(transaction)
|
|
if err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
config.Transactions = append(config.Transactions, newTransaction)
|
|
}
|
|
|
|
for _, transaction := range configYaml.Transactions {
|
|
newTransaction, err := parseTransaction(transaction)
|
|
if err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
config.Transactions = append(config.Transactions, newTransaction)
|
|
}
|
|
|
|
return config
|
|
}
|
|
|
|
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 {
|
|
return time.Time{}, err
|
|
}
|
|
|
|
if regMatch {
|
|
return time.Parse("2006-1-2", dateString)
|
|
}
|
|
|
|
if dateString == "first" {
|
|
if currentDay.Day() == 1 {
|
|
return currentDay, nil
|
|
} else {
|
|
return firstOfMonth(currentDay).AddDate(0, 1, 0), nil
|
|
}
|
|
}
|
|
|
|
if dateString == "last" {
|
|
return lastOfMonth(currentDay), nil
|
|
}
|
|
|
|
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 ingestTransactions(transactions []*Transaction, transactionLog *TransactionLog) {
|
|
for _, transaction := range transactions {
|
|
if math.Abs(transaction.Amount) != 0 {
|
|
transactionLog.addTransaction(transaction)
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
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 {
|
|
tl.addDay(date)
|
|
t := Transaction{
|
|
Amount: transaction.Amount,
|
|
Date: date,
|
|
Delayable: transaction.Delayable,
|
|
Frequency: "",
|
|
ID: uuid.NewString(),
|
|
Delayed: false,
|
|
Name: transaction.Name,
|
|
OriginDate: date,
|
|
Shift: transaction.Shift,
|
|
}
|
|
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 {
|
|
if transaction.Amount > 0 {
|
|
add := 1
|
|
newDate := transaction.Date
|
|
|
|
if transaction.Shift == "early" {
|
|
add = -1
|
|
}
|
|
|
|
for isWeekend(newDate) {
|
|
newDate = newDate.AddDate(0, 0, add)
|
|
}
|
|
|
|
return newDate
|
|
|
|
}
|
|
|
|
if transaction.Shift == "early" {
|
|
return previousLogDay(&transactionLog, transaction.Date).Date
|
|
}
|
|
|
|
return nextLogDay(&transactionLog, transaction.Date).Date
|
|
}
|
|
|
|
func (tl *TransactionLog) delayTransaction(transaction *Transaction) {
|
|
newDate := calculateShift(*tl, *transaction)
|
|
|
|
if newDate.Before(tl.first()) || newDate == transaction.Date {
|
|
logrus.Debugf("Transaction not delayable %s (%s | %f)", transaction.ID, transaction.Name, transaction.Amount)
|
|
transaction.Delayable = false
|
|
return
|
|
}
|
|
|
|
if newDate.After(tl.last()) {
|
|
newDate = tl.last()
|
|
}
|
|
|
|
tl.addDay(newDate)
|
|
|
|
delete(tl.LogDays[transaction.Date].Transactions, transaction.ID)
|
|
|
|
transaction.Date = newDate
|
|
tl.LogDays[newDate].Transactions[transaction.ID] = transaction
|
|
|
|
}
|
|
|
|
func (tl *TransactionLog) executeTransaction(transaction *Transaction) bool {
|
|
logDay := tl.LogDays[transaction.Date]
|
|
accounts := make([]Account, len(logDay.EndingBalance))
|
|
copy(accounts, logDay.EndingBalance)
|
|
|
|
if isWeekend(transaction.Date) && transaction.Delayable {
|
|
logrus.Debugf("Delaying %s due to weekend scheduling\n", transaction.Name)
|
|
tl.delayTransaction(transaction)
|
|
|
|
return false
|
|
}
|
|
|
|
logrus.Debugf("Balance: %f | Minimum: %f\n", accounts[0].Balance, accounts[0].Minimum)
|
|
logrus.Debugf("Delayable: %t", transaction.Delayable)
|
|
if !isAffordable(transaction.Amount, accounts[0].Balance, accounts[0].Minimum) && transaction.Delayable {
|
|
logrus.Debugf("Delaying %s: %f (%s) due to lack of funds\n",
|
|
transaction.Name, transaction.Amount, transaction.Date)
|
|
tl.delayTransaction(transaction)
|
|
|
|
transaction.Delayed = true
|
|
|
|
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 true
|
|
}
|
|
|
|
func isAffordable(charge, balance, minimum float64) bool {
|
|
if charge > 0 {
|
|
return true
|
|
}
|
|
|
|
logrus.Debugf("Charge: %f | Balance - Miniumum: %f", charge, balance-minimum)
|
|
logrus.Debugf("abs(charge) < (balance - minimum): %t", math.Abs(charge) < (balance-minimum))
|
|
|
|
return math.Abs(charge) < (balance - minimum)
|
|
}
|
|
|
|
func currentDay() time.Time {
|
|
return stripTime(time.Now().Local())
|
|
}
|
|
|
|
func firstOfMonth(t time.Time) time.Time {
|
|
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.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 {
|
|
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)
|
|
}
|
|
if frequency == "daily" {
|
|
initDate = initDate.AddDate(0, 0, 1)
|
|
} else if frequency == "weekly" {
|
|
initDate = initDate.AddDate(0, 0, 7)
|
|
} else if frequency == "biweekly" {
|
|
initDate = initDate.AddDate(0, 0, 14)
|
|
} else if frequency == "monthly" {
|
|
if initDate == lastOfMonth(initDate) {
|
|
initDate = lastOfMonth(firstOfMonth(initDate).AddDate(0, 1, 0))
|
|
} else {
|
|
initDate = initDate.AddDate(0, 1, 0)
|
|
}
|
|
} else {
|
|
logrus.Error("Invalid Frequency")
|
|
}
|
|
}
|
|
|
|
return dateList
|
|
}
|
|
|
|
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.Delayed {
|
|
categorized["late"] = append(categorized["late"], t.ID)
|
|
} else {
|
|
categorized["standard"] = append(categorized["standard"], t.ID)
|
|
}
|
|
}
|
|
|
|
return categorized
|
|
}
|
|
|
|
func (tl *TransactionLog) processTransactions(accounts []Account) bool {
|
|
shifted := false
|
|
endBalance := accounts
|
|
|
|
logrus.Debugf("\n\nITERATION %d\n\n", tl.Iteration)
|
|
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)
|
|
if processTransactionsByID(tl, categorized["credits"]) {
|
|
shifted = true
|
|
}
|
|
if processTransactionsByID(tl, categorized["late"]) {
|
|
shifted = true
|
|
}
|
|
if processTransactionsByID(tl, categorized["standard"]) {
|
|
shifted = true
|
|
}
|
|
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 {
|
|
tl.deleteDay(date)
|
|
}
|
|
}
|
|
|
|
tl.Iteration++
|
|
return shifted
|
|
}
|
|
|
|
func processTransactionsByID(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)
|
|
}
|
|
logrus.Debugf("Processing %s (%s | %f)", id, transaction.Name, transaction.Amount)
|
|
if !transactionLog.executeTransaction(transaction) {
|
|
shifted = true
|
|
}
|
|
}
|
|
|
|
return shifted
|
|
}
|
|
|
|
func getTerminalWidth() int {
|
|
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, " ")
|
|
width, err := strconv.Atoi(sttyOut[1])
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
return width
|
|
}
|
|
|
|
func printWithMaxWidth(maxWidth int, suffix string, strs ...string) {
|
|
total := 0
|
|
whiteSpace := regexp.MustCompile(`^\s+$`)
|
|
for _, str := range strs {
|
|
line := ""
|
|
if whiteSpace.MatchString(str) {
|
|
fmt.Print(str)
|
|
total += len(str)
|
|
continue
|
|
}
|
|
words := strings.Fields(str)
|
|
for _, word := range words {
|
|
total += len(word)
|
|
if len(line)+len(word)+1 >= maxWidth {
|
|
fmt.Println(line)
|
|
line = ""
|
|
}
|
|
if line == "" {
|
|
line = word
|
|
} else {
|
|
line = line + " " + word
|
|
total += 1
|
|
}
|
|
}
|
|
fmt.Print(line)
|
|
}
|
|
|
|
repeatCount := maxWidth - total - len(suffix)
|
|
if repeatCount < 0 {
|
|
repeatCount = 0
|
|
}
|
|
|
|
terminate := strings.Repeat(" ", repeatCount) + suffix
|
|
|
|
fmt.Println(terminate)
|
|
|
|
}
|
|
|
|
func printTransactionLine(amount float64, name, prefix, suffix string, maxWidth int) {
|
|
segment := fmt.Sprintf("%s$%.2f", prefix, math.Abs(amount))
|
|
spacer := strings.Repeat(" ", (maxWidth/4)-len(segment))
|
|
printWithMaxWidth(maxWidth, suffix, segment, spacer, fmt.Sprintf("| %s", name))
|
|
}
|
|
|
|
func printOutput(logDay LogDay) {
|
|
|
|
fullWidth := getTerminalWidth()
|
|
|
|
if fullWidth > 120 {
|
|
fullWidth = 120
|
|
}
|
|
|
|
border := strings.Repeat("=", fullWidth)
|
|
divider := strings.Repeat("-", fullWidth-1) + "|"
|
|
|
|
fmt.Println(border)
|
|
printWithMaxWidth(fullWidth, "|", logDay.Date.Format("Jan 02 2006"))
|
|
|
|
for _, v := range logDay.StartingBalance {
|
|
printTransactionLine(v.Balance, v.Name, "", "|", fullWidth)
|
|
}
|
|
for _, charge := range logDay.transactions() {
|
|
prefix := "-"
|
|
if charge.Amount > 0 {
|
|
prefix = "+"
|
|
}
|
|
|
|
if charge.Delayed {
|
|
suffix := fmt.Sprintf("(%s - %s) |", "Late", charge.OriginDate.Format("Jan 02 2006"))
|
|
printTransactionLine(charge.Amount, charge.Name, prefix, suffix, fullWidth)
|
|
} else if charge.Date != charge.OriginDate {
|
|
suffix := fmt.Sprintf("(%s - %s) |", "Weekend", charge.OriginDate.Format("Jan 02 2006"))
|
|
printTransactionLine(charge.Amount, charge.Name, prefix, suffix, fullWidth)
|
|
} else {
|
|
printTransactionLine(charge.Amount, charge.Name, prefix, "|", fullWidth)
|
|
}
|
|
}
|
|
|
|
fmt.Println(divider)
|
|
for _, v := range logDay.EndingBalance {
|
|
printTransactionLine(v.Balance, v.Name, "", "|", fullWidth)
|
|
}
|
|
fmt.Println(border + "\n")
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Getenv("DEBUG")) > 0 {
|
|
logrus.SetLevel(logrus.DebugLevel)
|
|
}
|
|
|
|
yfile, err := os.ReadFile(os.Args[1])
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
var configYaml ConfigYaml
|
|
|
|
err = yaml.Unmarshal(yfile, &configYaml)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
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)
|
|
if err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}
|
|
|
|
if configYaml.End != "" {
|
|
transactionLog.EndDate, err = time.Parse("2006-1-2", configYaml.End)
|
|
if err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}
|
|
|
|
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 = transactionLog.processTransactions(config.Accounts)
|
|
}
|
|
|
|
for _, date := range transactionLog.dates() {
|
|
logDay := transactionLog.LogDays[date]
|
|
printOutput(*logDay)
|
|
}
|
|
|
|
}
|