diff --git a/frontend/components/accountOverview.vue b/frontend/components/accountOverview.vue
index e930f62..13cc29b 100644
--- a/frontend/components/accountOverview.vue
+++ b/frontend/components/accountOverview.vue
@@ -8,7 +8,7 @@
/>
-
{{ accountIdToName[accountId] }}
+
{{ accountIdToName[accountId] }}
@@ -304,11 +293,12 @@
/* eslint-disable sort-imports */
import { Modal } from 'bootstrap'
-import { formatNumber, responseToJSON } from '../helpers'
+import { formatNumber } from '../helpers'
import rangeSelector from './rangeSelector.vue'
+import txEditor from './txEditor.vue'
export default {
- components: { rangeSelector },
+ components: { rangeSelector, txEditor },
computed: {
account() {
@@ -382,16 +372,7 @@ export default {
data() {
return {
- form: {
- amount: 0,
- category: '',
- cleared: false,
- date: new Date().toISOString()
- .split('T')[0],
-
- description: '',
- payee: '',
- },
+ editedTxId: null,
modals: {
createTransfer: {
@@ -410,36 +391,6 @@ export default {
},
methods: {
- createTransaction() {
- return fetch('/api/transactions', {
- body: JSON.stringify({
- ...this.form,
- account: this.accountId,
- category: this.form.category || null,
- time: new Date(this.form.date),
- }),
- headers: {
- 'Content-Type': 'application/json',
- },
- method: 'POST',
- })
- .then(responseToJSON)
- .then(() => {
- this.$emit('update-accounts')
- this.fetchTransactions()
-
- this.showAddTransaction = false
- this.form = {
- amount: 0,
- category: '',
- cleared: false,
- date: this.form.date,
- description: '',
- payee: '',
- }
- })
- },
-
deleteSelected() {
const actions = []
for (const id of this.selectedTx) {
@@ -455,6 +406,14 @@ export default {
})
},
+ editSelected() {
+ this.editTx(this.selectedTx[0])
+ },
+
+ editTx(txId) {
+ this.editedTxId = txId
+ },
+
fetchTransactions() {
const since = this.timeRange.start.toISOString()
const until = this.timeRange.end.toISOString()
@@ -500,6 +459,12 @@ export default {
})
},
+ txSaved() {
+ this.editedTxId = null
+ this.$emit('update-accounts')
+ this.fetchTransactions()
+ },
+
updateSelectAll(evt) {
this.selectedTxRaw = Object.fromEntries(this.transactions.map(tx => [tx.id, evt.target.checked]))
},
diff --git a/frontend/components/txEditor.vue b/frontend/components/txEditor.vue
new file mode 100644
index 0000000..9cedb8b
--- /dev/null
+++ b/frontend/components/txEditor.vue
@@ -0,0 +1,165 @@
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
diff --git a/frontend/helpers.js b/frontend/helpers.js
index b8c79cf..9c3876b 100644
--- a/frontend/helpers.js
+++ b/frontend/helpers.js
@@ -39,5 +39,9 @@ export function responseToJSON(resp) {
throw new Error(`non-2xx status code: ${resp.status}`)
}
+ if (resp.status === 204) {
+ return null
+ }
+
return resp.json()
}
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 51afaaf..2010847 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -61,6 +61,9 @@ func RegisterHandler(apiRouter *mux.Router, dbc *database.Client, logger *logrus
apiRouter.
HandleFunc("/transactions/{id}", as.handleUpdateTransaction).
Methods(http.MethodPatch)
+ apiRouter.
+ HandleFunc("/transactions/{id}", as.handleOverwriteTransaction).
+ Methods(http.MethodPut)
}
func (a apiServer) errorResponse(w http.ResponseWriter, err error, desc string, status int) {
diff --git a/pkg/api/transaction.go b/pkg/api/transaction.go
index 339d611..c31fae0 100644
--- a/pkg/api/transaction.go
+++ b/pkg/api/transaction.go
@@ -123,6 +123,31 @@ func (a apiServer) handleListTransactionsByAccount(w http.ResponseWriter, r *htt
a.jsonResponse(w, http.StatusOK, txs)
}
+func (a apiServer) handleOverwriteTransaction(w http.ResponseWriter, r *http.Request) {
+ var (
+ tx database.Transaction
+ txID uuid.UUID
+ err error
+ )
+
+ if txID, err = uuid.Parse(mux.Vars(r)["id"]); err != nil {
+ a.errorResponse(w, err, "parsing id", http.StatusBadRequest)
+ return
+ }
+
+ if err = json.NewDecoder(r.Body).Decode(&tx); err != nil {
+ a.errorResponse(w, err, "parsing body", http.StatusBadRequest)
+ return
+ }
+
+ if err = a.dbc.UpdateTransaction(txID, tx); err != nil {
+ a.errorResponse(w, err, "updating transaction", http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
+
func (a apiServer) handleUpdateTransaction(w http.ResponseWriter, r *http.Request) {
var (
txID uuid.UUID
diff --git a/pkg/database/database.go b/pkg/database/database.go
index 3fc60c3..3603837 100644
--- a/pkg/database/database.go
+++ b/pkg/database/database.go
@@ -421,6 +421,33 @@ func (c *Client) UpdateAccountName(id uuid.UUID, name string) (err error) {
return nil
}
+// UpdateTransaction takes a transaction, fetches the stored transaction
+// applies some sanity actions and stores it back to the database
+func (c *Client) UpdateTransaction(txID uuid.UUID, tx Transaction) (err error) {
+ if err = c.retryTx(func(db *gorm.DB) error {
+ var oldTX Transaction
+ if err := db.First(&oldTX, "id = ?", txID).Error; err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return backoff.NewErrCannotRetry(fmt.Errorf("fetching old transaction: %w", err)) //nolint:wrapcheck
+ }
+ return fmt.Errorf("fetching old transaction: %w", err)
+ }
+
+ tx.ID = txID
+ tx.Account = oldTX.Account // Changing that would create chaos
+
+ if err = tx.Validate(c); err != nil {
+ return fmt.Errorf("validating transaction: %w", err)
+ }
+
+ return db.Save(&tx).Error
+ }); err != nil {
+ return fmt.Errorf("updating transaction: %w", err)
+ }
+
+ return nil
+}
+
// UpdateTransactionCategory modifies the category of the given
// transaction. (It is not possible to remove a category with this)
func (c *Client) UpdateTransactionCategory(id uuid.UUID, cat uuid.UUID) (err error) {