package http import ( "context" "encoding/csv" "fmt" "io" "strings" "time" "unicode/utf8" "git.javil.eu/jacob1123/budgeteer/postgres" "github.com/google/uuid" "github.com/jackc/pgtype" ) type YNABImport struct { Context context.Context accounts []postgres.Account payees []postgres.Payee queries *postgres.Queries budgetID uuid.UUID } func NewYNABImport(q *postgres.Queries, budgetID uuid.UUID) (*YNABImport, error) { accounts, err := q.GetAccounts(context.Background(), uuid.UUID{}) if err != nil { return nil, err } payees, err := q.GetPayees(context.Background(), uuid.UUID{}) if err != nil { return nil, err } return &YNABImport{ Context: context.Background(), accounts: accounts, payees: payees, queries: q, budgetID: budgetID, }, nil } func (ynab *YNABImport) Import(r io.Reader) error { csv := csv.NewReader(r) csv.Comma = '\t' csv.LazyQuotes = true csvData, err := csv.ReadAll() if err != nil { return fmt.Errorf("could not read from tsv: %w", err) } count := 0 for _, record := range csvData[1:] { accountName := record[0] account, err := ynab.GetAccount(accountName) if err != nil { return fmt.Errorf("could not get account %s: %w", accountName, err) } //flag := record[1] dateString := record[2] date, err := time.Parse("02.01.2006", dateString) if err != nil { return fmt.Errorf("could not parse date %s: %w", dateString, err) } payeeName := record[3] payeeID, err := ynab.GetPayee(payeeName) if err != nil { return fmt.Errorf("could not get payee %s: %w", payeeName, err) } //category := record[4] //also in 5 + 6 split by group/category memo := record[7] outflow := record[8] inflow := record[9] amount, err := GetAmount(inflow, outflow) if err != nil { return fmt.Errorf("could not parse amount from (%s/%s): %w", inflow, outflow, err) } //cleared := record[10] transaction := postgres.CreateTransactionParams{ Date: date, Memo: memo, AccountID: account.ID, PayeeID: payeeID, Amount: amount, } _, err = ynab.queries.CreateTransaction(ynab.Context, transaction) if err != nil { return fmt.Errorf("could not save transaction %v: %w", transaction, err) } count++ } fmt.Printf("Imported %d transactions\n", count) return nil } func trimLastChar(s string) string { r, size := utf8.DecodeLastRuneInString(s) if r == utf8.RuneError && (size == 0 || size == 1) { size = 0 } return s[:len(s)-size] } func GetAmount(inflow string, outflow string) (pgtype.Numeric, error) { // Remove trailing currency inflow = strings.Replace(trimLastChar(inflow), ",", ".", 1) outflow = strings.Replace(trimLastChar(outflow), ",", ".", 1) num := pgtype.Numeric{} err := num.Set(inflow) if err != nil { return num, fmt.Errorf("Could not parse inflow %s: %w", inflow, err) } // if inflow is zero, use outflow if num.Int.Int64() != 0 { return num, nil } err = num.Set("-" + outflow) if err != nil { return num, fmt.Errorf("Could not parse outflow %s: %w", inflow, err) } return num, nil } func (ynab *YNABImport) GetAccount(name string) (*postgres.Account, error) { for _, acc := range ynab.accounts { if acc.Name == name { return &acc, nil } } account, err := ynab.queries.CreateAccount(ynab.Context, postgres.CreateAccountParams{Name: name, BudgetID: ynab.budgetID}) if err != nil { return nil, err } ynab.accounts = append(ynab.accounts, account) return &account, nil } func (ynab *YNABImport) GetPayee(name string) (uuid.NullUUID, error) { if name == "" { return uuid.NullUUID{}, nil } for _, pay := range ynab.payees { if pay.Name == name { return uuid.NullUUID{UUID: pay.ID, Valid: true}, nil } } payee, err := ynab.queries.CreatePayee(ynab.Context, postgres.CreatePayeeParams{Name: name, BudgetID: ynab.budgetID}) if err != nil { return uuid.NullUUID{}, err } ynab.payees = append(ynab.payees, payee) return uuid.NullUUID{UUID: payee.ID, Valid: true}, nil }