From 495bc2b7c30c56f86ea3db507ab6ecefe5102411 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Mon, 6 Dec 2021 20:19:38 +0000 Subject: [PATCH] Import category --- http/ynab-import.go | 103 ++++++++++++++++---- postgres/categories.sql.go | 117 +++++++++++++++++++++++ postgres/models.go | 25 +++-- postgres/queries/categories.sql | 20 ++++ postgres/queries/transactions.sql | 4 +- postgres/schema/202112021109_initial.sql | 18 ++++ postgres/transactions.sql.go | 19 ++-- 7 files changed, 274 insertions(+), 32 deletions(-) create mode 100644 postgres/categories.sql.go create mode 100644 postgres/queries/categories.sql diff --git a/http/ynab-import.go b/http/ynab-import.go index b284bb5..2b1afb2 100644 --- a/http/ynab-import.go +++ b/http/ynab-import.go @@ -14,11 +14,13 @@ import ( ) type YNABImport struct { - Context context.Context - accounts []postgres.Account - payees []postgres.Payee - queries *postgres.Queries - budgetID uuid.UUID + Context context.Context + accounts []postgres.Account + payees []postgres.Payee + categories []postgres.GetCategoriesRow + categoryGroups []postgres.CategoryGroup + queries *postgres.Queries + budgetID uuid.UUID } func NewYNABImport(q *postgres.Queries, budgetID uuid.UUID) (*YNABImport, error) { @@ -32,12 +34,24 @@ func NewYNABImport(q *postgres.Queries, budgetID uuid.UUID) (*YNABImport, error) return nil, err } + categories, err := q.GetCategories(context.Background(), budgetID) + if err != nil { + return nil, err + } + + categoryGroups, err := q.GetCategoryGroups(context.Background(), budgetID) + if err != nil { + return nil, err + } + return &YNABImport{ - Context: context.Background(), - accounts: accounts, - payees: payees, - queries: q, - budgetID: budgetID, + Context: context.Background(), + accounts: accounts, + payees: payees, + categories: categories, + categoryGroups: categoryGroups, + queries: q, + budgetID: budgetID, }, nil } @@ -74,7 +88,12 @@ func (ynab *YNABImport) Import(r io.Reader) error { return fmt.Errorf("could not get payee %s: %w", payeeName, err) } - //category := record[4] //also in 5 + 6 split by group/category + categoryGroup, categoryName := record[5], record[6] //also in 5 + 6 split by group/category + category, err := ynab.GetCategory(categoryGroup, categoryName) + if err != nil { + return fmt.Errorf("could not get category %s: %w", payeeName, err) + } + memo := record[7] outflow := record[8] @@ -87,11 +106,12 @@ func (ynab *YNABImport) Import(r io.Reader) error { //cleared := record[10] transaction := postgres.CreateTransactionParams{ - Date: date, - Memo: memo, - AccountID: account.ID, - PayeeID: payeeID, - Amount: amount, + Date: date, + Memo: memo, + AccountID: account.ID, + PayeeID: payeeID, + CategoryID: category, + Amount: amount, } _, err = ynab.queries.CreateTransaction(ynab.Context, transaction) if err != nil { @@ -172,3 +192,54 @@ func (ynab *YNABImport) GetPayee(name string) (uuid.NullUUID, error) { ynab.payees = append(ynab.payees, payee) return uuid.NullUUID{UUID: payee.ID, Valid: true}, nil } + +func (ynab *YNABImport) GetCategory(group string, name string) (uuid.NullUUID, error) { + if group == "" || name == "" { + return uuid.NullUUID{}, nil + } + + for _, category := range ynab.categories { + if category.Name == name && category.Group == group { + return uuid.NullUUID{UUID: category.ID, Valid: true}, nil + } + } + + for _, categoryGroup := range ynab.categoryGroups { + if categoryGroup.Name == group { + createCategory := postgres.CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID} + category, err := ynab.queries.CreateCategory(ynab.Context, createCategory) + if err != nil { + return uuid.NullUUID{}, err + } + + getCategory := postgres.GetCategoriesRow{ + ID: category.ID, + CategoryGroupID: category.CategoryGroupID, + Name: category.Name, + Group: categoryGroup.Name, + } + ynab.categories = append(ynab.categories, getCategory) + return uuid.NullUUID{UUID: category.ID, Valid: true}, nil + } + } + + categoryGroup, err := ynab.queries.CreateCategoryGroup(ynab.Context, postgres.CreateCategoryGroupParams{Name: name, BudgetID: ynab.budgetID}) + if err != nil { + return uuid.NullUUID{}, err + } + ynab.categoryGroups = append(ynab.categoryGroups, categoryGroup) + + category, err := ynab.queries.CreateCategory(ynab.Context, postgres.CreateCategoryParams{Name: name, CategoryGroupID: categoryGroup.ID}) + if err != nil { + return uuid.NullUUID{}, err + } + + getCategory := postgres.GetCategoriesRow{ + ID: category.ID, + CategoryGroupID: category.CategoryGroupID, + Name: category.Name, + Group: categoryGroup.Name, + } + ynab.categories = append(ynab.categories, getCategory) + return uuid.NullUUID{UUID: category.ID, Valid: true}, nil +} diff --git a/postgres/categories.sql.go b/postgres/categories.sql.go new file mode 100644 index 0000000..27e9908 --- /dev/null +++ b/postgres/categories.sql.go @@ -0,0 +1,117 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: categories.sql + +package postgres + +import ( + "context" + + "github.com/google/uuid" +) + +const createCategory = `-- name: CreateCategory :one +INSERT INTO categories +(name, category_group_id) +VALUES ($1, $2) +RETURNING id, category_group_id, name +` + +type CreateCategoryParams struct { + Name string + CategoryGroupID uuid.UUID +} + +func (q *Queries) CreateCategory(ctx context.Context, arg CreateCategoryParams) (Category, error) { + row := q.db.QueryRowContext(ctx, createCategory, arg.Name, arg.CategoryGroupID) + var i Category + err := row.Scan(&i.ID, &i.CategoryGroupID, &i.Name) + return i, err +} + +const createCategoryGroup = `-- name: CreateCategoryGroup :one +INSERT INTO category_groups +(name, budget_id) +VALUES ($1, $2) +RETURNING id, budget_id, name +` + +type CreateCategoryGroupParams struct { + Name string + BudgetID uuid.UUID +} + +func (q *Queries) CreateCategoryGroup(ctx context.Context, arg CreateCategoryGroupParams) (CategoryGroup, error) { + row := q.db.QueryRowContext(ctx, createCategoryGroup, arg.Name, arg.BudgetID) + var i CategoryGroup + err := row.Scan(&i.ID, &i.BudgetID, &i.Name) + return i, err +} + +const getCategories = `-- name: GetCategories :many +SELECT categories.id, categories.category_group_id, categories.name, category_groups.name as group FROM categories +INNER JOIN category_groups ON categories.category_group_id = category_groups.id +WHERE category_groups.budget_id = $1 +` + +type GetCategoriesRow struct { + ID uuid.UUID + CategoryGroupID uuid.UUID + Name string + Group string +} + +func (q *Queries) GetCategories(ctx context.Context, budgetID uuid.UUID) ([]GetCategoriesRow, error) { + rows, err := q.db.QueryContext(ctx, getCategories, budgetID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetCategoriesRow + for rows.Next() { + var i GetCategoriesRow + if err := rows.Scan( + &i.ID, + &i.CategoryGroupID, + &i.Name, + &i.Group, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getCategoryGroups = `-- name: GetCategoryGroups :many +SELECT category_groups.id, category_groups.budget_id, category_groups.name FROM category_groups +WHERE category_groups.budget_id = $1 +` + +func (q *Queries) GetCategoryGroups(ctx context.Context, budgetID uuid.UUID) ([]CategoryGroup, error) { + rows, err := q.db.QueryContext(ctx, getCategoryGroups, budgetID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CategoryGroup + for rows.Next() { + var i CategoryGroup + if err := rows.Scan(&i.ID, &i.BudgetID, &i.Name); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/postgres/models.go b/postgres/models.go index a7070cd..e3cc9a5 100644 --- a/postgres/models.go +++ b/postgres/models.go @@ -21,6 +21,18 @@ type Budget struct { LastModification sql.NullTime } +type Category struct { + ID uuid.UUID + CategoryGroupID uuid.UUID + Name string +} + +type CategoryGroup struct { + ID uuid.UUID + BudgetID uuid.UUID + Name string +} + type Payee struct { ID uuid.UUID BudgetID uuid.UUID @@ -28,12 +40,13 @@ type Payee struct { } type Transaction struct { - ID uuid.UUID - Date time.Time - Memo string - Amount Numeric - AccountID uuid.UUID - PayeeID uuid.NullUUID + ID uuid.UUID + Date time.Time + Memo string + Amount Numeric + AccountID uuid.UUID + CategoryID uuid.NullUUID + PayeeID uuid.NullUUID } type User struct { diff --git a/postgres/queries/categories.sql b/postgres/queries/categories.sql new file mode 100644 index 0000000..4ee4e58 --- /dev/null +++ b/postgres/queries/categories.sql @@ -0,0 +1,20 @@ +-- name: CreateCategoryGroup :one +INSERT INTO category_groups +(name, budget_id) +VALUES ($1, $2) +RETURNING *; + +-- name: GetCategoryGroups :many +SELECT category_groups.* FROM category_groups +WHERE category_groups.budget_id = $1; + +-- name: CreateCategory :one +INSERT INTO categories +(name, category_group_id) +VALUES ($1, $2) +RETURNING *; + +-- name: GetCategories :many +SELECT categories.*, category_groups.name as group FROM categories +INNER JOIN category_groups ON categories.category_group_id = category_groups.id +WHERE category_groups.budget_id = $1; \ No newline at end of file diff --git a/postgres/queries/transactions.sql b/postgres/queries/transactions.sql index adbe754..d77cbfb 100644 --- a/postgres/queries/transactions.sql +++ b/postgres/queries/transactions.sql @@ -1,7 +1,7 @@ -- name: CreateTransaction :one INSERT INTO transactions -(date, memo, amount, account_id, payee_id) -VALUES ($1, $2, $3, $4, $5) +(date, memo, amount, account_id, payee_id, category_id) +VALUES ($1, $2, $3, $4, $5, $6) RETURNING *; -- name: GetTransactionsForBudget :many diff --git a/postgres/schema/202112021109_initial.sql b/postgres/schema/202112021109_initial.sql index ddc3e88..481cc04 100644 --- a/postgres/schema/202112021109_initial.sql +++ b/postgres/schema/202112021109_initial.sql @@ -33,6 +33,7 @@ CREATE TABLE payees ( budget_id uuid NOT NULL, name varchar(50) NOT NULL ); +ALTER TABLE "payees" ADD FOREIGN KEY ("budget_id") REFERENCES "budgets" ("id"); CREATE TABLE transactions ( id uuid DEFAULT uuid_generate_v4() PRIMARY KEY, @@ -40,10 +41,27 @@ CREATE TABLE transactions ( memo text NOT NULL, amount decimal(12,2) NOT NULL, account_id uuid NOT NULL, + category_id uuid, payee_id uuid ); ALTER TABLE "transactions" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id"); ALTER TABLE "transactions" ADD FOREIGN KEY ("payee_id") REFERENCES "payees" ("id"); +ALTER TABLE "transactions" ADD FOREIGN KEY ("category_id") REFERENCES "categories" ("id"); + +CREATE TABLE category_groups ( + id uuid DEFAULT uuid_generate_v4() PRIMARY KEY, + budget_id uuid NOT NULL, + name varchar(50) NOT NULL +); +ALTER TABLE "category_groups" ADD FOREIGN KEY ("budget_id") REFERENCES "budgets" ("id"); + +CREATE TABLE categories ( + id uuid DEFAULT uuid_generate_v4() PRIMARY KEY, + category_group_id uuid NOT NULL, + name varchar(50) NOT NULL +); +ALTER TABLE "categories" ADD FOREIGN KEY ("category_group_id") REFERENCES "category_group" ("id"); + -- +goose Down DROP TABLE transactions; diff --git a/postgres/transactions.sql.go b/postgres/transactions.sql.go index 40e4d22..a5e90a2 100644 --- a/postgres/transactions.sql.go +++ b/postgres/transactions.sql.go @@ -12,17 +12,18 @@ import ( const createTransaction = `-- name: CreateTransaction :one INSERT INTO transactions -(date, memo, amount, account_id, payee_id) -VALUES ($1, $2, $3, $4, $5) -RETURNING id, date, memo, amount, account_id, payee_id +(date, memo, amount, account_id, payee_id, category_id) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING id, date, memo, amount, account_id, category_id, payee_id ` type CreateTransactionParams struct { - Date time.Time - Memo string - Amount Numeric - AccountID uuid.UUID - PayeeID uuid.NullUUID + Date time.Time + Memo string + Amount Numeric + AccountID uuid.UUID + PayeeID uuid.NullUUID + CategoryID uuid.NullUUID } func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (Transaction, error) { @@ -32,6 +33,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa arg.Amount, arg.AccountID, arg.PayeeID, + arg.CategoryID, ) var i Transaction err := row.Scan( @@ -40,6 +42,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa &i.Memo, &i.Amount, &i.AccountID, + &i.CategoryID, &i.PayeeID, ) return i, err