~mendelmaleh/pfin

7549f84d367cc8e30c7fa04d46351d7e82f6c074 — Mendel E a month ago 619d7c9
Refactor, change output format
3 files changed, 108 insertions(+), 74 deletions(-)

M cmd/main/main.go
M cmd/status/main.go
M cmd/unpaid/main.go
M cmd/main/main.go => cmd/main/main.go +23 -12
@@ 11,10 11,7 @@ import (
	"git.sr.ht/~mendelmaleh/pfin"
	"git.sr.ht/~mendelmaleh/pfin/util"

	_ "git.sr.ht/~mendelmaleh/pfin/parser/amex"
	_ "git.sr.ht/~mendelmaleh/pfin/parser/bofa"
	_ "git.sr.ht/~mendelmaleh/pfin/parser/capitalone"
	_ "git.sr.ht/~mendelmaleh/pfin/parser/personal"
	_ "git.sr.ht/~mendelmaleh/pfin/parser/all"
)

type Opts struct {


@@ 57,27 54,41 @@ func main() {

	tw := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)

	var sum = map[string]float64{}
	users := map[string]int{}
	sums := []struct{ debits, credits float64 }{}

	for _, tx := range txns {
		if opts.Users.Filter(tx.User()) {
		u := tx.User()
		a := tx.Amount()

		if opts.Users.Filter(u) {
			continue
		}

		if _, ok := sum[tx.User()]; !ok {
			sum[tx.User()] = 0
		if _, ok := users[u]; !ok {
			users[u] = len(users)
			sums = append(sums, struct{ debits, credits float64 }{})
		}

		if a < 0 {
			sums[users[u]].debits += a
		} else {
			sums[users[u]].credits += a
		}

		sum[tx.User()] += tx.Amount()
		fmt.Fprintln(tw, util.FormatTx(tx, "\t"))
	}

	tw.Flush()
	fmt.Println()

	tw.Init(os.Stdout, 0, 8, 1, '\t', 0)
	for user, total := range sum {
		fmt.Fprintf(tw, "%s\t%.2f\n", user, total)
	tw.Init(os.Stdout, 0, 8, 1, '\t', 1)

	fmt.Fprintf(tw, "name\tdebits\tcredits\ttotal\n")
	fmt.Fprintf(tw, "----\t------\t-------\t-----\n")

	for k, v := range users {
		fmt.Fprintf(tw, "%s\t%.2f\t%.2f\t%.2f\n", k, sums[v].debits, sums[v].credits, sums[v].debits+sums[v].credits)
	}

	tw.Flush()

M cmd/status/main.go => cmd/status/main.go +33 -9
@@ 3,7 3,10 @@ package main
import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"sort"
	"text/tabwriter"
	"time"

	"git.sr.ht/~mendelmaleh/pfin"


@@ 19,10 22,29 @@ func main() {
		log.Fatal(err)
	}

	// parse accounts
	txns := make(map[string][]pfin.Transaction, len(config.Account))
	tw := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
	defer tw.Flush()

	for name, acc := range config.Account {
	fmt.Fprint(tw, "account\tlast tx\tdays\tlast file\n")
	fmt.Fprint(tw, "-------\t-------\t----\t---------\n")

	var accounts []string
	for name, _ := range config.Account {
		accounts = append(accounts, name)
	}

	sort.Strings(accounts)

	for _, name := range accounts {
		acc := config.Account[name]

		// parse dirs
		matches, err := util.MatchDir(acc, config.Pfin.Root)
		if err != nil {
			log.Fatal(err)
		}

		// parse transactions
		tx, err := util.ParseDir(acc, config.Pfin.Root)
		if err != nil {
			log.Fatal(err)


@@ 32,12 54,14 @@ func main() {
			return tx[i].Date().Before(tx[j].Date())
		})

		txns[name] = tx
	}

	// days since last transaction
	for name, tx := range txns {
		// output
		last := tx[len(tx)-1].Date()
		fmt.Printf("%s: %d\n", name, int(time.Now().Sub(last).Hours())/24)
		fmt.Fprintf(
			tw, "%s\t%s\t%d\t%s\n",
			name,
			util.FormatDate(last),
			int(time.Now().Sub(last).Hours())/24,
			filepath.Base(matches[len(matches)-1]),
		)
	}
}

M cmd/unpaid/main.go => cmd/unpaid/main.go +52 -53
@@ 38,7 38,7 @@ func main() {
	}

	var debits []pfin.Transaction
	var credit float64
	var debit, credit float64

	for name, acc := range config.Account {
		if opts.Accounts.Filter(name) && opts.Payments.Filter(name) {


@@ 55,12 55,13 @@ func main() {
				continue
			}

			if tx.Amount() < 0 {
				credit += tx.Amount()
			} else {
			if a := tx.Amount(); a > 0 {
				debits = append(debits, tx)
				debit += a
			} else {
				// credits = append(credits, tx)
				credit += a
			}

		}
	}



@@ 69,65 70,63 @@ func main() {
	})

	tw := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
	i := 0

	categories := make(map[string]float64)
	balance := credit * -1
	var paid float64
	for i < len(debits) {
		a := debits[i].Amount()

	var total, paid float64
	for _, tx := range debits {
		a := tx.Amount()

		// calculate paid
		if a < balance {
			paid += a
		if paid+a > -credit {
			break
		}

		// print uncovered debits
		if a > balance {
			total += a
		paid += a
		i++
	}

			// calculate unpaid categories
			c := tx.Category()
	var unpaid float64
	for ; i < len(debits); i++ {
		tx := debits[i]
		unpaid += tx.Amount()

		fmt.Fprintln(tw, strings.Join([]string{
			util.FormatDate(tx.Date()),
			util.FormatCents(tx.Amount()),
			tx.Name(),
			tx.Category(),
		}, opts.Separator))
	}

			if _, ok := categories[c]; !ok {
				categories[c] = a
			} else {
				categories[c] += a
			}
	fmt.Fprint(tw, "\n")
	tw.Flush()

			fmt.Fprintln(tw, strings.Join([]string{
				util.FormatDate(tx.Date()),
				util.FormatCents(a),
				tx.Name(),
				tx.Category(),
			}, opts.Separator))
		}
	tw.Init(os.Stdout, 1, 8, 2, ' ', 0)

		// update balance
		balance -= a
	}
	func(keys ...string) {
		header := strings.Join(keys, "\t")
		fmt.Fprintln(tw, header)

	data := [][]string{
		{},
		{"Total:", util.FormatCents(total), "((at least partially) unpaid)"},
		{"Paid:", util.FormatCents(paid), "(completely covered by payments)"},
		{"Payments:", util.FormatCents(credit), "(previous payments)"},
		{"Balance:", util.FormatCents(balance), "(unpaid + paid - payments) * -1"},
		{},
		{"By category:"},
		{},
	}
		fmt.Fprintln(tw, strings.Map(func(r rune) rune {
			if r != '\t' {
				return '-'
			}
			return r
		}, header))
	}("balance", "unpaid", "paid", "debits", "credits")

	var sorted []string
	for k, _ := range categories {
		sorted = append(sorted, k)
	}
	func(values ...float64) {
		var b strings.Builder

	sort.Strings(sorted)
	for _, k := range sorted {
		data = append(data, []string{k + ":", util.FormatCents(categories[k])})
	}
		for i, v := range values {
			if i != 0 {
				b.WriteString("\t")
			}

			b.WriteString(util.FormatCents(v))
		}

		fmt.Fprintln(tw, b.String())
	}(debit+credit, unpaid, paid, debit, credit)

	fmt.Fprint(tw, util.FormatFields(data, opts.Separator))
	tw.Flush()
}