diff --git a/postgres/accounts.sql.go b/postgres/accounts.sql.go index ab5de4e..666e620 100644 --- a/postgres/accounts.sql.go +++ b/postgres/accounts.sql.go @@ -5,7 +5,7 @@ package postgres import ( "context" - "time" + "database/sql" "git.javil.eu/jacob1123/budgeteer/postgres/numeric" "github.com/google/uuid" @@ -15,7 +15,7 @@ const createAccount = `-- name: CreateAccount :one INSERT INTO accounts (name, budget_id) VALUES ($1, $2) -RETURNING id, budget_id, name, on_budget, is_open +RETURNING id, budget_id, name, on_budget, is_open, last_reconciled ` type CreateAccountParams struct { @@ -32,12 +32,13 @@ func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (A &i.Name, &i.OnBudget, &i.IsOpen, + &i.LastReconciled, ) return i, err } const getAccount = `-- name: GetAccount :one -SELECT accounts.id, accounts.budget_id, accounts.name, accounts.on_budget, accounts.is_open FROM accounts +SELECT accounts.id, accounts.budget_id, accounts.name, accounts.on_budget, accounts.is_open, accounts.last_reconciled FROM accounts WHERE accounts.id = $1 ` @@ -50,12 +51,13 @@ func (q *Queries) GetAccount(ctx context.Context, id uuid.UUID) (Account, error) &i.Name, &i.OnBudget, &i.IsOpen, + &i.LastReconciled, ) return i, err } const getAccounts = `-- name: GetAccounts :many -SELECT accounts.id, accounts.budget_id, accounts.name, accounts.on_budget, accounts.is_open FROM accounts +SELECT accounts.id, accounts.budget_id, accounts.name, accounts.on_budget, accounts.is_open, accounts.last_reconciled FROM accounts WHERE accounts.budget_id = $1 AND accounts.is_open = TRUE ORDER BY accounts.name @@ -76,6 +78,7 @@ func (q *Queries) GetAccounts(ctx context.Context, budgetID uuid.UUID) ([]Accoun &i.Name, &i.OnBudget, &i.IsOpen, + &i.LastReconciled, ); err != nil { return nil, err } @@ -91,8 +94,7 @@ func (q *Queries) GetAccounts(ctx context.Context, budgetID uuid.UUID) ([]Accoun } const getAccountsWithBalance = `-- name: GetAccountsWithBalance :many -SELECT accounts.id, accounts.name, accounts.on_budget, accounts.is_open, - (SELECT MAX(transactions.date) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.status = 'Reconciled')::date as last_reconciled, +SELECT accounts.id, accounts.name, accounts.on_budget, accounts.is_open, accounts.last_reconciled, (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW())::decimal(12,2) as working_balance, (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status IN ('Cleared', 'Reconciled'))::decimal(12,2) as cleared_balance, (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status = 'Reconciled')::decimal(12,2) as reconciled_balance @@ -107,7 +109,7 @@ type GetAccountsWithBalanceRow struct { Name string OnBudget bool IsOpen bool - LastReconciled time.Time + LastReconciled sql.NullTime WorkingBalance numeric.Numeric ClearedBalance numeric.Numeric ReconciledBalance numeric.Numeric @@ -193,13 +195,24 @@ func (q *Queries) SearchAccounts(ctx context.Context, arg SearchAccountsParams) return items, nil } +const setLastReconciled = `-- name: SetLastReconciled :exec +UPDATE accounts +SET last_reconciled = NOW() +WHERE accounts.id = $1 +` + +func (q *Queries) SetLastReconciled(ctx context.Context, id uuid.UUID) error { + _, err := q.db.ExecContext(ctx, setLastReconciled, id) + return err +} + const updateAccount = `-- name: UpdateAccount :one UPDATE accounts SET name = $1, on_budget = $2, is_open = $3 WHERE accounts.id = $4 -RETURNING id, budget_id, name, on_budget, is_open +RETURNING id, budget_id, name, on_budget, is_open, last_reconciled ` type UpdateAccountParams struct { @@ -223,6 +236,7 @@ func (q *Queries) UpdateAccount(ctx context.Context, arg UpdateAccountParams) (A &i.Name, &i.OnBudget, &i.IsOpen, + &i.LastReconciled, ) return i, err } diff --git a/postgres/models.go b/postgres/models.go index 7ff33e8..8e01747 100644 --- a/postgres/models.go +++ b/postgres/models.go @@ -32,11 +32,12 @@ func (e *TransactionStatus) Scan(src interface{}) error { } type Account struct { - ID uuid.UUID - BudgetID uuid.UUID - Name string - OnBudget bool - IsOpen bool + ID uuid.UUID + BudgetID uuid.UUID + Name string + OnBudget bool + IsOpen bool + LastReconciled sql.NullTime } type Assignment struct { diff --git a/postgres/queries/accounts.sql b/postgres/queries/accounts.sql index 0e62900..6390403 100644 --- a/postgres/queries/accounts.sql +++ b/postgres/queries/accounts.sql @@ -15,8 +15,7 @@ AND accounts.is_open = TRUE ORDER BY accounts.name; -- name: GetAccountsWithBalance :many -SELECT accounts.id, accounts.name, accounts.on_budget, accounts.is_open, - (SELECT MAX(transactions.date) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.status = 'Reconciled')::date as last_reconciled, +SELECT accounts.id, accounts.name, accounts.on_budget, accounts.is_open, accounts.last_reconciled, (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW())::decimal(12,2) as working_balance, (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status IN ('Cleared', 'Reconciled'))::decimal(12,2) as cleared_balance, (SELECT SUM(transactions.amount) FROM transactions WHERE transactions.account_id = accounts.id AND transactions.date < NOW() AND transactions.status = 'Reconciled')::decimal(12,2) as reconciled_balance @@ -38,4 +37,9 @@ SET name = $1, on_budget = $2, is_open = $3 WHERE accounts.id = $4 -RETURNING *; \ No newline at end of file +RETURNING *; + +-- name: SetLastReconciled :exec +UPDATE accounts +SET last_reconciled = NOW() +WHERE accounts.id = $1; \ No newline at end of file diff --git a/postgres/schema/0018_reconciled-on.sql b/postgres/schema/0018_reconciled-on.sql new file mode 100644 index 0000000..a8adb68 --- /dev/null +++ b/postgres/schema/0018_reconciled-on.sql @@ -0,0 +1,5 @@ +-- +goose Up +ALTER TABLE accounts ADD COLUMN last_reconciled date NULL; + +-- +goose Down +ALTER TABLE accounts DROP COLUMN last_reconciled; \ No newline at end of file diff --git a/server/reconcile.go b/server/reconcile.go index 7b9cd5a..2ac619b 100644 --- a/server/reconcile.go +++ b/server/reconcile.go @@ -65,6 +65,12 @@ func (h *Handler) reconcileTransactions(c *gin.Context) { return } + err = h.Service.SetLastReconciled(c.Request.Context(), accountUUID) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("set last reconciled: %w", err)) + return + } + err = tx.Commit() if err != nil { c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("commit: %w", err)) diff --git a/web/src/pages/BudgetSidebar.vue b/web/src/pages/BudgetSidebar.vue index e3e2045..4b2697d 100644 --- a/web/src/pages/BudgetSidebar.vue +++ b/web/src/pages/BudgetSidebar.vue @@ -20,9 +20,12 @@ const OnBudgetAccountsBalance = computed(() => accountStore.OnBudgetAccountsBala const OffBudgetAccountsBalance = computed(() => accountStore.OffBudgetAccountsBalance); function isRecentlyReconciled(account: Account) { + if(!account.LastReconciled.Valid) + return false; + const now = new Date().getTime(); const recently = 7 * 24 * 60 * 60 * 1000; - return new Date(now - recently).getTime() < account.LastReconciled.getTime(); + return new Date(now - recently).getTime() < account.LastReconciled.Time.getTime(); } function getAccountName(account: Account) { diff --git a/web/src/stores/budget-account.ts b/web/src/stores/budget-account.ts index bf64694..e582248 100644 --- a/web/src/stores/budget-account.ts +++ b/web/src/stores/budget-account.ts @@ -21,7 +21,12 @@ export interface Account { WorkingBalance: number ReconciledBalance: number Transactions: string[] - LastReconciled: Date + LastReconciled: NullDate +} + +interface NullDate { + Valid: boolean + Time: Date } export interface Category { diff --git a/web/src/stores/budget.ts b/web/src/stores/budget.ts index 57200c7..49b8310 100644 --- a/web/src/stores/budget.ts +++ b/web/src/stores/budget.ts @@ -58,7 +58,8 @@ export const useBudgetsStore = defineStore('budget', { for (const account of response.Accounts || []) { const existingAccount = accounts.Accounts.get(account.ID); account.Transactions = existingAccount?.Transactions ?? []; - account.LastReconciled = new Date(account.LastReconciled); + if(account.LastReconciled.Valid) + account.LastReconciled.Time = new Date(account.LastReconciled.Time); accounts.Accounts.set(account.ID, account); } for (const category of response.Categories || []) {