Compare commits

...

10 commits

Author SHA1 Message Date
2b23f1488c
Update dependencies
Some checks failed
test-and-build / test-and-build (push) Has been cancelled
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-15 16:00:47 +01:00
777eceafb7
Fix: Auto-clear money transfers between categories
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-10 12:54:31 +01:00
4e255c2740
Add category-activity transaction view
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-03-10 12:52:53 +01:00
c93b2ade07
prepare release v0.6.0
Some checks failed
test-and-build / test-and-build (push) Has been cancelled
2024-02-09 13:16:12 +01:00
00ecb36ab8
Add "clear today" functionality
to move a transaction to today and mark it cleared in one step

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-02-09 13:15:14 +01:00
d101c24b85
Fix: Paired transactions must be updated with negative sum
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-02-07 15:01:48 +01:00
e1de837967
prepare release v0.5.0
Some checks failed
test-and-build / test-and-build (push) Has been cancelled
2024-02-06 15:02:23 +01:00
95dd2414b5
Fix: Broken category ID parsing for money transfers
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-02-06 11:10:58 +01:00
5d41188876
Add description to money transfers
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-02-04 12:40:16 +01:00
a3523c0a16
prepare release v0.4.0
Some checks failed
test-and-build / test-and-build (push) Has been cancelled
2024-02-03 01:39:00 +01:00
9 changed files with 189 additions and 65 deletions

View file

@ -1,3 +1,20 @@
# 0.6.0 / 2024-02-09
* Add "clear today" functionality
* Fix: Paired transactions must be updated with negative sum
# 0.5.0 / 2024-02-06
* Add description to money transfers
* Fix: Broken category ID parsing for money transfers
# 0.4.0 / 2024-02-03
* Move creation of starting balances to backend
* Restore selected date-range for transaction list
* Fix: Update amount of paired transactions
* Fix: Unallocated gets unreadable if negative
# 0.3.0 / 2024-02-01
* Add account reconcilation

View file

