From eb461aa7fb8d668028e83618a9175a9bd527640f Mon Sep 17 00:00:00 2001
From: Tim Wundenberg
Date: Fri, 16 May 2025 22:26:41 +0200
Subject: [PATCH] feat(transaction): #80 calculate balances
---
handler/transaction.go | 38 +-
service/transaction.go | 116 ++++-
template/transaction/transaction.templ | 6 +-
test.log | 572 -------------------------
4 files changed, 121 insertions(+), 611 deletions(-)
delete mode 100644 test.log
diff --git a/handler/transaction.go b/handler/transaction.go
index 8bb27a0..53d07d8 100644
--- a/handler/transaction.go
+++ b/handler/transaction.go
@@ -3,7 +3,6 @@ package handler
import (
"net/http"
"spend-sparrow/handler/middleware"
- "spend-sparrow/log"
"spend-sparrow/service"
t "spend-sparrow/template/transaction"
"spend-sparrow/types"
@@ -43,24 +42,6 @@ func (h TransactionImpl) Handle(r *http.ServeMux) {
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)
@@ -192,6 +173,24 @@ func (h TransactionImpl) handleUpdateTransaction() http.HandlerFunc {
}
}
+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, please refresh", http.StatusOK)
+ }
+}
+
func (h TransactionImpl) handleDeleteTransaction() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := middleware.GetUser(r)
@@ -225,6 +224,5 @@ func (h TransactionImpl) getTransactionData(accounts []*types.Account, treasureC
treasureChestMap[treasureChest.Id] = root + treasureChest.Name
}
}
- log.Info("treasureChestMap: %v", treasureChestMap)
return accountMap, treasureChestMap
}
diff --git a/service/transaction.go b/service/transaction.go
index 69b60d7..c48fbdc 100644
--- a/service/transaction.go
+++ b/service/transaction.go
@@ -76,7 +76,18 @@ func (s TransactionImpl) Add(user *types.User, transactionInput types.Transactio
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)
+ err = db.TransformAndLogDbError("transaction Update", r, err)
+ if err != nil {
+ return nil, 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 Update", r, err)
if err != nil {
return nil, err
}
@@ -111,7 +122,17 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
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)
+ err = db.TransformAndLogDbError("transaction Update", r, err)
+ if err != nil {
+ return nil, 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 Update", r, err)
if err != nil {
return nil, err
}
@@ -127,7 +148,17 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
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)
+ err = db.TransformAndLogDbError("transaction Update", r, err)
+ if err != nil {
+ return nil, 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 Update", r, err)
if err != nil {
return nil, err
}
@@ -211,7 +242,16 @@ func (s TransactionImpl) Delete(user *types.User, id string) error {
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 {
+ if err != nil && err != db.ErrNotFound {
+ return err
+ }
+ r, err = s.db.Exec(`
+ UPDATE treasure_chest
+ SET current_balance = current_balance - (SELECT value FROM "transaction" WHERE id = ? AND user_id = ?)
+ WHERE id = (SELECT treasure_chest_id FROM "transaction" WHERE id = ? AND user_id = ?)
+ `, uuid, user.Id, uuid, user.Id)
+ err = db.TransformAndLogDbError("transaction Delete", r, err)
+ if err != nil && err != db.ErrNotFound {
return err
}
@@ -230,7 +270,14 @@ func (s TransactionImpl) RecalculateBalances(user *types.User) error {
return ErrUnauthorized
}
- r, err := s.db.Exec(`
+ tx, err := s.db.Beginx()
+ err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err)
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback()
+
+ r, err := tx.Exec(`
UPDATE account
SET current_balance = 0
WHERE user_id = ?`, user.Id)
@@ -239,7 +286,7 @@ func (s TransactionImpl) RecalculateBalances(user *types.User) error {
return err
}
- r, err = s.db.Exec(`
+ r, err = tx.Exec(`
UPDATE treasure_chest
SET current_balance = 0
WHERE user_id = ?`, user.Id)
@@ -248,7 +295,10 @@ func (s TransactionImpl) RecalculateBalances(user *types.User) error {
return err
}
- rows, err := s.db.Queryx(`SELECT account_id, treasure_chest_id, value FROM "transaction" WHERE user_id = ?`, user.Id)
+ rows, err := tx.Queryx(`
+ SELECT *
+ FROM "transaction"
+ WHERE user_id = ?`, user.Id)
err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err)
if err != nil && err != db.ErrNotFound {
return err
@@ -268,8 +318,22 @@ func (s TransactionImpl) RecalculateBalances(user *types.User) error {
return err
}
+ updateErrors(transaction)
+ r, err = tx.Exec(`
+ UPDATE "transaction"
+ SET error = ?
+ WHERE user_id = ?
+ AND id = ?`, transaction.Error, user.Id, transaction.Id)
+ err = db.TransformAndLogDbError("transaction RecalculateBalances", r, err)
+ if err != nil {
+ return err
+ }
+ if transaction.Error != nil {
+ log.Info("err: %s", *transaction.Error)
+ }
+
if transaction.AccountId != nil {
- r, err = s.db.Exec(`
+ r, err = tx.Exec(`
UPDATE account
SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id)
@@ -279,7 +343,7 @@ func (s TransactionImpl) RecalculateBalances(user *types.User) error {
}
}
if transaction.TreasureChestId != nil {
- r, err = s.db.Exec(`
+ r, err = tx.Exec(`
UPDATE treasure_chest
SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id)
@@ -290,6 +354,8 @@ func (s TransactionImpl) RecalculateBalances(user *types.User) error {
}
}
+ tx.Commit()
+
return nil
}
@@ -404,20 +470,34 @@ func (s TransactionImpl) validateAndEnrichTransaction(oldTransaction *types.Tran
UpdatedAt: updatedAt,
UpdatedBy: &updatedBy,
}
- error := getErrors(transaction)
- if error != "" {
- transaction.Error = &error
- }
+
+ updateErrors(&transaction)
+
return &transaction, nil
}
-func getErrors(transaction types.Transaction) string {
+func updateErrors(transaction *types.Transaction) {
+ error := ""
+
if transaction.Value < 0 {
- // panic("unimplemented")
+ if transaction.AccountId == nil {
+ error = "no account specified"
+ } else if transaction.TreasureChestId == nil {
+ error = "no treasure chest specified"
+ }
} else if transaction.Value > 0 {
- // panic("unimplemented")
+ if transaction.AccountId == nil && transaction.TreasureChestId == nil {
+ error = "either an account or a treasure chest needs to be specified"
+ } else if transaction.AccountId != nil && transaction.TreasureChestId != nil {
+ error = "positive amounts can only be applied to either an account or a treasure chest"
+ }
} else {
- return "\"value\" needs to be positive or negative"
+ error = "\"value\" needs to be specified"
+ }
+
+ if error == "" {
+ transaction.Error = nil
+ } else {
+ transaction.Error = &error
}
- return ""
}
diff --git a/template/transaction/transaction.templ b/template/transaction/transaction.templ
index 59965a5..1e93f54 100644
--- a/template/transaction/transaction.templ
+++ b/template/transaction/transaction.templ
@@ -174,7 +174,11 @@ templ TransactionItem(transaction *types.Transaction, accounts, treasureChests m
}
- { displayBalance(transaction.Value)+" €" }
+ if transaction.Value < 0 {
+ { displayBalance(transaction.Value)+" €" }
+ } else {
+ { displayBalance(transaction.Value)+" €" }
+ }