Add account reconcilation

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2024-02-01 12:44:56 +01:00
parent ded3444391
commit dae017f99e
Signed by: luzifer
SSH key fingerprint: SHA256:/xtE5lCgiRDQr8SLxHMS92ZBlACmATUmF1crK16Ks4E
5 changed files with 63 additions and 0 deletions

View file

@ -72,6 +72,14 @@
Add Transfer
</button>
<button
class="btn btn-success"
@click="markAccountReconciled"
>
<i class="fas fa-fw fa-square-check mr-1" />
Mark Reconciled
</button>
<button
class="btn btn-secondary"
:disabled="selectedTx.length !== 1"
@ -80,6 +88,7 @@
<i class="fas fa-fw fa-pencil mr-1" />
Edit Transaction
</button>
<button
type="button"
class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
@ -178,7 +187,14 @@
{{ formatNumber(tx.amount) }}
</td>
<td>
<span
v-if="tx.reconciled"
class="text-success"
>
<i class="fas fa-lock" />
</span>
<a
v-else
href="#"
:class="{'text-decoration-none':true, 'text-muted': !tx.cleared, 'text-success': tx.cleared}"
@click.prevent="markCleared(tx.id, !tx.cleared)"
@ -482,6 +498,13 @@ export default {
formatNumber,
markAccountReconciled() {
return fetch(`/api/accounts/${this.accountId}/reconcile`, {
method: 'PUT',
})
.then(() => this.fetchTransactions())
},
markCleared(txId, cleared) {
return fetch(`/api/transactions/${txId}?cleared=${cleared}`, {
method: 'PATCH',

View file

@ -102,6 +102,25 @@ func (a apiServer) handleListAccounts(w http.ResponseWriter, r *http.Request) {
a.jsonResponse(w, http.StatusOK, payload)
}
func (a apiServer) handleAccountReconcile(w http.ResponseWriter, r *http.Request) {
var (
acctID uuid.UUID
err error
)
if acctID, err = uuid.Parse(mux.Vars(r)["id"]); err != nil {
a.errorResponse(w, err, "parsing id", http.StatusBadRequest)
return
}
if err = a.dbc.MarkAccountReconciled(acctID); err != nil {
a.errorResponse(w, err, "marking reconciled", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (a apiServer) handleTransferMoney(w http.ResponseWriter, r *http.Request) {
var (
amount float64

View file

@ -38,6 +38,9 @@ func RegisterHandler(apiRouter *mux.Router, dbc *database.Client, logger *logrus
apiRouter.
HandleFunc("/accounts/{id}", as.handleUpdateAccount).
Methods(http.MethodPatch)
apiRouter.
HandleFunc("/accounts/{id}/reconcile", as.handleAccountReconcile).
Methods(http.MethodPut)
apiRouter.
HandleFunc("/accounts/{id}/transactions", as.handleListTransactionsByAccount).
Methods(http.MethodGet)

View file

@ -267,6 +267,23 @@ func (c *Client) ListTransactionsByAccount(acc uuid.UUID, since, until time.Time
return txs, nil
}
// MarkAccountReconciled marks all cleared transactions as reconciled.
// The account balance is NOT checked in this method.
func (c *Client) MarkAccountReconciled(acc uuid.UUID) (err error) {
if err = c.retryTx(func(db *gorm.DB) error {
return db.
Model(&Transaction{}).
Where("account = ?", acc).
Where("cleared = ?", true).
Update("reconciled", true).
Error
}); err != nil {
return fmt.Errorf("updating transactions: %w", err)
}
return nil
}
// TransferMoney creates new Transactions for the given account
// transfer. The account type of the from and to account must match
// for this to work.

View file

@ -39,6 +39,7 @@ type (
Account uuid.NullUUID `gorm:"type:uuid" json:"account"`
Category uuid.NullUUID `gorm:"type:uuid" json:"category"`
Cleared bool `json:"cleared"`
Reconciled bool `json:"reconciled"`
PairKey uuid.NullUUID `gorm:"type:uuid" json:"-"`
}