From af9b785985303e5329c7bbe71ab54edda32b580f Mon Sep 17 00:00:00 2001 From: Tim Wundenberg Date: Fri, 16 May 2025 15:45:52 +0200 Subject: [PATCH] feat(transaction): #80 calculate account balances --- handler/transaction.go | 19 ++++ service/transaction.go | 116 +++++++++++++++++++- template/account/account.templ | 8 +- template/auth/user.templ | 14 ++- template/dashboard.templ | 2 +- template/transaction/transaction.templ | 2 +- template/treasurechest/treasure_chest.templ | 6 +- 7 files changed, 158 insertions(+), 9 deletions(-) diff --git a/handler/transaction.go b/handler/transaction.go index 089b2a2..f0714f8 100644 --- a/handler/transaction.go +++ b/handler/transaction.go @@ -37,9 +37,28 @@ func (h TransactionImpl) Handle(r *http.ServeMux) { r.Handle("GET /transaction", h.handleTransactionPage()) r.Handle("GET /transaction/{id}", h.handleTransactionItemComp()) r.Handle("POST /transaction/{id}", h.handleUpdateTransaction()) + r.Handle("POST /transaction/recalculate", h.handleRecalculate()) r.Handle("DELETE /transaction/{id}", h.handleDeleteTransaction()) } +func (h TransactionImpl) handleRecalculate() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user := middleware.GetUser(r) + if user == nil { + utils.DoRedirect(w, r, "/auth/signin") + return + } + + err := h.s.RecalculateBalances(user) + if err != nil { + handleError(w, r, err) + return + } + + utils.TriggerToastWithStatus(w, r, "success", "Balances recalculated", http.StatusOK) + } +} + func (h TransactionImpl) handleTransactionPage() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user := middleware.GetUser(r) diff --git a/service/transaction.go b/service/transaction.go index 5baeb23..3a4b5de 100644 --- a/service/transaction.go +++ b/service/transaction.go @@ -31,6 +31,8 @@ type Transaction interface { Get(user *types.User, id string) (*types.Transaction, error) GetAll(user *types.User) ([]*types.Transaction, error) Delete(user *types.User, id string) error + + RecalculateBalances(user *types.User) error } type TransactionImpl struct { @@ -69,6 +71,17 @@ func (s TransactionImpl) Add(user *types.User, transactionInput types.Transactio return nil, err } + if transaction.AccountId != nil { + r, err = s.db.Exec(` + UPDATE account + SET current_balance = current_balance + ? + WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) + err = db.TransformAndLogDbError("transaction UpdateAccount", r, err) + if err != nil { + return nil, err + } + } + return transaction, nil } @@ -93,11 +106,33 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput) return nil, types.ErrInternal } + if transaction.AccountId != nil { + r, err := s.db.Exec(` + UPDATE account + SET current_balance = current_balance - ? + WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) + err = db.TransformAndLogDbError("transaction UpdateAccount", r, err) + if err != nil { + return nil, err + } + } + transaction, err = s.validateAndEnrichTransaction(transaction, user.Id, input) if err != nil { return nil, err } + if transaction.AccountId != nil { + r, err := s.db.Exec(` + UPDATE account + SET current_balance = current_balance + ? + WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) + err = db.TransformAndLogDbError("transaction UpdateAccount", r, err) + if err != nil { + return nil, err + } + } + r, err := s.db.NamedExec(` UPDATE "transaction" SET @@ -169,7 +204,17 @@ func (s TransactionImpl) Delete(user *types.User, id string) error { return fmt.Errorf("could not parse Id: %w", ErrBadRequest) } - r, err := s.db.Exec("DELETE FROM \"transaction\" WHERE id = ? AND user_id = ?", uuid, user.Id) + r, err := s.db.Exec(` + UPDATE account + SET current_balance = current_balance - (SELECT value FROM "transaction" WHERE id = ? AND user_id = ?) + WHERE id = (SELECT account_id FROM "transaction" WHERE id = ? AND user_id = ?) + `, uuid, user.Id, uuid, user.Id) + err = db.TransformAndLogDbError("transaction Delete", r, err) + if err != nil { + return err + } + + r, err = s.db.Exec("DELETE FROM \"transaction\" WHERE id = ? AND user_id = ?", uuid, user.Id) err = db.TransformAndLogDbError("transaction Delete", r, err) if err != nil { return err @@ -290,3 +335,72 @@ func (s TransactionImpl) validateAndEnrichTransaction(transaction *types.Transac UpdatedBy: &updatedBy, }, nil } + +func (s TransactionImpl) RecalculateBalances(user *types.User) error { + transactionMetric.WithLabelValues("recalculate").Inc() + if user == nil { + return ErrUnauthorized + } + + r, err := s.db.Exec(` + UPDATE account + SET current_balance = 0 + WHERE user_id = ?`, user.Id) + err = db.TransformAndLogDbError("transaction RecalculateBalances", r, err) + if err != nil && err != db.ErrNotFound { + return err + } + + r, err = s.db.Exec(` + UPDATE treasure_chest + SET current_balance = 0 + WHERE user_id = ?`, user.Id) + err = db.TransformAndLogDbError("transaction RecalculateBalances", r, err) + if err != nil && err != db.ErrNotFound { + return err + } + + rows, err := s.db.Queryx(`SELECT account_id, treasure_chest_id, value FROM "transaction" WHERE user_id = ?`, user.Id) + err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err) + if err != nil && err != db.ErrNotFound { + return err + } + defer func() { + err := rows.Close() + if err != nil { + log.Error("transaction RecalculateBalances: %v", err) + } + }() + + transaction := &types.Transaction{} + for rows.Next() { + err = rows.StructScan(transaction) + err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err) + if err != nil { + return err + } + + if transaction.AccountId != nil { + r, err = s.db.Exec(` + UPDATE account + SET current_balance = current_balance + ? + WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) + err = db.TransformAndLogDbError("transaction RecalculateBalances", r, err) + if err != nil { + return err + } + } + if transaction.TreasureChestId != nil { + r, err = s.db.Exec(` + UPDATE treasure_chest + SET current_balance = current_balance + ? + WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id) + err = db.TransformAndLogDbError("transaction RecalculateBalances", r, err) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/template/account/account.templ b/template/account/account.templ index 5d9c595..2f6a7d1 100644 --- a/template/account/account.templ +++ b/template/account/account.templ @@ -13,7 +13,7 @@ templ Account(accounts []*types.Account) { class="ml-auto button button-primary px-2 flex-1 flex items-center gap-2 justify-center" > @svg.Plus() -

New Account

+

New Account

for _, account := range accounts { @@ -80,7 +80,11 @@ templ AccountItem(account *types.Account) {

{ account.Name }

-

{ displayBalance(account.CurrentBalance) }

+ if account.CurrentBalance < 0 { +

{ displayBalance(account.CurrentBalance) }

+ } else { +

{ displayBalance(account.CurrentBalance) }

+ } -