@ -58,6 +58,7 @@
<button
v-shortkey.once="['ctrl', 'alt', 'n']"
class="btn btn-primary"
:disabled="accountIsCategory"
@click="showAddTransaction = !showAddTransaction"
@shortkey="showAddTransaction = !showAddTransaction"
>
@ -66,6 +67,7 @@
</button>
<button
class="btn btn-primary"
:disabled="accountIsCategory"
data-bs-toggle="modal"
data-bs-target="#transferMoneyModal"
>
@ -75,6 +77,7 @@
<button
class="btn btn-success"
:disabled="accountIsCategory"
@click="markAccountReconciled"
>
<i class="fas fa-fw fa-square-check mr-1" />
@ -109,6 +112,16 @@
Move to Today
</button>
</li>
<li>
<button
class="dropdown-item"
:disabled="selectedTx.length < 1"
@click="clearToday"
>
<i class="fas fa-fw fa-copyright mr-1" />
Clear Today
</button>
</li>
<li>
<button
v-shortkey="['del']"
@ -140,6 +153,9 @@
<th class="minimized-column">
Date
</th>
<th v-if="accountIsCategory">
Account
</th>
<th>Payee</th>
<th v-if="account.type !== 'tracking'">
Category
@ -167,7 +183,7 @@
>
<tr
v-if="tx.id !== editedTxId"
:key="tx.id"
:key="`display-${tx.id}`"
@dblclick="editTx(tx.id)"
>
<td>
@ -179,6 +195,9 @@
<td class="minimized-column">
{{ new Date(tx.time).toLocaleDateString() }}
</td>
<td v-if="accountIsCategory">
{{ accountIdToName[tx.account] }}
</td>
<td>{{ tx.payee }}</td>
<td v-if="account.type !== 'tracking'">
{{ accountIdToName[tx.category] }}
@ -206,7 +225,7 @@
</tr>
<tx-editor
v-else
:key="tx.id"
:key="`editor-${tx.id}`"
:account="account"
:accounts="accounts"
:edit="tx"
@ -228,7 +247,7 @@
aria-labelledby="transferMoneyModalLabel"
aria-hidden="true"
>
<div class="modal-dialog modal-sm">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1
@ -306,6 +325,19 @@
</select>
</div>
<div class="mb-3">
<label
for="transferMoneyModalDescription"
class="form-label"
>Description</label>
<input
id="transferMoneyModalDescription"
v-model.number="modals.createTransfer.description"
type="text"
class="form-control"
>
</div>
<div class="mb-3">
<label
for="transferMoneyModalAmount"
@ -368,6 +400,10 @@ export default {
return Object.fromEntries(this.accounts.map(acc => [acc.id, acc.name]))
},
accountIsCategory() {
return this.account.type === 'category'
},
accountTypes() {
return Object.fromEntries(this.accounts.map(acc => [acc.id, acc.type]))
},
@ -453,6 +489,7 @@ export default {
createTransfer: {
amount: 0,
category: '',
description: '',
from: '',
to: '',
},
@ -470,6 +507,22 @@ export default {
methods: {
classFromNumber,
clearToday() {
return this.patchSelected([
{
op: 'replace',
path: '/time',
value: new Date(new Date().toISOString()
.split('T')[0]),
},
{
op: 'replace',
path: '/cleared',
value: true,
},
])
},
deleteSelected() {
const actions = []
for (const id of this.selectedTx) {
@ -522,22 +575,26 @@ export default {
},
moveToToday() {
return this.patchSelected([
{
op: 'replace',
path: '/time',
value: new Date(new Date().toISOString()
.split('T')[0]),
},
])
},
patchSelected(patchset = []) {
const actions = []
for (const id of this.selectedTx) {
actions.push(fetch(`/api/transactions/${id}`, {
body: JSON.stringify([
{
op: 'replace',
path: '/time',
value: new Date(new Date().toISOString()
.split('T')[0]),
},
]),
body: JSON.stringify(patchset),
method: 'PATCH',
}))
}
Promise.all(actions)
return Promise.all(actions)
.then(() => {
this.$emit('update-accounts')
this.fetchTransactions()
@ -550,6 +607,9 @@ export default {
if (this.modals.createTransfer.category) {
params.set('category', this.modals.createTransfer.category)
}
if (this.modals.createTransfer.description) {
params.set('description', this.modals.createTransfer.description)
}
return fetch(`/api/accounts/${this.modals.createTransfer.from}/transfer/${this.modals.createTransfer.to}?${params.toString()}`, {
method: 'PUT',

View file

@ -63,7 +63,13 @@
{{ formatNumber(allocatedByCategory[cat.id] || 0) }}
</td>
<td :class="classFromNumber(activityByCategory[cat.id] || 0, ['text-end'])">
{{ formatNumber(activityByCategory[cat.id] || 0) }}
<router-link
class="text-white text-decoration-none"
:to="{ name: 'account-transactions', params: { accountId: cat.id }}"
title="List Transactions"
>
{{ formatNumber(activityByCategory[cat.id] || 0) }}
</router-link>
</td>
<td class="text-end">
<a

View file

@ -11,6 +11,9 @@
@keyup.enter="$refs.payee.focus()"
>
</td>
<td v-if="accountIsCategory">
{{ accountIdToName[edit.account] }}
</td>
<td>
<input
ref="payee"
@ -75,6 +78,14 @@ import { responseToJSON } from '../helpers'
export default {
computed: {
accountIdToName() {
return Object.fromEntries(this.accounts.map(acc => [acc.id, acc.name]))
},
accountIsCategory() {
return this.account.type === 'category'
},
categories() {
const cats = this.accounts.filter(acc => acc.type === 'category')
cats.sort((a, b) => a.name.localeCompare(b.name))

23
go.mod
View file

@ -3,17 +3,17 @@ module git.luzifer.io/luzifer/accounting
go 1.21.5
require (
github.com/Luzifer/go_helpers/v2 v2.22.0
github.com/Luzifer/go_helpers/v2 v2.23.0
github.com/Luzifer/rconfig/v2 v2.5.0
github.com/glebarez/sqlite v1.10.0
github.com/google/uuid v1.5.0
github.com/glebarez/sqlite v1.11.0
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
gopkg.in/evanphx/json-patch.v5 v5.8.1
gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.5
gopkg.in/evanphx/json-patch.v5 v5.9.0
gorm.io/driver/postgres v1.5.7
gorm.io/gorm v1.25.7
)
require (
@ -22,23 +22,24 @@ require (
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/pgx/v5 v5.5.2 // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/validator.v2 v2.0.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.40.5 // indirect
modernc.org/libc v1.44.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/sqlite v1.28.0 // indirect
modernc.org/sqlite v1.29.5 // indirect
)

66
go.sum
View file

@ -1,5 +1,5 @@
github.com/Luzifer/go_helpers/v2 v2.22.0 h1:rJrZkJDzAiq4J0RUbwPI7kQ5rUy7BYQ/GUpo3fSM0y0=
github.com/Luzifer/go_helpers/v2 v2.22.0/go.mod h1:cIIqMPu3NT8/6kHke+03hVznNDLLKVGA74Lz47CWJyA=
github.com/Luzifer/go_helpers/v2 v2.23.0 h1:VowDwOCl6nOt+GVqKUX/do6a94pEeqNTRHb29MsoGX4=
github.com/Luzifer/go_helpers/v2 v2.23.0/go.mod h1:BSGkJ/dxqs7AxsfZt8zjJb4R6YB5dONS+/ad7foLUrk=
github.com/Luzifer/rconfig/v2 v2.5.0 h1:zx5lfQbNX3za4VegID97IeY+M+BmfgHxWJTYA94sxok=
github.com/Luzifer/rconfig/v2 v2.5.0/go.mod h1:eGWUPQeCPv/Pr/p0hjmwFgI20uqvwi/Szen69hUzGzU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -9,20 +9,20 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.2 h1:iLlpgp4Cp/gC9Xuscl7lFL1PhhW+ZLtXZcrfCt4C3tA=
github.com/jackc/pgx/v5 v5.5.2/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@ -35,6 +35,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -52,35 +54,55 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v5 v5.8.1 h1:BVxXj2YS+4i9fttNkVvDKi4Pg1pVMpVE8tdEwaKeQY0=
gopkg.in/evanphx/json-patch.v5 v5.8.1/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk=
gopkg.in/evanphx/json-patch.v5 v5.9.0 h1:hx1VU2SGj4F8r9b8GUwJLdc8DNO8sy79ZGui0G05GLo=
gopkg.in/evanphx/json-patch.v5 v5.9.0/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk=
gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY=
gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
modernc.org/libc v1.40.5 h1:B9KljZSWzWCV2WtgQ54xu0Ig4imof21SLnKFx7qZ3os=
modernc.org/libc v1.40.5/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
modernc.org/cc/v4 v4.19.3 h1:vE9kmJqUcyvNOf8F2Hn8od14SOMq34BiqcZ2tMzLk5c=
modernc.org/cc/v4 v4.19.3/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.10.1 h1:qi+3luLv0LR5UkLmZyKXZxIC4K/136vcAUoYYeGSS+g=
modernc.org/ccgo/v4 v4.10.1/go.mod h1:9YDnb1IIvHymh899K5a++jza0JIWygZPTc5dlh7xvhQ=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/libc v1.44.1 h1:dsoFMypkA7AofdQCx4JZpwym4DuWqyzhH1tpyU8ZV5g=
modernc.org/libc v1.44.1/go.mod h1:RRqfGVjvILF5AdNP3RPCiihj7+Dn2pIBrdlU60lA9vs=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE=
modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

View file

@ -54,7 +54,7 @@ func (a apiServer) handleCreateAccount(w http.ResponseWriter, r *http.Request) {
})
case database.AccountTypeCategory:
err = a.dbc.TransferMoney(database.UnallocatedMoney, acc.ID, payload.StartingBalance)
err = a.dbc.TransferMoney(database.UnallocatedMoney, acc.ID, payload.StartingBalance, "")
case database.AccountTypeTracking:
_, err = a.dbc.CreateTransaction(database.Transaction{
@ -177,19 +177,19 @@ func (a apiServer) handleTransferMoney(w http.ResponseWriter, r *http.Request) {
}
if r.URL.Query().Has("category") {
if category, err = uuid.Parse(mux.Vars(r)["category"]); err != nil {
if category, err = uuid.Parse(r.URL.Query().Get("category")); err != nil {
a.errorResponse(w, err, "parsing category", http.StatusBadRequest)
return
}
}
if category == uuid.Nil {
if err = a.dbc.TransferMoney(from, to, amount); err != nil {
if err = a.dbc.TransferMoney(from, to, amount, r.URL.Query().Get("description")); err != nil {
a.errorResponse(w, err, "transferring money", http.StatusInternalServerError)
return
}
} else {
if err = a.dbc.TransferMoneyWithCategory(from, to, amount, category); err != nil {
if err = a.dbc.TransferMoneyWithCategory(from, to, amount, r.URL.Query().Get("description"), category); err != nil {
a.errorResponse(w, err, "transferring money", http.StatusInternalServerError)
return
}

View file

@ -287,7 +287,7 @@ func (c *Client) MarkAccountReconciled(acc uuid.UUID) (err error) {
// TransferMoney creates new Transactions for the given account
// transfer. The account type of the from and to account must match
// for this to work.
func (c *Client) TransferMoney(from, to uuid.UUID, amount float64) (err error) {
func (c *Client) TransferMoney(from, to uuid.UUID, amount float64, description string) (err error) {
var fromAcc, toAcc Account
if fromAcc, err = c.GetAccount(from); err != nil {
@ -311,7 +311,8 @@ func (c *Client) TransferMoney(from, to uuid.UUID, amount float64) (err error) {
txs = []*Transaction{
{
Time: time.Now().UTC(),
Description: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Payee: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Description: description,
Amount: -amount,
Account: uuid.NullUUID{UUID: from, Valid: true},
Category: uuid.NullUUID{},
@ -320,7 +321,8 @@ func (c *Client) TransferMoney(from, to uuid.UUID, amount float64) (err error) {
},
{
Time: time.Now().UTC(),
Description: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Payee: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Description: description,
Amount: amount,
Account: uuid.NullUUID{UUID: to, Valid: true},
Category: uuid.NullUUID{},
@ -334,20 +336,22 @@ func (c *Client) TransferMoney(from, to uuid.UUID, amount float64) (err error) {
txs = []*Transaction{
{
Time: time.Now().UTC(),
Description: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Payee: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Description: description,
Amount: -amount,
Account: uuid.NullUUID{},
Category: uuid.NullUUID{UUID: from, Valid: true},
Cleared: false,
Cleared: true,
PairKey: uuid.NullUUID{UUID: pairKey, Valid: true},
},
{
Time: time.Now().UTC(),
Description: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Payee: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Description: description,
Amount: amount,
Account: uuid.NullUUID{},
Category: uuid.NullUUID{UUID: to, Valid: true},
Cleared: false,
Cleared: true,
PairKey: uuid.NullUUID{UUID: pairKey, Valid: true},
},
}
@ -370,7 +374,7 @@ func (c *Client) TransferMoney(from, to uuid.UUID, amount float64) (err error) {
// TransferMoneyWithCategory creates new Transactions for the given
// account transfer. This is not possible for category type accounts.
func (c *Client) TransferMoneyWithCategory(from, to uuid.UUID, amount float64, category uuid.UUID) (err error) {
func (c *Client) TransferMoneyWithCategory(from, to uuid.UUID, amount float64, description string, category uuid.UUID) (err error) {
var fromAcc, toAcc Account
if fromAcc, err = c.GetAccount(from); err != nil {
@ -390,7 +394,8 @@ func (c *Client) TransferMoneyWithCategory(from, to uuid.UUID, amount float64, c
if err = c.retryTx(func(tx *gorm.DB) (err error) {
fromTx := Transaction{
Time: time.Now().UTC(),
Description: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Payee: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Description: description,
Amount: -amount,
Account: uuid.NullUUID{UUID: from, Valid: true},
Category: uuid.NullUUID{},
@ -404,7 +409,8 @@ func (c *Client) TransferMoneyWithCategory(from, to uuid.UUID, amount float64, c
toTx := Transaction{
Time: time.Now().UTC(),
Description: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Payee: fmt.Sprintf("Transfer: %s → %s", fromAcc.Name, toAcc.Name),
Description: description,
Amount: amount,
Account: uuid.NullUUID{UUID: to, Valid: true},
Category: uuid.NullUUID{},
@ -494,7 +500,8 @@ func (c *Client) UpdateTransaction(txID uuid.UUID, tx Transaction) (err error) {
if err = db.Model(&Transaction{}).
Where("pair_key = ?", oldTX.PairKey.UUID).
Update("amount", tx.Amount).
Where("id <> ?", oldTX.ID).
Update("amount", -tx.Amount).
Error; err != nil {
return fmt.Errorf("updating amount for paired transaction: %w", err)
}

View file

@ -94,7 +94,7 @@ func TestPairKeyRemoval(t *testing.T) {
testCheckAcctBal(t, bals, tb2.ID, 0)
// Transfer some money
require.NoError(t, dbc.TransferMoney(tb1.ID, tb2.ID, 500))
require.NoError(t, dbc.TransferMoney(tb1.ID, tb2.ID, 500, ""))
bals, err = dbc.ListAccountBalances(false)
require.NoError(t, err)
testCheckAcctBal(t, bals, tb1.ID, -500)
@ -163,7 +163,7 @@ func TestTransactions(t *testing.T) {
testCheckAcctBal(t, bals, UnallocatedMoney, 1000)
// Lets redistribute the money
require.NoError(t, dbc.TransferMoney(UnallocatedMoney, tc.ID, 500))
require.NoError(t, dbc.TransferMoney(UnallocatedMoney, tc.ID, 500, ""))
bals, err = dbc.ListAccountBalances(false)
require.NoError(t, err)
testCheckAcctBal(t, bals, tb1.ID, 1000)
@ -173,7 +173,7 @@ func TestTransactions(t *testing.T) {
testCheckAcctBal(t, bals, UnallocatedMoney, 500)
// Now transfer some money to another budget account
require.NoError(t, dbc.TransferMoney(tb1.ID, tb2.ID, 100))
require.NoError(t, dbc.TransferMoney(tb1.ID, tb2.ID, 100, ""))
bals, err = dbc.ListAccountBalances(false)
require.NoError(t, err)
testCheckAcctBal(t, bals, tb1.ID, 900)
@ -183,7 +183,7 @@ func TestTransactions(t *testing.T) {
testCheckAcctBal(t, bals, UnallocatedMoney, 500)
// And some to a tracking account (needs category)
require.NoError(t, dbc.TransferMoneyWithCategory(tb1.ID, tt.ID, 100, tc.ID))
require.NoError(t, dbc.TransferMoneyWithCategory(tb1.ID, tt.ID, 100, "", tc.ID))
bals, err = dbc.ListAccountBalances(false)
require.NoError(t, err)
testCheckAcctBal(t, bals, tb1.ID, 800)