feat(transaction): #80 calculate account balances
Some checks failed
Build Docker Image / Build-Docker-Image (push) Has been cancelled
Some checks failed
Build Docker Image / Build-Docker-Image (push) Has been cancelled
This commit is contained in:
@@ -37,9 +37,28 @@ func (h TransactionImpl) Handle(r *http.ServeMux) {
|
|||||||
r.Handle("GET /transaction", h.handleTransactionPage())
|
r.Handle("GET /transaction", h.handleTransactionPage())
|
||||||
r.Handle("GET /transaction/{id}", h.handleTransactionItemComp())
|
r.Handle("GET /transaction/{id}", h.handleTransactionItemComp())
|
||||||
r.Handle("POST /transaction/{id}", h.handleUpdateTransaction())
|
r.Handle("POST /transaction/{id}", h.handleUpdateTransaction())
|
||||||
|
r.Handle("POST /transaction/recalculate", h.handleRecalculate())
|
||||||
r.Handle("DELETE /transaction/{id}", h.handleDeleteTransaction())
|
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 {
|
func (h TransactionImpl) handleTransactionPage() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
user := middleware.GetUser(r)
|
user := middleware.GetUser(r)
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ type Transaction interface {
|
|||||||
Get(user *types.User, id string) (*types.Transaction, error)
|
Get(user *types.User, id string) (*types.Transaction, error)
|
||||||
GetAll(user *types.User) ([]*types.Transaction, error)
|
GetAll(user *types.User) ([]*types.Transaction, error)
|
||||||
Delete(user *types.User, id string) error
|
Delete(user *types.User, id string) error
|
||||||
|
|
||||||
|
RecalculateBalances(user *types.User) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransactionImpl struct {
|
type TransactionImpl struct {
|
||||||
@@ -69,6 +71,17 @@ func (s TransactionImpl) Add(user *types.User, transactionInput types.Transactio
|
|||||||
return nil, err
|
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
|
return transaction, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,11 +106,33 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
|
|||||||
return nil, types.ErrInternal
|
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)
|
transaction, err = s.validateAndEnrichTransaction(transaction, user.Id, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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(`
|
r, err := s.db.NamedExec(`
|
||||||
UPDATE "transaction"
|
UPDATE "transaction"
|
||||||
SET
|
SET
|
||||||
@@ -169,7 +204,17 @@ func (s TransactionImpl) Delete(user *types.User, id string) error {
|
|||||||
return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
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)
|
err = db.TransformAndLogDbError("transaction Delete", r, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -290,3 +335,67 @@ func (s TransactionImpl) validateAndEnrichTransaction(transaction *types.Transac
|
|||||||
UpdatedBy: &updatedBy,
|
UpdatedBy: &updatedBy,
|
||||||
}, nil
|
}, 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 rows.Close()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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"
|
class="ml-auto button button-primary px-2 flex-1 flex items-center gap-2 justify-center"
|
||||||
>
|
>
|
||||||
@svg.Plus()
|
@svg.Plus()
|
||||||
<p class="">New Account</p>
|
<p>New Account</p>
|
||||||
</button>
|
</button>
|
||||||
<div id="account-items" class="my-6 flex flex-col items-center">
|
<div id="account-items" class="my-6 flex flex-col items-center">
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
@@ -80,7 +80,11 @@ templ AccountItem(account *types.Account) {
|
|||||||
<div id="account" class="border-1 border-gray-300 w-full my-4 p-4 bg-gray-50 rounded-lg">
|
<div id="account" class="border-1 border-gray-300 w-full my-4 p-4 bg-gray-50 rounded-lg">
|
||||||
<div class="text-xl flex justify-end gap-4">
|
<div class="text-xl flex justify-end gap-4">
|
||||||
<p class="mr-auto">{ account.Name }</p>
|
<p class="mr-auto">{ account.Name }</p>
|
||||||
<p class="mr-20 text-green-700">{ displayBalance(account.CurrentBalance) }</p>
|
if account.CurrentBalance < 0 {
|
||||||
|
<p class="mr-20 text-red-700">{ displayBalance(account.CurrentBalance) }</p>
|
||||||
|
} else {
|
||||||
|
<p class="mr-20 text-green-700">{ displayBalance(account.CurrentBalance) }</p>
|
||||||
|
}
|
||||||
<button
|
<button
|
||||||
hx-get={ "/account/" + account.Id.String() + "?edit=true" }
|
hx-get={ "/account/" + account.Id.String() + "?edit=true" }
|
||||||
hx-target="closest #account"
|
hx-target="closest #account"
|
||||||
|
|||||||
@@ -11,16 +11,24 @@ templ UserComp(user string) {
|
|||||||
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"></path>
|
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="absolute hidden group-has-hover:block w-full">
|
<div class="absolute hidden group-has-hover:block w-full z-2">
|
||||||
<ul class="w-fit float-right mr-4 p-3 border-2 border-gray-200 rounded-lg bg-white shadow-lg">
|
<ul class="w-fit float-right mr-4 p-3 border-2 border-gray-200 rounded-lg bg-white shadow-lg">
|
||||||
<li class="mb-1">
|
<li class="mb-1">
|
||||||
<a class="button w-full px-1 button-neglect block" hx-post="/api/auth/signout" hx-target="#user-info">Sign Out</a>
|
<a class="button w-full px-1 button-neglect block" hx-post="/api/auth/signout" hx-target="#user-info">Sign Out</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="mb-1">
|
<li class="mb-4">
|
||||||
<a class="button w-full px-1 button-neglect block" href="/auth/change-password">Change Password</a>
|
<a class="button w-full px-1 button-neglect block" href="/auth/change-password">Change Password</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="mb-4">
|
||||||
|
<button
|
||||||
|
hx-post="/transaction/recalculate"
|
||||||
|
hx-swap="none"
|
||||||
|
type="button"
|
||||||
|
class="button text-left w-full px-1 button-neglect block mt-4"
|
||||||
|
>Recalculate</button>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button w-full px-1 button-neglect text-gray-400 mt-4 block" href="/auth/delete-account" class="">
|
<a class="button w-full px-1 button-neglect text-gray-400 block" href="/auth/delete-account">
|
||||||
Delete
|
Delete
|
||||||
Account
|
Account
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package template
|
package template
|
||||||
|
|
||||||
templ Dashboard() {
|
templ Dashboard() {
|
||||||
<div class="">
|
<div>
|
||||||
<h1 class="text-8xl">
|
<h1 class="text-8xl">
|
||||||
Dashboard
|
Dashboard
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ templ Transaction(transactions []*types.Transaction, accounts []*types.Account,
|
|||||||
class="ml-auto button button-primary px-2 flex-1 flex items-center gap-2 justify-center"
|
class="ml-auto button button-primary px-2 flex-1 flex items-center gap-2 justify-center"
|
||||||
>
|
>
|
||||||
@svg.Plus()
|
@svg.Plus()
|
||||||
<p class="">New Transaction</p>
|
<p>New Transaction</p>
|
||||||
</button>
|
</button>
|
||||||
<div id="transaction-items" class="my-6 flex flex-col items-center">
|
<div id="transaction-items" class="my-6 flex flex-col items-center">
|
||||||
for _, transaction := range transactions {
|
for _, transaction := range transactions {
|
||||||
|
|||||||
@@ -104,7 +104,11 @@ templ TreasureChestItem(treasureChest *types.TreasureChest) {
|
|||||||
<div id="treasurechest" class={ "border-1 border-gray-300 w-full p-4 bg-gray-50 rounded-lg" + identation }>
|
<div id="treasurechest" class={ "border-1 border-gray-300 w-full p-4 bg-gray-50 rounded-lg" + identation }>
|
||||||
<div class="text-xl flex justify-end items-center gap-4">
|
<div class="text-xl flex justify-end items-center gap-4">
|
||||||
<p class="mr-auto">{ treasureChest.Name }</p>
|
<p class="mr-auto">{ treasureChest.Name }</p>
|
||||||
<p class="mr-20 text-green-700">{ displayBalance(treasureChest.CurrentBalance) }</p>
|
if treasureChest.CurrentBalance < 0 {
|
||||||
|
<p class="mr-20 text-red-700">{ displayBalance(treasureChest.CurrentBalance) }</p>
|
||||||
|
} else {
|
||||||
|
<p class="mr-20 text-green-700">{ displayBalance(treasureChest.CurrentBalance) }</p>
|
||||||
|
}
|
||||||
<button
|
<button
|
||||||
hx-get={ "/treasurechest/" + treasureChest.Id.String() + "?edit=true" }
|
hx-get={ "/treasurechest/" + treasureChest.Id.String() + "?edit=true" }
|
||||||
hx-target="closest #treasurechest"
|
hx-target="closest #treasurechest"
|
||||||
|
|||||||
Reference in New Issue
Block a user