diff --git a/build.sh b/build.sh index 3de9ff0..5386151 100644 --- a/build.sh +++ b/build.sh @@ -6,6 +6,6 @@ rm build/* go build -o ./build/mercury ./src -mercury_path="/home/dtookey/work/clarity-reporting/qb/" ./build/mercury +mercury_qb_path="/home/dtookey/work/clarity-reporting/qb/" ./build/mercury diff --git a/src/mercury.go b/src/mercury.go index fec9698..3bc0692 100644 --- a/src/mercury.go +++ b/src/mercury.go @@ -6,11 +6,17 @@ import ( "mercury/src/insight" "mercury/src/mercury" "os" + "time" ) func main() { + s := time.Now() + + processQbBilling() + f := time.Now() + log.Println(f.Sub(s).Microseconds()) //fetchInsightData() - test() + //test() } func test() { @@ -29,12 +35,13 @@ func test() { } func processQbBilling() { - reportBase := os.Getenv("mercury_path") + reportBase := os.Getenv("mercury_qb_path") + log.Printf("Searching for documents in %s\n", reportBase) if len(reportBase) == 0 { log.Fatalln("please set the mercury_path env var. we don't know where to look otherwise") } - mercury.ProcessCSVsFromPath(reportBase, "/home/dtookey/work/clarity-reporting/qb_all_bc.csv") + mercury.ProcessTrialBalances(reportBase, "./test.csv") } func fetchInsightData() { diff --git a/src/mercury/csv.go b/src/mercury/csv.go index 8426854..56c3f56 100644 --- a/src/mercury/csv.go +++ b/src/mercury/csv.go @@ -5,6 +5,8 @@ import ( "fmt" "log" "os" + "path" + "strconv" "strings" ) @@ -17,13 +19,166 @@ const ( Amount ) -type BillingLine struct { - BillingCode string - Date string - InvoiceNumber string - AccountName string - Class string - Amount string +const ( + DotRune = 65533 + expenseAccountCutoff = 50000 +) + +var ( + TrialBalanceHeaders = []string{"Account Name", "Amount", "Period", "Account Type"} +) + +type ( + BillingLine struct { + BillingCode string + Date string + InvoiceNumber string + AccountName string + Class string + Amount string + } + + TrialBalanceLine struct { + Amount float64 + AccountName string + Period string + AccountType string + } +) + +func ProcessTrialBalances(pathlikeIn string, pathlikeOut string) { + paths := enumFiles(pathlikeIn) + table := make([]*[]*TrialBalanceLine, 0, 100) + for _, p := range *paths { + table = append(table, processTrialBalance(p)) + } + writeTable(&table, pathlikeOut) +} + +func writeTable(table *[]*[]*TrialBalanceLine, pathlikeOut string) { + writer := createCsvWriter(pathlikeOut) + writer.Write(TrialBalanceHeaders) + for _, sheet := range *table { + for _, line := range *sheet { + writer.Write(line.toRow()) + } + } + writer.Flush() +} + +func formatDate(period string) string { + month := period[0:2] + date := period[2:4] + year := period[4:6] + return fmt.Sprintf("20%s-%s-%s", year, month, date) +} + +func processTrialBalance(pathlike *string) *[]*TrialBalanceLine { + ret := make([]*TrialBalanceLine, 0, 50) + file, err := os.OpenFile(*pathlike, os.O_RDONLY, 0755) + if err != nil { + log.Fatalln(err) + } + defer file.Close() + reader := csv.NewReader(file) + table, err := reader.ReadAll() + if err != nil { + log.Fatalln(err) + } + + period := getDateFromFileName(pathlike) + + buff := make([]rune, 500) + for _, row := range table { + ret = append(ret, rowBufferToBalanceLine(&row, &buff, period)) + } + return &ret +} + +func getDateFromFileName(pathlike *string) string { + fileName := path.Base(*pathlike) + date := strings.Split(fileName, " ")[0] + return date +} + +func getAccountTypeFromName(accountName string) string { + parts := strings.Split(accountName, " ") + number, err := strconv.Atoi(parts[0]) + if err != nil { + log.Fatalln(err) + } + if number < expenseAccountCutoff { + return "income" + } else { + return "expense" + } + +} + +func rowBufferToBalanceLine(row *[]string, buffer *[]rune, date string) *TrialBalanceLine { + if strings.Index((*row)[0], ":") > -1 { + return revenueRowBufferToBalanceLine(row, buffer, date) + } else { + return expenseRowBufferToBalanceLine(row, buffer, date) + } +} + +func revenueRowBufferToBalanceLine(row *[]string, buffer *[]rune, date string) *TrialBalanceLine { + balance := TrialBalanceLine{} + target := strings.Split((*row)[0], ":")[1] + dotIdx := strings.IndexRune(target, DotRune) + buff := *buffer + if dotIdx > -1 { + src := []rune(target) + copy(buff[:dotIdx-1], src[:dotIdx-1]) + copy(buff[dotIdx-1:len(src)-2], src[dotIdx+1:]) + target = string(buff[:len(src)-2]) + } + + amount, err := strconv.ParseFloat((*row)[2], 64) + if err != nil { + log.Println("Error in revenueRowBufferToBalanceLine") + log.Println(err) + amount = 0.0 + } + + balance.AccountName = target + balance.Amount = amount + balance.Period = formatDate(date) + balance.AccountType = getAccountTypeFromName(target) + return &balance +} + +func expenseRowBufferToBalanceLine(row *[]string, buffer *[]rune, date string) *TrialBalanceLine { + balance := TrialBalanceLine{} + target := (*row)[0] + dotIdx := strings.IndexRune(target, DotRune) + buff := *buffer + if dotIdx > -1 { + src := []rune(target) + copy(buff[:dotIdx-1], src[:dotIdx-1]) + copy(buff[dotIdx-1:len(src)-2], src[dotIdx+1:]) + target = string(buff[:len(src)-2]) + } + + //carve-out for the revenue parent account + amountIdx := 1 + if target == "40000 Revenue" { + amountIdx = 2 + } + + amount, err := strconv.ParseFloat((*row)[amountIdx], 64) + if err != nil { + log.Println("Error in expenseRowBufferToBalanceLine") + log.Println(err) + amount = 0.0 + } + + balance.AccountName = target + balance.Amount = amount + balance.Period = formatDate(date) + balance.AccountType = getAccountTypeFromName(target) + return &balance } func ProcessCSVsFromPath(pathlikeIn string, pathlikeOut string) { @@ -53,7 +208,8 @@ func createCsvWriter(pathlike string) *csv.Writer { log.Fatal(err) } } else { - file, err = os.OpenFile(pathlike, os.O_RDWR, 0777) + os.Remove(pathlike) + file, err = os.Create(pathlike) if err != nil { log.Fatal(err) } @@ -127,3 +283,7 @@ func enumFiles(pathlikebase string) *[]*string { func (b *BillingLine) toRow() []string { return []string{b.BillingCode, b.Date, b.InvoiceNumber, b.AccountName, b.Class, b.Amount} } + +func (t *TrialBalanceLine) toRow() []string { + return []string{t.AccountName, strconv.FormatFloat(t.Amount, 'f', 2, 64), t.Period, t.AccountType} +}