diff options
author | Martin Polden <mpolden@mpolden.no> | 2021-11-11 22:07:41 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2021-11-11 22:11:20 +0100 |
commit | e8946bd434d795dd652658bf135968e8112eefd7 (patch) | |
tree | 3cf1d8ad32ae5e0c3fcdab2175aa247da2d95d54 | |
parent | 3ceda428ae17b2bedddacab0d73843e1226b7aa0 (diff) |
record: Add support for Bulder Bank
-rw-r--r-- | record/bulder/builder_test.go | 51 | ||||
-rw-r--r-- | record/bulder/bulder.go | 82 |
2 files changed, 133 insertions, 0 deletions
diff --git a/record/bulder/builder_test.go b/record/bulder/builder_test.go new file mode 100644 index 0000000..45fea01 --- /dev/null +++ b/record/bulder/builder_test.go @@ -0,0 +1,51 @@ +package bulder + +import ( + "strings" + "testing" + "time" +) + +func date(year int, month time.Month, day int) time.Time { + return time.Date(year, month, day, 0, 0, 0, 0, time.UTC) +} + +func TestRead(t *testing.T) { + in := `Dato;Inn på konto;Ut fra konto;Balanse;Til konto;Til kontonummer;Fra konto;Fra kontonummer;Type;Tekst/KID;Hovedkategori;Underkategori +2021-11-10;2000,00;;2000,00;min konto;mitt kontonr;annen konto;annet kontonr;;Gave;; +2021-11-15;;-1000,00;1000,00;annen konto;annet kontonr;min konto;mitt kontonr;;Butikk 1;; +` + r := NewReader(strings.NewReader(in)) + rs, err := r.Read() + if err != nil { + t.Fatal(err) + } + + var tests = []struct { + t time.Time + text string + amount int64 + balance int64 + }{ + {date(2021, 11, 10), "Gave", 200000, 200000}, + {date(2021, 11, 15), "Butikk 1", -100000, 100000}, + } + if len(rs) != len(tests) { + t.Fatalf("want %d records, got %d", len(tests), len(rs)) + } + + for i, tt := range tests { + if !rs[i].Time.Equal(tt.t) { + t.Errorf("#%d: want Time = %s, got %s", i, tt.t, rs[i].Time) + } + if rs[i].Text != tt.text { + t.Errorf("#%d: want Text = %q, got %q", i, tt.text, rs[i].Text) + } + if rs[i].Amount != tt.amount { + t.Errorf("#%d: want Amount = %d, got %d", i, tt.amount, rs[i].Amount) + } + if rs[i].Balance != tt.balance { + t.Errorf("#%d: want Balance = %d, got %d", i, tt.balance, rs[i].Balance) + } + } +} diff --git a/record/bulder/bulder.go b/record/bulder/bulder.go new file mode 100644 index 0000000..815f3eb --- /dev/null +++ b/record/bulder/bulder.go @@ -0,0 +1,82 @@ +package bulder + +import ( + "bufio" + "encoding/csv" + "fmt" + "io" + "strconv" + "strings" + "time" + + "github.com/mpolden/journal/record" +) + +// Reader implements a reader for Bulder-encoded (CSV) records. +type Reader struct { + rd io.Reader +} + +// NewReader returns a new reader for Bulder-encoded records. +func NewReader(rd io.Reader) *Reader { + return &Reader{ + rd: rd, + } +} + +// Read all records from the underlying reader. +func (r *Reader) Read() ([]record.Record, error) { + buf := bufio.NewReader(r.rd) + c := csv.NewReader(buf) + c.Comma = ';' + var rs []record.Record + line := 0 + for { + csvRecord, err := c.Read() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + line++ + if line == 1 { + continue // Skip header + } + if len(csvRecord) < 12 { + continue + } + t, err := time.Parse("2006-01-02", csvRecord[0]) + if err != nil { + return nil, fmt.Errorf("invalid time on line %d: %q: %w", line, csvRecord[0], err) + } + amountValue := csvRecord[1] + if amountValue == "" { + amountValue = csvRecord[2] + } + amount, err := parseAmount(amountValue) + if err != nil { + return nil, fmt.Errorf("invalid amount on line %d: %q: %w", line, amountValue, err) + } + var balance int64 + balanceValue := csvRecord[3] + if balanceValue != "" { + balance, err = parseAmount(balanceValue) + if err != nil { + return nil, fmt.Errorf("invalid balance on line %d: %q: %w", line, balanceValue, err) + } + } + text := csvRecord[9] + rs = append(rs, record.Record{Time: t, Text: text, Amount: amount, Balance: balance}) + } + return rs, nil +} + +func parseAmount(s string) (int64, error) { + v := strings.ReplaceAll(s, ",", "") + n, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return 0, err + } + return n, nil +} |