wip: recurring transactions
Some checks failed
Build Docker Image / Build-Docker-Image (push) Failing after 56s

This commit is contained in:
2025-05-23 09:29:03 +02:00
parent 95be988862
commit 96b1ec2264
4 changed files with 110 additions and 77 deletions

View File

@@ -82,7 +82,6 @@ func (h TransactionRecurringImpl) handleUpdateTransactionRecurring() http.Handle
input := types.TransactionRecurringInput{ input := types.TransactionRecurringInput{
Id: r.PathValue("id"), Id: r.PathValue("id"),
IntervalMonths: r.FormValue("interval-months"), IntervalMonths: r.FormValue("interval-months"),
LastExecution: r.FormValue("last-execution"),
Active: r.FormValue("active"), Active: r.FormValue("active"),
Party: r.FormValue("party"), Party: r.FormValue("party"),
Description: r.FormValue("description"), Description: r.FormValue("description"),

View File

@@ -127,7 +127,6 @@ func (s TransactionRecurringImpl) Update(user *types.User, input types.Transacti
UPDATE transaction_recurring UPDATE transaction_recurring
SET SET
interval_months = :interval_months, interval_months = :interval_months,
last_execution = :last_execution,
active = :active, active = :active,
party = :party, party = :party,
description = :description, description = :description,
@@ -340,7 +339,6 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
updatedAt *time.Time updatedAt *time.Time
updatedBy uuid.UUID updatedBy uuid.UUID
intervalMonths int64 intervalMonths int64
active bool
err error err error
rowCount int rowCount int
@@ -362,6 +360,8 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
updatedBy = userId updatedBy = userId
} }
hasAccount := false
hasTreasureChest := false
if input.AccountId != "" { if input.AccountId != "" {
temp, err := uuid.Parse(input.AccountId) temp, err := uuid.Parse(input.AccountId)
if err != nil { if err != nil {
@@ -379,6 +379,7 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
return nil, fmt.Errorf("account not found: %w", ErrBadRequest) return nil, fmt.Errorf("account not found: %w", ErrBadRequest)
} }
hasAccount = true
} }
if input.TreasureChestId != "" { if input.TreasureChestId != "" {
@@ -400,6 +401,16 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
if treasureChest.ParentId == nil { if treasureChest.ParentId == nil {
return nil, fmt.Errorf("treasure chest is a group: %w", ErrBadRequest) return nil, fmt.Errorf("treasure chest is a group: %w", ErrBadRequest)
} }
hasTreasureChest = true
}
if !hasAccount && !hasTreasureChest {
log.Error("transactionRecurring validate: %v", err)
return nil, fmt.Errorf("either account or treasure chest is required: %w", ErrBadRequest)
}
if hasAccount && hasTreasureChest {
log.Error("transactionRecurring validate: %v", err)
return nil, fmt.Errorf("either account or treasure chest is required, not both: %w", ErrBadRequest)
} }
valueFloat, err := strconv.ParseFloat(input.Value, 64) valueFloat, err := strconv.ParseFloat(input.Value, 64)
@@ -430,10 +441,9 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
log.Error("transactionRecurring validate: %v", err) log.Error("transactionRecurring validate: %v", err)
return nil, fmt.Errorf("intervalMonths needs to be greater than 0: %w", ErrBadRequest) return nil, fmt.Errorf("intervalMonths needs to be greater than 0: %w", ErrBadRequest)
} }
active, err = strconv.ParseBool(input.Active) active := false
if err != nil { if input.Active == "on" {
log.Error("transactionRecurring validate: %v", err) active = true
return nil, fmt.Errorf("could not parse active: %w", ErrBadRequest)
} }
transactionRecurring := types.TransactionRecurring{ transactionRecurring := types.TransactionRecurring{

View File

@@ -5,58 +5,76 @@ import "spend-sparrow/template/svg"
import "spend-sparrow/types" import "spend-sparrow/types"
templ TransactionRecurringItems(transactionsRecurring []*types.TransactionRecurring) { templ TransactionRecurringItems(transactionsRecurring []*types.TransactionRecurring) {
<div id="transaction-recurring-items" class="my-6"> <table id="transaction-recurring-items" class="gap-2 w-full my-6 border-collapse border-spacing-20">
for _, transaction := range transactionsRecurring { <thead>
@TransactionRecurringItem(transaction) <tr>
} <th class="text-left text-sm text-gray-500">Party</th>
</div> <th class="text-left text-sm text-gray-500">Description</th>
<th class="text-right text-sm text-gray-500">Value (€)</th>
<th class="text-right text-sm text-gray-500">Actions</th>
</tr>
</thead>
<tbody>
for _, transaction := range transactionsRecurring {
@TransactionRecurringItem(transaction)
}
</tbody>
</table>
} }
templ TransactionRecurringItem(transactionRecurring *types.TransactionRecurring) { templ TransactionRecurringItem(transactionRecurring *types.TransactionRecurring) {
<div id={ "transaction-recurring" + transactionRecurring.Id.String() } class=""> <tr id={ "transaction-recurring" + transactionRecurring.Id.String() } class="mx-20">
<p class="text-sm text-gray-500"> <td>
if transactionRecurring.Party != "" { <p class="text-sm text-gray-500">
{ transactionRecurring.Party } if transactionRecurring.Party != "" {
{ transactionRecurring.Party }
} else {
&nbsp;
}
</p>
</td>
<td>
<p class="text-sm text-gray-500">
if transactionRecurring.Description != "" {
{ transactionRecurring.Description }
} else {
&nbsp;
}
</p>
</td>
<td>
if transactionRecurring.Value < 0 {
<p class="mr-8 min-w-22 text-right text-red-700">{ displayBalance(transactionRecurring.Value)+" €" }</p>
} else { } else {
&nbsp; <p class="mr-8 w-22 text-right text-green-700">{ displayBalance(transactionRecurring.Value)+" €" }</p>
} }
</p> </td>
<p class="text-sm text-gray-500"> <td class="flex gap-2">
if transactionRecurring.Description != "" { <button
{ transactionRecurring.Description } hx-get={ "/transaction-recurring/" + transactionRecurring.Id.String() + "?edit=true" }
} else { hx-target="closest #transaction"
&nbsp; hx-swap="outerHTML"
} class="button button-neglect px-1 flex items-center gap-2"
</p> >
if transactionRecurring.Value < 0 { @svg.Edit()
<p class="mr-8 min-w-22 text-right text-red-700">{ displayBalance(transactionRecurring.Value)+" €" }</p> <span>
} else { Edit
<p class="mr-8 w-22 text-right text-green-700">{ displayBalance(transactionRecurring.Value)+" €" }</p> </span>
} </button>
<button <button
hx-get={ "/transaction-recurring/" + transactionRecurring.Id.String() + "?edit=true" } hx-delete={ "/transaction-recurring/" + transactionRecurring.Id.String() }
hx-target="closest #transaction" hx-target="closest #transaction"
hx-swap="outerHTML" hx-swap="outerHTML"
class="button button-neglect px-1 flex items-center gap-2" hx-confirm="Are you sure you want to delete this transaction?"
> class="button button-neglect px-1 flex items-center gap-2"
@svg.Edit() >
<span> @svg.Delete()
Edit <span>
</span> Delete
</button> </span>
<button </button>
hx-delete={ "/transaction-recurring/" + transactionRecurring.Id.String() } </td>
hx-target="closest #transaction" </tr>
hx-swap="outerHTML"
hx-confirm="Are you sure you want to delete this transaction?"
class="button button-neglect px-1 flex items-center gap-2"
>
@svg.Delete()
<span>
Delete
</span>
</button>
</div>
} }
templ EditTransactionRecurring(transactionRecurring *types.TransactionRecurring, accountId, treasureChestId string) { templ EditTransactionRecurring(transactionRecurring *types.TransactionRecurring, accountId, treasureChestId string) {
@@ -68,13 +86,13 @@ templ EditTransactionRecurring(transactionRecurring *types.TransactionRecurring,
party := "" party := ""
description := "" description := ""
value := "0.00" value := "0.00"
intervalMonth := "1" intervalMonths := "1"
active := false active := true
if transactionRecurring == nil { if transactionRecurring == nil {
id = "new" id = "new"
cancelUrl = "/empty" cancelUrl = "/empty"
} else { } else {
intervalMonth = fmt.Sprintf("%d", transactionRecurring.IntervalMonths) intervalMonths = fmt.Sprintf("%d", transactionRecurring.IntervalMonths)
active = transactionRecurring.Active active = transactionRecurring.Active
party = transactionRecurring.Party party = transactionRecurring.Party
description = transactionRecurring.Description description = transactionRecurring.Description
@@ -97,16 +115,16 @@ templ EditTransactionRecurring(transactionRecurring *types.TransactionRecurring,
name="active" name="active"
id="active" id="active"
type="checkbox" type="checkbox"
value={ active } checked?={ active }
class="bg-white input" class="bg-white input"
/> />
<label for="active" class="select-none text-sm text-gray-800">Active</label> <label for="active" class="select-none text-sm text-gray-800">Active</label>
</div> </div>
<label for="interval-month" class="text-sm text-gray-500">Interval Month</label> <label for="interval-months" class="text-sm text-gray-500">Interval Months</label>
<input <input
name="interval-month" name="interval-months"
type="number" type="number"
value={ intervalMonth } value={ intervalMonths }
class="bg-white input" class="bg-white input"
/> />
<label for="party" class="text-sm text-gray-500">Party</label> <label for="party" class="text-sm text-gray-500">Party</label>
@@ -131,8 +149,12 @@ templ EditTransactionRecurring(transactionRecurring *types.TransactionRecurring,
value={ value } value={ value }
class="bg-white input" class="bg-white input"
/> />
<!-- <label for="account-id" class="text-sm text-gray-500">Account</label> --> if accountId != "" {
<!-- <label for="treasure-chest-id" class="text-sm text-gray-500">Treasure Chest</label> --> <input type="text" name="account-id" class="hidden text-sm text-gray-500" value={ accountId }/>
}
if treasureChestId != "" {
<input type="text" name="treasure-chest-id" class="hidden text-sm text-gray-500" value={ treasureChestId }/>
}
</div> </div>
<button type="submit" class="button button-neglect px-1 flex items-center gap-2"> <button type="submit" class="button button-neglect px-1 flex items-center gap-2">
@svg.Save() @svg.Save()

View File

@@ -95,20 +95,22 @@ templ EditTreasureChest(treasureChest *types.TreasureChest, parents []*types.Tre
</span> </span>
</button> </button>
</form> </form>
<div class="m-10 border-b-gray-400 border-b-1"></div> if id != "new" {
<div class="flex"> <div class="m-10 border-b-gray-400 border-b-1"></div>
<h3 class="text-sm text-gray-500">Monthly Transactions</h3> <div class="flex">
<button <h3 class="text-sm text-gray-500">Monthly Transactions</h3>
hx-get="/transaction-recurring/new" <button
hx-target="#transaction-recurring-items" hx-get={ "/transaction-recurring/new?treasure-chest-id=" + id }
hx-swap="afterbegin" hx-target="#transaction-recurring-items"
class="button button-primary ml-auto px-2 flex items-center gap-2" hx-swap="afterbegin"
> class="button button-primary ml-auto px-2 flex items-center gap-2"
@svg.Plus() >
<p>New Monthly Transaction</p> @svg.Plus()
</button> <p>New Monthly Transaction</p>
</div> </button>
@transactionsRecurring </div>
@transactionsRecurring
}
</div> </div>
} }