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

View file

@ -102,6 +102,25 @@ func (a apiServer) handleListAccounts(w http.ResponseWriter, r *http.Request) {
a.jsonResponse(w, http.StatusOK, payload) 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) { func (a apiServer) handleTransferMoney(w http.ResponseWriter, r *http.Request) {
var ( var (
amount float64 amount float64

View file

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

View file

@ -267,6 +267,23 @@ func (c *Client) ListTransactionsByAccount(acc uuid.UUID, since, until time.Time
return txs, nil 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 // TransferMoney creates new Transactions for the given account
// transfer. The account type of the from and to account must match // transfer. The account type of the from and to account must match
// for this to work. // for this to work.

View file

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