feat(transaction): #80 add errors to transactions
This commit was merged in pull request #84.
This commit is contained in:
@@ -3,12 +3,14 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"spend-sparrow/handler/middleware"
|
"spend-sparrow/handler/middleware"
|
||||||
|
"spend-sparrow/log"
|
||||||
"spend-sparrow/service"
|
"spend-sparrow/service"
|
||||||
t "spend-sparrow/template/transaction"
|
t "spend-sparrow/template/transaction"
|
||||||
"spend-sparrow/types"
|
"spend-sparrow/types"
|
||||||
"spend-sparrow/utils"
|
"spend-sparrow/utils"
|
||||||
|
|
||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Transaction interface {
|
type Transaction interface {
|
||||||
@@ -85,7 +87,9 @@ func (h TransactionImpl) handleTransactionPage() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
comp := t.Transaction(transactions, accounts, treasureChests)
|
accountMap, treasureChestMap := h.getTransactionData(accounts, treasureChests)
|
||||||
|
|
||||||
|
comp := t.Transaction(transactions, accountMap, treasureChestMap)
|
||||||
h.r.RenderLayout(r, w, comp, user)
|
h.r.RenderLayout(r, w, comp, user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +131,8 @@ func (h TransactionImpl) handleTransactionItemComp() http.HandlerFunc {
|
|||||||
if r.URL.Query().Get("edit") == "true" {
|
if r.URL.Query().Get("edit") == "true" {
|
||||||
comp = t.EditTransaction(transaction, accounts, treasureChests)
|
comp = t.EditTransaction(transaction, accounts, treasureChests)
|
||||||
} else {
|
} else {
|
||||||
comp = t.TransactionItem(transaction)
|
accountMap, treasureChestMap := h.getTransactionData(accounts, treasureChests)
|
||||||
|
comp = t.TransactionItem(transaction, accountMap, treasureChestMap)
|
||||||
}
|
}
|
||||||
h.r.Render(r, w, comp)
|
h.r.Render(r, w, comp)
|
||||||
}
|
}
|
||||||
@@ -169,7 +174,20 @@ func (h TransactionImpl) handleUpdateTransaction() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
comp := t.TransactionItem(transaction)
|
accounts, err := h.account.GetAll(user)
|
||||||
|
if err != nil {
|
||||||
|
handleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
treasureChests, err := h.treasureChest.GetAll(user)
|
||||||
|
if err != nil {
|
||||||
|
handleError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accountMap, treasureChestMap := h.getTransactionData(accounts, treasureChests)
|
||||||
|
comp := t.TransactionItem(transaction, accountMap, treasureChestMap)
|
||||||
h.r.Render(r, w, comp)
|
h.r.Render(r, w, comp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,3 +209,22 @@ func (h TransactionImpl) handleDeleteTransaction() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h TransactionImpl) getTransactionData(accounts []*types.Account, treasureChests []*types.TreasureChest) (map[uuid.UUID]string, map[uuid.UUID]string) {
|
||||||
|
accountMap := make(map[uuid.UUID]string, 0)
|
||||||
|
for _, account := range accounts {
|
||||||
|
accountMap[account.Id] = account.Name
|
||||||
|
}
|
||||||
|
treasureChestMap := make(map[uuid.UUID]string, 0)
|
||||||
|
root := ""
|
||||||
|
for _, treasureChest := range treasureChests {
|
||||||
|
if treasureChest.ParentId == uuid.Nil {
|
||||||
|
root = treasureChest.Name + " > "
|
||||||
|
treasureChestMap[treasureChest.Id] = treasureChest.Name
|
||||||
|
} else {
|
||||||
|
treasureChestMap[treasureChest.Id] = root + treasureChest.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info("treasureChestMap: %v", treasureChestMap)
|
||||||
|
return accountMap, treasureChestMap
|
||||||
|
}
|
||||||
|
|||||||
2
migration/005_transaction_add_error.up.sql
Normal file
2
migration/005_transaction_add_error.up.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
ALTER TABLE "transaction" ADD COLUMN error TEXT;
|
||||||
@@ -64,8 +64,8 @@ func (s TransactionImpl) Add(user *types.User, transactionInput types.Transactio
|
|||||||
}
|
}
|
||||||
|
|
||||||
r, err := s.db.NamedExec(`
|
r, err := s.db.NamedExec(`
|
||||||
INSERT INTO "transaction" (id, user_id, account_id, treasure_chest_id, value, timestamp, note, created_at, created_by)
|
INSERT INTO "transaction" (id, user_id, account_id, treasure_chest_id, value, timestamp, note, error, created_at, created_by)
|
||||||
VALUES (:id, :user_id, :account_id, :treasure_chest_id, :value, :timestamp, :note, :created_at, :created_by)`, transaction)
|
VALUES (:id, :user_id, :account_id, :treasure_chest_id, :value, :timestamp, :note, :error, :created_at, :created_by)`, transaction)
|
||||||
err = db.TransformAndLogDbError("transaction Insert", r, err)
|
err = db.TransformAndLogDbError("transaction Insert", r, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -141,6 +141,7 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
|
|||||||
value = :value,
|
value = :value,
|
||||||
timestamp = :timestamp,
|
timestamp = :timestamp,
|
||||||
note = :note,
|
note = :note,
|
||||||
|
error = :error,
|
||||||
updated_at = :updated_at,
|
updated_at = :updated_at,
|
||||||
updated_by = :updated_by
|
updated_by = :updated_by
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
@@ -184,7 +185,7 @@ func (s TransactionImpl) GetAll(user *types.User) ([]*types.Transaction, error)
|
|||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
transactions := make([]*types.Transaction, 0)
|
transactions := make([]*types.Transaction, 0)
|
||||||
err := s.db.Select(&transactions, `SELECT * FROM "transaction" WHERE user_id = ? ORDER BY timestamp`, user.Id)
|
err := s.db.Select(&transactions, `SELECT * FROM "transaction" WHERE user_id = ? ORDER BY timestamp DESC`, user.Id)
|
||||||
err = db.TransformAndLogDbError("transaction GetAll", nil, err)
|
err = db.TransformAndLogDbError("transaction GetAll", nil, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -223,119 +224,6 @@ func (s TransactionImpl) Delete(user *types.User, id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TransactionImpl) validateAndEnrichTransaction(transaction *types.Transaction, userId uuid.UUID, input types.TransactionInput) (*types.Transaction, error) {
|
|
||||||
|
|
||||||
var (
|
|
||||||
id uuid.UUID
|
|
||||||
accountUuid *uuid.UUID
|
|
||||||
treasureChestUuid *uuid.UUID
|
|
||||||
createdAt time.Time
|
|
||||||
createdBy uuid.UUID
|
|
||||||
updatedAt *time.Time
|
|
||||||
updatedBy uuid.UUID
|
|
||||||
|
|
||||||
err error
|
|
||||||
rowCount int
|
|
||||||
)
|
|
||||||
|
|
||||||
if transaction == nil {
|
|
||||||
id, err = s.random.UUID()
|
|
||||||
if err != nil {
|
|
||||||
return nil, types.ErrInternal
|
|
||||||
}
|
|
||||||
createdAt = s.clock.Now()
|
|
||||||
createdBy = userId
|
|
||||||
} else {
|
|
||||||
id = transaction.Id
|
|
||||||
createdAt = transaction.CreatedAt
|
|
||||||
createdBy = transaction.CreatedBy
|
|
||||||
time := s.clock.Now()
|
|
||||||
updatedAt = &time
|
|
||||||
updatedBy = userId
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.AccountId != "" {
|
|
||||||
temp, err := uuid.Parse(input.AccountId)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("transaction validate: %v", err)
|
|
||||||
return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest)
|
|
||||||
}
|
|
||||||
accountUuid = &temp
|
|
||||||
err = s.db.Get(&rowCount, `SELECT COUNT(*) FROM account WHERE id = ? AND user_id = ?`, accountUuid, userId)
|
|
||||||
err = db.TransformAndLogDbError("transaction validate", nil, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if rowCount == 0 {
|
|
||||||
log.Error("transaction validate: %v", err)
|
|
||||||
return nil, fmt.Errorf("account not found: %w", ErrBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.TreasureChestId != "" {
|
|
||||||
temp, err := uuid.Parse(input.TreasureChestId)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("transaction validate: %v", err)
|
|
||||||
return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest)
|
|
||||||
}
|
|
||||||
treasureChestUuid = &temp
|
|
||||||
err = s.db.Get(&rowCount, `SELECT COUNT(*) FROM treasure_chest WHERE id = ? AND user_id = ?`, treasureChestUuid, userId)
|
|
||||||
err = db.TransformAndLogDbError("transaction validate", nil, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if rowCount == 0 {
|
|
||||||
log.Error("transaction validate: %v", err)
|
|
||||||
return nil, fmt.Errorf("treasure chest not found: %w", ErrBadRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueFloat, err := strconv.ParseFloat(input.Value, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("transaction validate: %v", err)
|
|
||||||
return nil, fmt.Errorf("could not parse value: %w", ErrBadRequest)
|
|
||||||
}
|
|
||||||
valueInt := int64(valueFloat * 100)
|
|
||||||
|
|
||||||
timestampTime, err := time.Parse("2006-01-02T15:04", input.Timestamp)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("transaction validate: %v", err)
|
|
||||||
return nil, fmt.Errorf("could not parse timestamp: %w", ErrBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.TimezoneOffsetMinutes != "" {
|
|
||||||
timezoneOffsetMinutes, err := strconv.Atoi(input.TimezoneOffsetMinutes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse timezone offset: %w", ErrBadRequest)
|
|
||||||
}
|
|
||||||
timestampTime = timestampTime.Add(time.Duration(-1*timezoneOffsetMinutes) * time.Minute)
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.Note != "" {
|
|
||||||
err = validateString(input.Note, "note")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.Transaction{
|
|
||||||
Id: id,
|
|
||||||
UserId: userId,
|
|
||||||
|
|
||||||
AccountId: accountUuid,
|
|
||||||
TreasureChestId: treasureChestUuid,
|
|
||||||
Value: valueInt,
|
|
||||||
Timestamp: timestampTime,
|
|
||||||
Note: input.Note,
|
|
||||||
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
UpdatedBy: &updatedBy,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s TransactionImpl) RecalculateBalances(user *types.User) error {
|
func (s TransactionImpl) RecalculateBalances(user *types.User) error {
|
||||||
transactionMetric.WithLabelValues("recalculate").Inc()
|
transactionMetric.WithLabelValues("recalculate").Inc()
|
||||||
if user == nil {
|
if user == nil {
|
||||||
@@ -404,3 +292,132 @@ func (s TransactionImpl) RecalculateBalances(user *types.User) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s TransactionImpl) validateAndEnrichTransaction(oldTransaction *types.Transaction, userId uuid.UUID, input types.TransactionInput) (*types.Transaction, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
id uuid.UUID
|
||||||
|
accountUuid *uuid.UUID
|
||||||
|
treasureChestUuid *uuid.UUID
|
||||||
|
createdAt time.Time
|
||||||
|
createdBy uuid.UUID
|
||||||
|
updatedAt *time.Time
|
||||||
|
updatedBy uuid.UUID
|
||||||
|
|
||||||
|
err error
|
||||||
|
rowCount int
|
||||||
|
)
|
||||||
|
|
||||||
|
if oldTransaction == nil {
|
||||||
|
id, err = s.random.UUID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.ErrInternal
|
||||||
|
}
|
||||||
|
createdAt = s.clock.Now()
|
||||||
|
createdBy = userId
|
||||||
|
} else {
|
||||||
|
id = oldTransaction.Id
|
||||||
|
createdAt = oldTransaction.CreatedAt
|
||||||
|
createdBy = oldTransaction.CreatedBy
|
||||||
|
time := s.clock.Now()
|
||||||
|
updatedAt = &time
|
||||||
|
updatedBy = userId
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.AccountId != "" {
|
||||||
|
temp, err := uuid.Parse(input.AccountId)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("transaction validate: %v", err)
|
||||||
|
return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest)
|
||||||
|
}
|
||||||
|
accountUuid = &temp
|
||||||
|
err = s.db.Get(&rowCount, `SELECT COUNT(*) FROM account WHERE id = ? AND user_id = ?`, accountUuid, userId)
|
||||||
|
err = db.TransformAndLogDbError("transaction validate", nil, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rowCount == 0 {
|
||||||
|
log.Error("transaction validate: %v", err)
|
||||||
|
return nil, fmt.Errorf("account not found: %w", ErrBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.TreasureChestId != "" {
|
||||||
|
temp, err := uuid.Parse(input.TreasureChestId)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("transaction validate: %v", err)
|
||||||
|
return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest)
|
||||||
|
}
|
||||||
|
treasureChestUuid = &temp
|
||||||
|
err = s.db.Get(&rowCount, `SELECT COUNT(*) FROM treasure_chest WHERE id = ? AND user_id = ?`, treasureChestUuid, userId)
|
||||||
|
err = db.TransformAndLogDbError("transaction validate", nil, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rowCount == 0 {
|
||||||
|
log.Error("transaction validate: %v", err)
|
||||||
|
return nil, fmt.Errorf("treasure chest not found: %w", ErrBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
valueFloat, err := strconv.ParseFloat(input.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("transaction validate: %v", err)
|
||||||
|
return nil, fmt.Errorf("could not parse value: %w", ErrBadRequest)
|
||||||
|
}
|
||||||
|
valueInt := int64(valueFloat * 100)
|
||||||
|
|
||||||
|
timestampTime, err := time.Parse("2006-01-02T15:04", input.Timestamp)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("transaction validate: %v", err)
|
||||||
|
return nil, fmt.Errorf("could not parse timestamp: %w", ErrBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.TimezoneOffsetMinutes != "" {
|
||||||
|
timezoneOffsetMinutes, err := strconv.Atoi(input.TimezoneOffsetMinutes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse timezone offset: %w", ErrBadRequest)
|
||||||
|
}
|
||||||
|
timestampTime = timestampTime.Add(time.Duration(-1*timezoneOffsetMinutes) * time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.Note != "" {
|
||||||
|
err = validateString(input.Note, "note")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction := types.Transaction{
|
||||||
|
Id: id,
|
||||||
|
UserId: userId,
|
||||||
|
|
||||||
|
AccountId: accountUuid,
|
||||||
|
TreasureChestId: treasureChestUuid,
|
||||||
|
Value: valueInt,
|
||||||
|
Timestamp: timestampTime,
|
||||||
|
Note: input.Note,
|
||||||
|
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
CreatedBy: createdBy,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
UpdatedBy: &updatedBy,
|
||||||
|
}
|
||||||
|
error := getErrors(transaction)
|
||||||
|
if error != "" {
|
||||||
|
transaction.Error = &error
|
||||||
|
}
|
||||||
|
return &transaction, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getErrors(transaction types.Transaction) string {
|
||||||
|
if transaction.Value < 0 {
|
||||||
|
// panic("unimplemented")
|
||||||
|
} else if transaction.Value > 0 {
|
||||||
|
// panic("unimplemented")
|
||||||
|
} else {
|
||||||
|
return "\"value\" needs to be positive or negative"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,3 +35,15 @@ templ Cancel() {
|
|||||||
<path fill="currentColor" d="m654 501l346 346l-154 154l-346-346l-346 346L0 847l346-346L0 155L154 1l346 346L846 1l154 154z"></path>
|
<path fill="currentColor" d="m654 501l346 346l-154 154l-346-346l-346 346L0 847l346-346L0 155L154 1l346 346L846 1l154 154z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templ Info() {
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="h-6 w-6 text-blue-700">
|
||||||
|
<mask id="ipSInfo0">
|
||||||
|
<g fill="none">
|
||||||
|
<path fill="#fff" stroke="#fff" stroke-linejoin="round" stroke-width="4" d="M24 44a19.937 19.937 0 0 0 14.142-5.858A19.937 19.937 0 0 0 44 24a19.938 19.938 0 0 0-5.858-14.142A19.937 19.937 0 0 0 24 4A19.938 19.938 0 0 0 9.858 9.858A19.938 19.938 0 0 0 4 24a19.937 19.937 0 0 0 5.858 14.142A19.938 19.938 0 0 0 24 44Z"></path>
|
||||||
|
<path fill="#000" fill-rule="evenodd" d="M24 11a2.5 2.5 0 1 1 0 5a2.5 2.5 0 0 1 0-5Z" clip-rule="evenodd"></path><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M24.5 34V20h-2M21 34h7"></path>
|
||||||
|
</g>
|
||||||
|
</mask>
|
||||||
|
<path fill="currentColor" d="M0 0h48v48H0z" mask="url(#ipSInfo0)"></path>
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import "spend-sparrow/template/svg"
|
|||||||
import "spend-sparrow/types"
|
import "spend-sparrow/types"
|
||||||
import "github.com/google/uuid"
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
templ Transaction(transactions []*types.Transaction, accounts []*types.Account, treasureChests []*types.TreasureChest) {
|
templ Transaction(transactions []*types.Transaction, accounts, treasureChests map[uuid.UUID]string) {
|
||||||
|
{{ }}
|
||||||
<div class="max-w-6xl mt-10 mx-auto">
|
<div class="max-w-6xl mt-10 mx-auto">
|
||||||
<button
|
<button
|
||||||
hx-get="/transaction/new"
|
hx-get="/transaction/new"
|
||||||
@@ -17,9 +18,9 @@ templ Transaction(transactions []*types.Transaction, accounts []*types.Account,
|
|||||||
@svg.Plus()
|
@svg.Plus()
|
||||||
<p>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">
|
||||||
for _, transaction := range transactions {
|
for _, transaction := range transactions {
|
||||||
@TransactionItem(transaction)
|
@TransactionItem(transaction, accounts, treasureChests)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -137,10 +138,42 @@ templ EditTransaction(transaction *types.Transaction, accounts []*types.Account,
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ TransactionItem(transaction *types.Transaction) {
|
templ TransactionItem(transaction *types.Transaction, accounts, treasureChests map[uuid.UUID]string) {
|
||||||
<div id="transaction" 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">
|
background := "bg-gray-50"
|
||||||
|
if transaction.Error != nil {
|
||||||
|
background = "bg-yellow-50"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
<div
|
||||||
|
id="transaction"
|
||||||
|
class={ "mt-4 border-1 grid grid-cols-[auto_auto_1fr_1fr_auto_auto_auto] gap-4 items-center text-xl border-gray-300 w-full p-4 rounded-lg " + background }
|
||||||
|
if transaction.Error != nil {
|
||||||
|
title={ *transaction.Error }
|
||||||
|
}
|
||||||
|
>
|
||||||
<p class="mr-auto datetime">{ transaction.Timestamp.String() }</p>
|
<p class="mr-auto datetime">{ transaction.Timestamp.String() }</p>
|
||||||
|
<div class="w-6">
|
||||||
|
if transaction.Error != nil {
|
||||||
|
@svg.Info()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
if transaction.AccountId != nil {
|
||||||
|
{ accounts[*transaction.AccountId] }
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
if transaction.TreasureChestId != nil {
|
||||||
|
{ treasureChests[*transaction.TreasureChestId] }
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<p class="mr-20 text-green-700">{ displayBalance(transaction.Value)+" €" }</p>
|
<p class="mr-20 text-green-700">{ displayBalance(transaction.Value)+" €" }</p>
|
||||||
<button
|
<button
|
||||||
hx-get={ "/transaction/" + transaction.Id.String() + "?edit=true" }
|
hx-get={ "/transaction/" + transaction.Id.String() + "?edit=true" }
|
||||||
@@ -165,7 +198,6 @@ templ TransactionItem(transaction *types.Transaction) {
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayBalance(balance int64) string {
|
func displayBalance(balance int64) string {
|
||||||
@@ -173,3 +205,6 @@ func displayBalance(balance int64) string {
|
|||||||
euros := float64(balance) / 100
|
euros := float64(balance) / 100
|
||||||
return fmt.Sprintf("%.2f", euros)
|
return fmt.Sprintf("%.2f", euros)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateReferences() {
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ type Transaction struct {
|
|||||||
// The value of the transacion. Negative for outgoing and positive for incoming transactions.
|
// The value of the transacion. Negative for outgoing and positive for incoming transactions.
|
||||||
Value int64
|
Value int64
|
||||||
|
|
||||||
|
// If an error is present, then the transaction is not valid and should not be used for calculations.
|
||||||
|
Error *string
|
||||||
CreatedAt time.Time `db:"created_at"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
CreatedBy uuid.UUID `db:"created_by"`
|
CreatedBy uuid.UUID `db:"created_by"`
|
||||||
UpdatedAt *time.Time `db:"updated_at"`
|
UpdatedAt *time.Time `db:"updated_at"`
|
||||||
|
|||||||
Reference in New Issue
Block a user