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..42a16ed --- /dev/null +++ b/postgres/schema/0018_reconciled-on.sql @@ -0,0 +1,12 @@ +-- +goose Up +ALTER TABLE accounts ADD COLUMN last_reconciled date NULL; +UPDATE accounts +SET last_reconciled = ( + SELECT MAX(transactions.date) + FROM transactions + WHERE transactions.account_id = accounts.id + AND transactions.status = 'Reconciled' +); + +-- +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/components/AccountWithReconciled.vue b/web/src/components/AccountWithReconciled.vue new file mode 100644 index 0000000..044c754 --- /dev/null +++ b/web/src/components/AccountWithReconciled.vue @@ -0,0 +1,31 @@ + + + \ No newline at end of file diff --git a/web/src/pages/BudgetSidebar.vue b/web/src/pages/BudgetSidebar.vue index e3e2045..0407e76 100644 --- a/web/src/pages/BudgetSidebar.vue +++ b/web/src/pages/BudgetSidebar.vue @@ -4,6 +4,7 @@ import Currency from "../components/Currency.vue" import { useBudgetsStore } from "../stores/budget" import { Account, useAccountStore } from "../stores/budget-account" import { useSettingsStore } from "../stores/settings" +import AccountWithReconciled from "../components/AccountWithReconciled.vue"; const settings = useSettingsStore(); const ExpandMenu = computed(() => settings.Menu.Expand); @@ -18,17 +19,6 @@ const OnBudgetAccounts = computed(() => accountStore.OnBudgetAccounts); const OffBudgetAccounts = computed(() => accountStore.OffBudgetAccounts); const OnBudgetAccountsBalance = computed(() => accountStore.OnBudgetAccountsBalance); const OffBudgetAccountsBalance = computed(() => accountStore.OffBudgetAccountsBalance); - -function isRecentlyReconciled(account: Account) { - const now = new Date().getTime(); - const recently = 7 * 24 * 60 * 60 * 1000; - return new Date(now - recently).getTime() < account.LastReconciled.getTime(); -} - -function getAccountName(account: Account) { - const reconciledMarker = isRecentlyReconciled(account) ? "" : " *"; - return account.Name + reconciledMarker; -}