Compare commits
10 commits
81b50f9dc7
...
2b23f1488c
Author | SHA1 | Date | |
---|---|---|---|
2b23f1488c | |||
777eceafb7 | |||
4e255c2740 | |||
c93b2ade07 | |||
00ecb36ab8 | |||
d101c24b85 | |||
e1de837967 | |||
95dd2414b5 | |||
5d41188876 | |||
a3523c0a16 |
9 changed files with 189 additions and 65 deletions
17
History.md
17
History.md
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
23
go.mod
|
@ -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
66
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue