aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2021-11-11 22:07:41 +0100
committerMartin Polden <mpolden@mpolden.no>2021-11-11 22:11:20 +0100
commite8946bd434d795dd652658bf135968e8112eefd7 (patch)
tree3cf1d8ad32ae5e0c3fcdab2175aa247da2d95d54
parent3ceda428ae17b2bedddacab0d73843e1226b7aa0 (diff)
record: Add support for Bulder Bank
-rw-r--r--record/bulder/builder_test.go51
-rw-r--r--record/bulder/bulder.go82
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
+}