From 3601f449c9cbf461a2ee1eb21ff11d4b38c52632 Mon Sep 17 00:00:00 2001 From: Alexander Avery Date: Sat, 28 May 2022 22:01:34 -0400 Subject: [PATCH] starting ofx and xml generators --- generators.go | 82 +++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 56 +++++++++++++++------------------ transactions.go | 19 ++++++------ 3 files changed, 116 insertions(+), 41 deletions(-) create mode 100644 generators.go diff --git a/generators.go b/generators.go new file mode 100644 index 0000000..26c0f48 --- /dev/null +++ b/generators.go @@ -0,0 +1,82 @@ +package main + +import ( + "encoding/xml" + "github.com/aclindsa/ofxgo" + "time" + "fmt" + "github.com/ochinchina/go-ini" + "io" +) + +func transactionsFromXml(r io.Reader, assets ini.Section, vendors []ini.Key) ([]transaction, error) { + type xmlTransaction struct { + XMLName xml.Name `xml:"transaction"` + Date string `xml:"Date"` + TrnAmt float64 `xml:"amount"` + Asset string `xml:"Asset"` + Expense string `xml:"Expense,omitempty"` + VendorName string `xml:"VendorName,omitempty"` + Currency string `xml:"Currency"` + } + type transactionParent struct { + XMLName xml.Name `xml:"transactions"` + Transactions []xmlTransaction `xml:"transaction"` + } + root := new(transactionParent) + d := xml.NewDecoder(r) + err := d.Decode(&root) + if err != nil { + return nil, fmt.Errorf("failed unmarshaling xml: %w", err) + } + + xtx := root.Transactions + transactions := make([]transaction, 0) + for _, t := range xtx { + var expense string + if t.VendorName != "" && t.Expense == "" { + expense = matchPartial(vendors, t.VendorName) + } + time, err := time.Parse("1/02/2006", t.Date) + if err != nil { + return nil, fmt.Errorf("failed parsing time: %w", err) + } + tx := transaction { + Date: time, + TrnAmt: t.TrnAmt, + Asset: t.Asset, + Expense: expense, + VendorName: t.VendorName, + Currency: t.Currency, + } + transactions = append(transactions, tx) + } + return transactions, nil +} + +func transactionsFromOfx(r io.Reader, assets ini.Section, vendors []ini.Key) ([]transaction, error) { + resp, err := ofxgo.ParseResponse(r) + if err != nil { + return nil, err + } + + transactions := make([]transaction, 0) + for _, m := range resp.Bank { + if stmt, ok := m.(*ofxgo.StatementResponse); ok { + for _, t := range stmt.BankTranList.Transactions { + acctId := stmt.BankAcctFrom.AcctID.String() + amt, _ := t.TrnAmt.Rat.Float64() + tx := transaction{ + Date: t.DtPosted.Time, + TrnAmt: amt, + Asset: assets.GetValueWithDefault(acctId, acctId), + Expense: matchPartial(vendors, t.Name.String()), + VendorName: t.Name.String(), + Currency: stmt.CurDef.String(), + } + transactions = append(transactions, tx) + } + } + } + return transactions, nil +} diff --git a/main.go b/main.go index a1ac3ae..bfeb7f0 100644 --- a/main.go +++ b/main.go @@ -1,19 +1,18 @@ package main import ( - "flag" - "os" "bufio" + "flag" + "github.com/ochinchina/go-ini" + "io" "log" + "os" + "sort" "strings" - "io" "text/template" - "sort" - "github.com/aclindsa/ofxgo" - "github.com/ochinchina/go-ini" ) -func matchVendor(keys []ini.Key, givenName string) string { +func matchPartial(keys []ini.Key, givenName string) string { for _, k := range keys { v := k.ValueWithDefault("") if strings.Contains(givenName, k.Name()) { @@ -34,20 +33,24 @@ func generateLedgerTransactions(transactions []transaction, w io.Writer) error { return nil } +var ( + config = flag.String("config", "", "configuration file containing all your vendor and account mappings") + inputType = flag.String("T", "ofx", "input type of transaction data - supports ofx and xml") +) + func main() { - var config = flag.String("config", "", "configuration file containing all your vendor and account mappings") flag.Parse() filename := flag.Arg(0) r, err := os.Open(filename) if err != nil { - log.Fatal(err) + log.Fatalf("failed opening data file: %w", err) } defer r.Close() c, err := os.Open(*config) if err != nil { - log.Fatal(err) + log.Fatalf("failed opening config file: %w", err) } defer c.Close() @@ -56,33 +59,24 @@ func main() { assets, err := cfg.GetSection("Assets") if err != nil { - log.Fatal(err) + log.Fatalf("failed finding assets section of config: %w", err) } vendors, err := cfg.GetSection("Vendors") if err != nil { - log.Fatal(err) - } - - resp, err := ofxgo.ParseResponse(r) - if err != nil { - log.Fatal(err) + log.Fatalf("failed finding vendors section of config: %w", err) } - transactions := make([]transaction, 0) - for _, m := range resp.Bank { - if stmt, ok := m.(*ofxgo.StatementResponse); ok { - for _, t := range stmt.BankTranList.Transactions { - acctId := stmt.BankAcctFrom.AcctID.String() - tx := transaction{ - Date: t.DtPosted.Time, - TrnAmt: t.TrnAmt, - Asset: assets.GetValueWithDefault(acctId, acctId), - Vendor: matchVendor(vendors.Keys(), t.Name.String()), - VendorName: t.Name.String(), - } - transactions = append(transactions, tx) - } + var transactions []transaction + if *inputType == "ofx" { + transactions, err = transactionsFromOfx(r, *assets, vendors.Keys()) + if err != nil { + log.Fatalf("failed generating transactions from ofx: %w", err) + } + } else if *inputType == "xml" { + transactions, err = transactionsFromXml(r, *assets, vendors.Keys()) + if err != nil { + log.Fatalf("failed generating transactions from xml: %w", err) } } diff --git a/transactions.go b/transactions.go index 8bc7a2b..353946b 100644 --- a/transactions.go +++ b/transactions.go @@ -2,24 +2,23 @@ package main import ( "time" - "github.com/aclindsa/ofxgo" ) -var transactionTemplate = -` +var transactionTemplate = ` {{ range . }} {{ .Date.Format "2006-01-02" }} {{ .VendorName }} - {{ .Asset }} ${{ .TrnAmt }} - {{ .Vendor }} + {{ .Asset }} {{ .TrnAmt }} {{ .Currency }} + {{ .Expense }} {{ end }} ` type transaction struct { - Date time.Time - TrnAmt ofxgo.Amount - Asset string - Vendor string - VendorName string + Date time.Time `xml:"Date"` + TrnAmt float64 `xml:"Amount"` + Asset string `xml:"Asset"` + Expense string `xml:"Expense,omitempty"` + VendorName string `xml:"VendorName,omitempty"` + Currency string `xml:"Currency"` } type byDate []transaction