mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-08-21 12:54:32 +00:00
Compare commits
144 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6d0906c37b | ||
|
a3ede0c6f6 | ||
|
72eab3c0eb | ||
|
c530961546 | ||
|
dd7aba0b51 | ||
|
7c54e17a30 | ||
|
342d72b0a6 | ||
|
22ee504e52 | ||
|
4880ee850e | ||
|
b45ce27817 | ||
|
dfad93d9ec | ||
|
52712c8cb2 | ||
|
740874e654 | ||
|
0f1f37a5df | ||
|
d609cbfb16 | ||
|
2375857c92 | ||
|
10275eb832 | ||
|
108867b2ef | ||
|
e5a77a86f9 | ||
|
309d3e8e95 | ||
|
1ef9b83180 | ||
|
85757e1a20 | ||
|
e2a18e0e7c | ||
|
056af8fd8a | ||
|
c72a63f218 | ||
|
fb823ed422 | ||
|
3fae6c0cac | ||
|
55b1e22d37 | ||
|
e6b95c7894 | ||
|
ea2f9e4919 | ||
|
08bd405e29 | ||
|
8294b2d526 | ||
|
b521fb8fae | ||
|
3e9789d5e6 | ||
|
13b029d7f0 | ||
|
59427ba5c2 | ||
|
46bba9d799 | ||
|
0ef1d1834f | ||
|
e39b2f5355 | ||
|
03dae53714 | ||
|
6be6187532 | ||
|
4c26f613ee | ||
|
765de2eeba | ||
|
49b1fef7ff | ||
|
36157afc6a | ||
|
c5c3638dbc | ||
|
926665a8f5 | ||
|
e35743ff42 | ||
|
321743dbf6 | ||
|
c2e1e25489 | ||
|
222387adba | ||
|
335414f25a | ||
|
ea903c105d | ||
|
aab29852b4 | ||
|
aa17ea0b68 | ||
|
deeb5bf118 | ||
|
47d5e8d169 | ||
|
033d61abbb | ||
|
28efc15820 | ||
|
1c7988fad2 | ||
|
c8077762ba | ||
|
ea53b34cbb | ||
|
270ac87e65 | ||
|
0a60f63bf8 | ||
|
38ed70243e | ||
|
0f2a9a9b37 | ||
|
ec525849fc | ||
|
dee1e1a79d | ||
|
98b5ed9e5b | ||
|
d8eb084240 | ||
|
c2f5fbe7d3 | ||
|
60f6542a1b | ||
|
93c2edf76c | ||
|
f74f4c5719 | ||
|
8c67c347e1 | ||
|
fcbc341f03 | ||
|
2dbce10483 | ||
|
8504b3e3a9 | ||
|
d4ab5dcefd | ||
|
5d11e285bc | ||
|
259e1350d5 | ||
|
dd54ec6122 | ||
|
c2b3791336 | ||
|
fc1f15cc14 | ||
|
5d74979f50 | ||
|
5dea7e5b41 | ||
|
a53a8a8529 | ||
|
aac7a2691d | ||
|
10e7be1729 | ||
|
9b1319f970 | ||
|
a38f909919 | ||
|
d8c0192f54 | ||
|
1e8d294b8d | ||
|
d3be64114b | ||
|
1bddd4da8e | ||
|
6b5ff2680b | ||
|
efe17f638a | ||
|
3c1f4b7377 | ||
|
acfc214eb1 | ||
|
fe1c605c11 | ||
|
891cd94031 | ||
|
c620d57f42 | ||
|
b076c92eb0 | ||
|
9e35216e98 | ||
|
3fa4734ba7 | ||
|
0144d09325 | ||
|
f28274aded | ||
|
d5f5df82b6 | ||
|
49c27aff2d | ||
|
7b1b0cfef9 | ||
|
f728bdeb5a | ||
|
4712a826d1 | ||
|
d7335d71ea | ||
|
e6a84ab387 | ||
|
fd5269490d | ||
|
05e307136c | ||
|
2c41215e84 | ||
|
d9dc394e7f | ||
|
a63daf6166 | ||
|
f878af0d3b | ||
|
def0578421 | ||
|
a2e07b1518 | ||
|
dc26ce308d | ||
|
0189b91d3b | ||
|
fb6706648e | ||
|
3c20fd7533 | ||
|
82f89ef614 | ||
|
4d10ae91b7 | ||
|
aebb677c21 | ||
|
abf9c65b21 | ||
|
524dcc7d97 | ||
|
0a4f6fd6b8 | ||
|
9c4beab0a7 | ||
|
b91e019416 | ||
|
ad97e33ec0 | ||
|
b1479b5dae | ||
|
f1445b0132 | ||
|
456dbfd29d | ||
|
0eea776b8b | ||
|
853a97307b | ||
|
3b28b5d07a | ||
|
a7bfa503d9 | ||
|
a7c3d32370 | ||
|
c64ddbec1f |
48
.ci/php-cs-fixer/composer.lock
generated
48
.ci/php-cs-fixer/composer.lock
generated
@@ -677,16 +677,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/diff",
|
||||
"version": "5.0.1",
|
||||
"version": "5.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/diff.git",
|
||||
"reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02"
|
||||
"reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/aae9a0a43bff37bd5d8d0311426c87bf36153f02",
|
||||
"reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b",
|
||||
"reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -732,7 +732,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/diff/issues",
|
||||
"security": "https://github.com/sebastianbergmann/diff/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/5.0.1"
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/5.0.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -740,20 +740,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-03-23T05:12:41+00:00"
|
||||
"time": "2023-05-01T07:48:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v6.2.8",
|
||||
"version": "v6.2.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b"
|
||||
"reference": "12288d9f4500f84a4d02254d4aa968b15488476f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/3582d68a64a86ec25240aaa521ec8bc2342b369b",
|
||||
"reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/12288d9f4500f84a4d02254d4aa968b15488476f",
|
||||
"reference": "12288d9f4500f84a4d02254d4aa968b15488476f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -820,7 +820,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v6.2.8"
|
||||
"source": "https://github.com/symfony/console/tree/v6.2.10"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -836,7 +836,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-03-29T21:42:15+00:00"
|
||||
"time": "2023-04-28T13:37:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -1069,16 +1069,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v6.2.7",
|
||||
"version": "v6.2.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "82b6c62b959f642d000456f08c6d219d749215b3"
|
||||
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/82b6c62b959f642d000456f08c6d219d749215b3",
|
||||
"reference": "82b6c62b959f642d000456f08c6d219d749215b3",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/fd588debf7d1bc16a2c84b4b3b71145d9946b894",
|
||||
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1112,7 +1112,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v6.2.7"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v6.2.10"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1128,7 +1128,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-02-14T08:44:56+00:00"
|
||||
"time": "2023-04-18T13:46:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
@@ -1755,16 +1755,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v6.2.8",
|
||||
"version": "v6.2.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "75ed64103df4f6615e15a7fe38b8111099f47416"
|
||||
"reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/75ed64103df4f6615e15a7fe38b8111099f47416",
|
||||
"reference": "75ed64103df4f6615e15a7fe38b8111099f47416",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e",
|
||||
"reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1796,7 +1796,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v6.2.8"
|
||||
"source": "https://github.com/symfony/process/tree/v6.2.10"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1812,7 +1812,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-03-09T16:20:02+00:00"
|
||||
"time": "2023-04-18T13:56:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
|
21
.github/workflows/lock.yml
vendored
21
.github/workflows/lock.yml
vendored
@@ -5,26 +5,15 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
permissions:
|
||||
issues: write # for dessant/lock-threads to lock issues
|
||||
pull-requests: write # for dessant/lock-threads to lock PRs
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
- uses: JC5/lock-threads@main
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: '90'
|
||||
issue-comment: >
|
||||
Hi there! This is an automatic reply. `Share and enjoy`
|
||||
|
||||
This issue is now `locked` :lock:.
|
||||
|
||||
- If you feel there is more to be said about this specific issue, please feel free to open a new issue. Please refer to this issue for clarity.
|
||||
- Follow-up questions and comments can also be posted in a new [discussion](https://github.com/firefly-iii/firefly-iii/discussions/)
|
||||
|
||||
Thank you for your consideration.
|
||||
issue-inactive-days: 90
|
||||
pr-inactive-days: 90
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -31,4 +31,4 @@ jobs:
|
||||
Thank you for your contributions.
|
||||
days-before-stale: 14
|
||||
days-before-close: 7
|
||||
exempt-issue-labels: 'enhancement,feature,bug,announcement,layout-v3'
|
||||
exempt-issue-labels: 'enhancement,feature,bug,announcement,epic'
|
||||
|
@@ -85,12 +85,11 @@ abstract class Controller extends BaseController
|
||||
{
|
||||
$bag = new ParameterBag();
|
||||
$page = (int)request()->get('page');
|
||||
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
}
|
||||
if ($page > (2 ^ 16)) {
|
||||
$page = (2 ^ 16);
|
||||
if ($page > pow(2, 16)) {
|
||||
$page = pow(2, 16);
|
||||
}
|
||||
$bag->set('page', $page);
|
||||
|
||||
|
@@ -57,7 +57,7 @@ class DestroyRequest extends FormRequest
|
||||
',not_assets_liabilities';
|
||||
|
||||
return [
|
||||
'objects' => sprintf('required|min:1|string|in:%s', $valid),
|
||||
'objects' => sprintf('required|max:255|min:1|string|in:%s', $valid),
|
||||
'unused' => 'in:true,false',
|
||||
];
|
||||
}
|
||||
|
@@ -73,7 +73,7 @@ class ExportRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'type' => 'in:csv',
|
||||
'accounts' => 'min:1',
|
||||
'accounts' => 'min:1|max:65536',
|
||||
'start' => 'date|before:end',
|
||||
'end' => 'date|after:start',
|
||||
];
|
||||
|
@@ -103,8 +103,8 @@ class StoreRequest extends FormRequest
|
||||
$ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes')));
|
||||
$type = $this->convertString('type');
|
||||
$rules = [
|
||||
'name' => 'required|min:1|uniqueAccountForUser',
|
||||
'type' => 'required|min:1|'.sprintf('in:%s', $types),
|
||||
'name' => 'required|max:1024|min:1|uniqueAccountForUser',
|
||||
'type' => 'required|max:1024|min:1|'.sprintf('in:%s', $types),
|
||||
'iban' => ['iban', 'nullable', new UniqueIban(null, $type)],
|
||||
'bic' => 'bic|nullable',
|
||||
'account_number' => ['between:1,255', 'nullable', new UniqueAccountNumber(null, $type)],
|
||||
@@ -120,7 +120,7 @@ class StoreRequest extends FormRequest
|
||||
'credit_card_type' => sprintf('nullable|in:%s|required_if:account_role,ccAsset', $ccPaymentTypes),
|
||||
'monthly_payment_date' => 'nullable|date|required_if:account_role,ccAsset|required_if:credit_card_type,monthlyFull',
|
||||
'liability_type' => 'nullable|required_if:type,liability|required_if:type,liabilities|in:loan,debt,mortgage',
|
||||
'liability_amount' => 'required_with:liability_start_date|min:0|numeric',
|
||||
'liability_amount' => 'required_with:liability_start_date|min:0|numeric|max:1000000000',
|
||||
'liability_start_date' => 'required_with:liability_amount|date',
|
||||
'liability_direction' => 'nullable|required_if:type,liability|required_if:type,liabilities|in:credit,debit',
|
||||
'interest' => 'between:0,100|numeric',
|
||||
|
@@ -94,7 +94,7 @@ class UpdateRequest extends FormRequest
|
||||
$ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes')));
|
||||
|
||||
$rules = [
|
||||
'name' => sprintf('min:1|uniqueAccountForUser:%d', $account->id),
|
||||
'name' => sprintf('min:1|max:1024|uniqueAccountForUser:%d', $account->id),
|
||||
'type' => sprintf('in:%s', $types),
|
||||
'iban' => ['iban', 'nullable', new UniqueIban($account, $this->convertString('type'))],
|
||||
'bic' => 'bic|nullable',
|
||||
@@ -104,7 +104,7 @@ class UpdateRequest extends FormRequest
|
||||
'virtual_balance' => 'numeric|nullable',
|
||||
'order' => 'numeric|nullable',
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'active' => [new IsBoolean()],
|
||||
'include_net_worth' => [new IsBoolean()],
|
||||
'account_role' => sprintf('in:%s|nullable|required_if:type,asset', $accountRoles),
|
||||
|
@@ -67,7 +67,7 @@ class Request extends FormRequest
|
||||
{
|
||||
return [
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'amount' => 'numeric|gt:0',
|
||||
'start' => 'date',
|
||||
'end' => 'date',
|
||||
|
@@ -82,7 +82,7 @@ class StoreRequest extends FormRequest
|
||||
'amount_min' => 'numeric|gt:0|required',
|
||||
'amount_max' => 'numeric|gt:0|required',
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'date' => 'date|required',
|
||||
'end_date' => 'date|after:date',
|
||||
'extension_date' => 'date|after:date',
|
||||
|
@@ -84,7 +84,7 @@ class UpdateRequest extends FormRequest
|
||||
'amount_min' => 'numeric|gt:0',
|
||||
'amount_max' => 'numeric|gt:0',
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'date' => 'date',
|
||||
'end_date' => 'date|after:date',
|
||||
'extension_date' => 'date|after:date',
|
||||
|
@@ -65,7 +65,7 @@ class StoreRequest extends FormRequest
|
||||
'end' => 'required|after:start|date',
|
||||
'amount' => 'required|gt:0',
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -69,7 +69,7 @@ class UpdateRequest extends FormRequest
|
||||
'end' => 'date',
|
||||
'amount' => 'gt:0',
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -63,7 +63,7 @@ class UpdateRequest extends FormRequest
|
||||
$objectGroup = $this->route()->parameter('objectGroup');
|
||||
|
||||
return [
|
||||
'title' => sprintf('min:1|uniqueObjectGroup:%d', $objectGroup->id),
|
||||
'title' => sprintf('max:1024|min:1|uniqueObjectGroup:%d', $objectGroup->id),
|
||||
'order' => 'numeric',
|
||||
];
|
||||
}
|
||||
|
@@ -158,9 +158,9 @@ class StoreRequest extends FormRequest
|
||||
'transactions.*.amount' => 'required|numeric|gt:0',
|
||||
'transactions.*.foreign_amount' => 'nullable|numeric|gt:0',
|
||||
'transactions.*.currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code',
|
||||
'transactions.*.currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
|
||||
'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
|
||||
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.source_name' => 'between:1,255|nullable',
|
||||
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
|
@@ -172,9 +172,9 @@ class UpdateRequest extends FormRequest
|
||||
'transactions.*.amount' => 'numeric|gt:0',
|
||||
'transactions.*.foreign_amount' => 'nullable|numeric|gt:0',
|
||||
'transactions.*.currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code',
|
||||
'transactions.*.currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
|
||||
'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
|
||||
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.source_name' => 'between:1,255|nullable',
|
||||
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
|
@@ -79,7 +79,7 @@ class StoreRequest extends FormRequest
|
||||
$return[] = [
|
||||
'type' => $trigger['type'],
|
||||
'value' => $trigger['value'],
|
||||
'active' => $this->convertBoolean((string)($trigger['active'] ?? 'false')),
|
||||
'active' => $this->convertBoolean((string)($trigger['active'] ?? 'true')),
|
||||
'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
@@ -100,7 +100,7 @@ class StoreRequest extends FormRequest
|
||||
$return[] = [
|
||||
'type' => $action['type'],
|
||||
'value' => $action['value'],
|
||||
'active' => $this->convertBoolean((string)($action['active'] ?? 'false')),
|
||||
'active' => $this->convertBoolean((string)($action['active'] ?? 'true')),
|
||||
'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
@@ -130,7 +130,7 @@ class StoreRequest extends FormRequest
|
||||
'rule_group_title' => 'nullable|between:1,255|required_without:rule_group_id|belongsToUser:rule_groups,title',
|
||||
'trigger' => 'required|in:store-journal,update-journal',
|
||||
'triggers.*.type' => 'required|in:'.implode(',', $validTriggers),
|
||||
'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue',
|
||||
'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue|max:1024',
|
||||
'triggers.*.stop_processing' => [new IsBoolean()],
|
||||
'triggers.*.active' => [new IsBoolean()],
|
||||
'actions.*.type' => 'required|in:'.implode(',', $validActions),
|
||||
|
@@ -147,7 +147,7 @@ class UpdateRequest extends FormRequest
|
||||
'rule_group_title' => 'nullable|between:1,255|belongsToUser:rule_groups,title',
|
||||
'trigger' => 'in:store-journal,update-journal',
|
||||
'triggers.*.type' => 'required|in:'.implode(',', $validTriggers),
|
||||
'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue',
|
||||
'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue|max:1024',
|
||||
'triggers.*.stop_processing' => [new IsBoolean()],
|
||||
'triggers.*.active' => [new IsBoolean()],
|
||||
'actions.*.type' => 'required|in:'.implode(',', $validActions),
|
||||
|
@@ -65,8 +65,8 @@ class StoreRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'tag' => 'required|min:1|uniqueObjectForUser:tags,tag',
|
||||
'description' => 'min:1|nullable',
|
||||
'tag' => 'required|min:1|uniqueObjectForUser:tags,tag|max:1024',
|
||||
'description' => 'min:1|nullable|max:65536',
|
||||
'date' => 'date|nullable',
|
||||
];
|
||||
|
||||
|
@@ -71,8 +71,8 @@ class UpdateRequest extends FormRequest
|
||||
$tag = $this->route()->parameter('tagOrId');
|
||||
// TODO check if uniqueObjectForUser is obsolete
|
||||
$rules = [
|
||||
'tag' => 'min:1|uniqueObjectForUser:tags,tag,'.$tag->id,
|
||||
'description' => 'min:1|nullable',
|
||||
'tag' => 'min:1|max:1024|uniqueObjectForUser:tags,tag,'.$tag->id,
|
||||
'description' => 'min:1|nullable|max:65536',
|
||||
'date' => 'date|nullable',
|
||||
];
|
||||
|
||||
|
@@ -188,9 +188,9 @@ class StoreRequest extends FormRequest
|
||||
|
||||
// currency info
|
||||
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
|
||||
'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code|nullable',
|
||||
'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
|
||||
'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
|
||||
'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code|nullable',
|
||||
'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
|
||||
|
||||
// amount
|
||||
'transactions.*.amount' => 'required|numeric|gt:0',
|
||||
@@ -225,25 +225,25 @@ class StoreRequest extends FormRequest
|
||||
|
||||
// other interesting fields
|
||||
'transactions.*.reconciled' => [new IsBoolean()],
|
||||
'transactions.*.notes' => 'min:1,max:50000|nullable',
|
||||
'transactions.*.notes' => 'min:1|max:50000|nullable',
|
||||
'transactions.*.tags' => 'between:0,255',
|
||||
|
||||
// meta info fields
|
||||
'transactions.*.internal_reference' => 'min:1,max:255|nullable',
|
||||
'transactions.*.external_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.recurrence_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.bunq_payment_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.external_url' => 'min:1,max:255|nullable|url',
|
||||
'transactions.*.internal_reference' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.recurrence_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_url' => 'min:1|max:255|nullable|url',
|
||||
|
||||
// SEPA fields:
|
||||
'transactions.*.sepa_cc' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ct_op' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ct_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_db' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_country' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ep' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ci' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_batch_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
|
||||
|
||||
// dates
|
||||
'transactions.*.interest_date' => 'date|nullable',
|
||||
@@ -270,7 +270,9 @@ class StoreRequest extends FormRequest
|
||||
$this->validateTransactionArray($validator);
|
||||
|
||||
// must submit at least one transaction.
|
||||
Log::debug('Now going to validateOneTransaction');
|
||||
$this->validateOneTransaction($validator);
|
||||
Log::debug('Now done with validateOneTransaction');
|
||||
|
||||
// all journals must have a description
|
||||
$this->validateDescriptions($validator);
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Requests\Models\Transaction;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Rules\BelongsUser;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
@@ -61,6 +62,7 @@ class UpdateRequest extends FormRequest
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$this->integerFields = [
|
||||
'order',
|
||||
@@ -163,6 +165,9 @@ class UpdateRequest extends FormRequest
|
||||
|
||||
/** @var array $transaction */
|
||||
foreach ($this->get('transactions') as $transaction) {
|
||||
if(!is_array($transaction)) {
|
||||
throw new FireflyException('Invalid data submitted: transaction is not array.');
|
||||
}
|
||||
// default response is to update nothing in the transaction:
|
||||
$current = [];
|
||||
$current = $this->getIntegerData($current, $transaction);
|
||||
@@ -330,9 +335,9 @@ class UpdateRequest extends FormRequest
|
||||
|
||||
// currency info
|
||||
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
|
||||
'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
|
||||
|
||||
// amount
|
||||
'transactions.*.amount' => 'numeric|gt:0|max:100000000000',
|
||||
@@ -359,25 +364,25 @@ class UpdateRequest extends FormRequest
|
||||
|
||||
// other interesting fields
|
||||
'transactions.*.reconciled' => [new IsBoolean()],
|
||||
'transactions.*.notes' => 'min:1,max:50000|nullable',
|
||||
'transactions.*.notes' => 'min:1|max:50000|nullable',
|
||||
'transactions.*.tags' => 'between:0,255',
|
||||
|
||||
// meta info fields
|
||||
'transactions.*.internal_reference' => 'min:1,max:255|nullable',
|
||||
'transactions.*.external_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.recurrence_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.bunq_payment_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.external_url' => 'min:1,max:255|nullable|url',
|
||||
'transactions.*.internal_reference' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.recurrence_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_url' => 'min:1|max:255|nullable|url',
|
||||
|
||||
// SEPA fields:
|
||||
'transactions.*.sepa_cc' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ct_op' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ct_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_db' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_country' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ep' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_ci' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_batch_id' => 'min:1,max:255|nullable',
|
||||
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
|
||||
|
||||
// dates
|
||||
'transactions.*.interest_date' => 'date|nullable',
|
||||
@@ -398,6 +403,7 @@ class UpdateRequest extends FormRequest
|
||||
*/
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
Log::debug('Now in withValidator');
|
||||
/** @var TransactionGroup $transactionGroup */
|
||||
$transactionGroup = $this->route()->parameter('transactionGroup');
|
||||
$validator->after(
|
||||
|
@@ -75,7 +75,7 @@ class StoreRequest extends FormRequest
|
||||
'name' => 'required|between:1,255|unique:transaction_currencies,name',
|
||||
'code' => 'required|between:3,51|unique:transaction_currencies,code',
|
||||
'symbol' => 'required|between:1,51|unique:transaction_currencies,symbol',
|
||||
'decimal_places' => 'between:0,20|numeric|min:0|max:20',
|
||||
'decimal_places' => 'between:0,20|numeric|min:0|max:12',
|
||||
'enabled' => [new IsBoolean()],
|
||||
'default' => [new IsBoolean()],
|
||||
|
||||
|
@@ -74,7 +74,7 @@ class UpdateRequest extends FormRequest
|
||||
'name' => sprintf('between:1,255|unique:transaction_currencies,name,%d', $currency->id),
|
||||
'code' => sprintf('between:3,51|unique:transaction_currencies,code,%d', $currency->id),
|
||||
'symbol' => sprintf('between:1,51|unique:transaction_currencies,symbol,%d', $currency->id),
|
||||
'decimal_places' => 'between:0,20|numeric|min:0|max:20',
|
||||
'decimal_places' => 'between:0,20|numeric|min:0|max:12',
|
||||
'enabled' => [new IsBoolean()],
|
||||
'default' => [new IsBoolean()],
|
||||
];
|
||||
|
@@ -59,9 +59,9 @@ class StoreRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|unique:link_types,name|min:1',
|
||||
'outward' => 'required|unique:link_types,outward|min:1|different:inward',
|
||||
'inward' => 'required|unique:link_types,inward|min:1|different:outward',
|
||||
'name' => 'required|unique:link_types,name|min:1|max:1024',
|
||||
'outward' => 'required|unique:link_types,outward|min:1|different:inward|max:1024',
|
||||
'inward' => 'required|unique:link_types,inward|min:1|different:outward|max:1024',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -64,9 +64,9 @@ class UpdateRequest extends FormRequest
|
||||
$linkType = $this->route()->parameter('linkType');
|
||||
|
||||
return [
|
||||
'name' => [Rule::unique('link_types', 'name')->ignore($linkType->id), 'min:1'],
|
||||
'outward' => ['different:inward', Rule::unique('link_types', 'outward')->ignore($linkType->id), 'min:1'],
|
||||
'inward' => ['different:outward', Rule::unique('link_types', 'inward')->ignore($linkType->id), 'min:1'],
|
||||
'name' => [Rule::unique('link_types', 'name')->ignore($linkType->id), 'min:1','max:1024'],
|
||||
'outward' => ['different:inward', Rule::unique('link_types', 'outward')->ignore($linkType->id), 'min:1','max:1024'],
|
||||
'inward' => ['different:outward', Rule::unique('link_types', 'inward')->ignore($linkType->id), 'min:1','max:1024'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
263
app/Console/Commands/Correction/CorrectAmounts.php
Normal file
263
app/Console/Commands/Correction/CorrectAmounts.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
/*
|
||||
* CorrectAmounts.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use FireflyIII\Models\AutoBudget;
|
||||
use FireflyIII\Models\AvailableBudget;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\CurrencyExchangeRate;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\PiggyBankRepetition;
|
||||
use FireflyIII\Models\RecurrenceTransaction;
|
||||
use FireflyIII\Models\RuleTrigger;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Class ReportSkeleton
|
||||
*/
|
||||
class CorrectAmounts extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This command makes sure positive and negative amounts are recorded correctly.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:fix-amount-pos-neg';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
// auto budgets must be positive
|
||||
$this->fixAutoBudgets();
|
||||
// available budgets must be positive
|
||||
$this->fixAvailableBudgets();
|
||||
// bills must be positive (both amounts)
|
||||
$this->fixBills();
|
||||
// budget limits must be positive
|
||||
$this->fixBudgetLimits();
|
||||
// currency_exchange_rates must be positive
|
||||
$this->fixExchangeRates();
|
||||
// piggy_bank_repetitions must be positive
|
||||
$this->fixRepetitions();
|
||||
// piggy_banks must be positive
|
||||
$this->fixPiggyBanks();
|
||||
// recurrences_transactions amount must be positive
|
||||
$this->fixRecurrences();
|
||||
// rule_triggers must be positive or zero (amount_less, amount_more, amount_is)
|
||||
$this->fixRuleTriggers();
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function fixAutoBudgets(): void
|
||||
{
|
||||
$set = AutoBudget::where('amount', '<', 0)->get();
|
||||
$count = $set->count();
|
||||
if (0 === $count) {
|
||||
$this->info('Correct: All auto budget amounts are positive.');
|
||||
return;
|
||||
}
|
||||
/** @var AutoBudget $item */
|
||||
foreach ($set as $item) {
|
||||
$item->amount = app('steam')->positive((string)$item->amount);
|
||||
$item->save();
|
||||
}
|
||||
$this->line(sprintf('Corrected %d auto budget amount(s).', $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function fixAvailableBudgets(): void
|
||||
{
|
||||
$set = AvailableBudget::where('amount', '<', 0)->get();
|
||||
$count = $set->count();
|
||||
if (0 === $count) {
|
||||
$this->info('Correct: All available budget amounts are positive.');
|
||||
return;
|
||||
}
|
||||
/** @var AvailableBudget $item */
|
||||
foreach ($set as $item) {
|
||||
$item->amount = app('steam')->positive((string)$item->amount);
|
||||
$item->save();
|
||||
}
|
||||
$this->line(sprintf('Corrected %d available budget amount(s).', $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function fixBills(): void
|
||||
{
|
||||
$set = Bill::where('amount_min', '<', 0)->orWhere('amount_max', '<', 0)->get();
|
||||
$count = $set->count();
|
||||
if (0 === $count) {
|
||||
$this->info('Correct: All bill amounts are positive.');
|
||||
return;
|
||||
}
|
||||
/** @var Bill $item */
|
||||
foreach ($set as $item) {
|
||||
$item->amount_min = app('steam')->positive((string)$item->amount_min);
|
||||
$item->amount_max = app('steam')->positive((string)$item->amount_max);
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function fixBudgetLimits(): void
|
||||
{
|
||||
$set = BudgetLimit::where('amount', '<', 0)->get();
|
||||
$count = $set->count();
|
||||
if (0 === $count) {
|
||||
$this->info('Correct: All budget limit amounts are positive.');
|
||||
return;
|
||||
}
|
||||
/** @var BudgetLimit $item */
|
||||
foreach ($set as $item) {
|
||||
$item->amount = app('steam')->positive((string)$item->amount);
|
||||
$item->save();
|
||||
}
|
||||
$this->line(sprintf('Corrected %d budget limit amount(s).', $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function fixExchangeRates(): void
|
||||
{
|
||||
$set = CurrencyExchangeRate::where('rate', '<', 0)->get();
|
||||
$count = $set->count();
|
||||
if (0 === $count) {
|
||||
$this->info('Correct: All currency exchange rates are positive.');
|
||||
return;
|
||||
}
|
||||
/** @var BudgetLimit $item */
|
||||
foreach ($set as $item) {
|
||||
$item->rate = app('steam')->positive((string)$item->rate);
|
||||
$item->save();
|
||||
}
|
||||
$this->line(sprintf('Corrected %d currency exchange rate(s).', $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function fixPiggyBanks(): void
|
||||
{
|
||||
$set = PiggyBank::where('targetamount', '<', 0)->get();
|
||||
$count = $set->count();
|
||||
if (0 === $count) {
|
||||
$this->info('Correct: All piggy bank amounts are positive.');
|
||||
return;
|
||||
}
|
||||
/** @var PiggyBankRepetition $item */
|
||||
foreach ($set as $item) {
|
||||
$item->targetamount = app('steam')->positive((string)$item->targetamount);
|
||||
$item->save();
|
||||
}
|
||||
$this->line(sprintf('Corrected %d piggy bank amount(s).', $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function fixRecurrences(): void
|
||||
{
|
||||
$set = RecurrenceTransaction::where('amount', '<', 0)
|
||||
->orWhere('foreign_amount', '<', 0)
|
||||
->get();
|
||||
$count = $set->count();
|
||||
if (0 === $count) {
|
||||
$this->info('Correct: All recurring transaction amounts are positive.');
|
||||
return;
|
||||
}
|
||||
/** @var PiggyBankRepetition $item */
|
||||
foreach ($set as $item) {
|
||||
$item->amount = app('steam')->positive((string)$item->amount);
|
||||
$item->foreign_amount = app('steam')->positive((string)$item->foreign_amount);
|
||||
$item->save();
|
||||
}
|
||||
$this->line(sprintf('Corrected %d recurring transaction amount(s).', $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function fixRepetitions(): void
|
||||
{
|
||||
$set = PiggyBankRepetition::where('currentamount', '<', 0)->get();
|
||||
$count = $set->count();
|
||||
if (0 === $count) {
|
||||
$this->info('Correct: All piggy bank repetition amounts are positive.');
|
||||
return;
|
||||
}
|
||||
/** @var PiggyBankRepetition $item */
|
||||
foreach ($set as $item) {
|
||||
$item->currentamount = app('steam')->positive((string)$item->currentamount);
|
||||
$item->save();
|
||||
}
|
||||
$this->line(sprintf('Corrected %d piggy bank repetition amount(s).', $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function fixRuleTriggers(): void
|
||||
{
|
||||
$set = RuleTrigger::whereIn('trigger_type', ['amount_less', 'amount_more', 'amount_is'])->get();
|
||||
$fixed = 0;
|
||||
/** @var RuleTrigger $item */
|
||||
foreach ($set as $item) {
|
||||
// basic check:
|
||||
if (-1 === bccomp((string)$item->trigger_value, '0')) {
|
||||
$fixed++;
|
||||
$item->trigger_value = app('steam')->positive((string)$item->trigger_value);
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
if (0 === $fixed) {
|
||||
$this->info('Correct: All rule trigger amounts are positive.');
|
||||
return;
|
||||
}
|
||||
$this->line(sprintf('Corrected %d rule trigger amount(s).', $fixed));
|
||||
}
|
||||
|
||||
}
|
@@ -52,8 +52,10 @@ class CorrectDatabase extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->line('Handle Firefly III database correction commands.');
|
||||
// if table does not exist, return false
|
||||
if (!Schema::hasTable('users')) {
|
||||
$this->error('No "users"-table, will not continue.');
|
||||
return 1;
|
||||
}
|
||||
$commands = [
|
||||
@@ -61,7 +63,7 @@ class CorrectDatabase extends Command
|
||||
'firefly-iii:create-link-types',
|
||||
'firefly-iii:create-access-tokens',
|
||||
'firefly-iii:remove-bills',
|
||||
'firefly-iii:fix-negative-limits',
|
||||
'firefly-iii:fix-amount-pos-neg',
|
||||
'firefly-iii:enable-currencies',
|
||||
'firefly-iii:fix-transfer-budgets',
|
||||
'firefly-iii:fix-uneven-amount',
|
||||
@@ -76,16 +78,16 @@ class CorrectDatabase extends Command
|
||||
'firefly-iii:fix-ob-currencies',
|
||||
'firefly-iii:fix-long-descriptions',
|
||||
'firefly-iii:fix-recurring-transactions',
|
||||
'firefly-iii:restore-oauth-keys',
|
||||
'firefly-iii:upgrade-group-information',
|
||||
'firefly-iii:fix-transaction-types',
|
||||
'firefly-iii:fix-frontpage-accounts',
|
||||
// new!
|
||||
'firefly-iii:unify-group-accounts',
|
||||
'firefly-iii:trigger-credit-recalculation',
|
||||
];
|
||||
foreach ($commands as $command) {
|
||||
$this->line(sprintf('Now executing %s', $command));
|
||||
Artisan::call($command);
|
||||
$result = Artisan::output();
|
||||
echo $result;
|
||||
$this->line(sprintf('Now executing command "%s"', $command));
|
||||
$this->call($command);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@@ -33,8 +33,8 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use JsonException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use JsonException;
|
||||
|
||||
/**
|
||||
* Class CorrectOpeningBalanceCurrencies
|
||||
|
@@ -61,6 +61,33 @@ class DeleteEmptyJournals extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function deleteEmptyJournals(): void
|
||||
{
|
||||
$start = microtime(true);
|
||||
$count = 0;
|
||||
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->groupBy('transaction_journals.id')
|
||||
->whereNull('transactions.transaction_journal_id')
|
||||
->get(['transaction_journals.id']);
|
||||
|
||||
foreach ($set as $entry) {
|
||||
try {
|
||||
TransactionJournal::find($entry->id)->delete();
|
||||
} catch (QueryException $e) {
|
||||
Log::info(sprintf('Could not delete entry: %s', $e->getMessage()));
|
||||
}
|
||||
|
||||
|
||||
$this->info(sprintf('Deleted empty transaction journal #%d', $entry->id));
|
||||
++$count;
|
||||
}
|
||||
if (0 === $count) {
|
||||
$this->info('No empty transaction journals.');
|
||||
}
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Verified empty journals in %s seconds', $end));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete transactions and their journals if they have an uneven number of transactions.
|
||||
*/
|
||||
@@ -91,31 +118,4 @@ class DeleteEmptyJournals extends Command
|
||||
$this->info('No uneven transaction journals.');
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteEmptyJournals(): void
|
||||
{
|
||||
$start = microtime(true);
|
||||
$count = 0;
|
||||
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->groupBy('transaction_journals.id')
|
||||
->whereNull('transactions.transaction_journal_id')
|
||||
->get(['transaction_journals.id']);
|
||||
|
||||
foreach ($set as $entry) {
|
||||
try {
|
||||
TransactionJournal::find($entry->id)->delete();
|
||||
} catch (QueryException $e) {
|
||||
Log::info(sprintf('Could not delete entry: %s', $e->getMessage()));
|
||||
}
|
||||
|
||||
|
||||
$this->info(sprintf('Deleted empty transaction journal #%d', $entry->id));
|
||||
++$count;
|
||||
}
|
||||
if (0 === $count) {
|
||||
$this->info('No empty transaction journals.');
|
||||
}
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Verified empty journals in %s seconds', $end));
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,6 @@ use Exception;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
@@ -66,6 +65,38 @@ class DeleteOrphanedTransactions extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function deleteFromOrphanedAccounts(): void
|
||||
{
|
||||
$set
|
||||
= Transaction::leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
||||
->whereNotNull('accounts.deleted_at')
|
||||
->get(['transactions.*']);
|
||||
$count = 0;
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($set as $transaction) {
|
||||
// delete journals
|
||||
$journal = TransactionJournal::find((int)$transaction->transaction_journal_id);
|
||||
if ($journal) {
|
||||
$journal->delete();
|
||||
}
|
||||
Transaction::where('transaction_journal_id', (int)$transaction->transaction_journal_id)->delete();
|
||||
$this->line(
|
||||
sprintf(
|
||||
'Deleted transaction journal #%d because account #%d was already deleted.',
|
||||
$transaction->transaction_journal_id,
|
||||
$transaction->account_id
|
||||
)
|
||||
);
|
||||
$count++;
|
||||
}
|
||||
if (0 === $count) {
|
||||
$this->info('No orphaned accounts.');
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteOrphanedJournals(): void
|
||||
{
|
||||
$set = TransactionJournal::leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
|
||||
@@ -129,36 +160,4 @@ class DeleteOrphanedTransactions extends Command
|
||||
$this->info('No orphaned transactions.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function deleteFromOrphanedAccounts(): void
|
||||
{
|
||||
$set
|
||||
= Transaction::leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
||||
->whereNotNull('accounts.deleted_at')
|
||||
->get(['transactions.*']);
|
||||
$count = 0;
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($set as $transaction) {
|
||||
// delete journals
|
||||
$journal = TransactionJournal::find((int)$transaction->transaction_journal_id);
|
||||
if ($journal) {
|
||||
$journal->delete();
|
||||
}
|
||||
Transaction::where('transaction_journal_id', (int)$transaction->transaction_journal_id)->delete();
|
||||
$this->line(
|
||||
sprintf(
|
||||
'Deleted transaction journal #%d because account #%d was already deleted.',
|
||||
$transaction->transaction_journal_id,
|
||||
$transaction->account_id
|
||||
)
|
||||
);
|
||||
$count++;
|
||||
}
|
||||
if (0 === $count) {
|
||||
$this->info('No orphaned accounts.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* FixBudgetLimits.php
|
||||
* Copyright (c) 2022 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use DB;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Class CorrectionSkeleton
|
||||
*/
|
||||
class FixBudgetLimits extends Command
|
||||
{
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Fixes negative budget limits';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'firefly-iii:fix-negative-limits';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$set = BudgetLimit::where('amount', '<', '0')->get();
|
||||
if (0 === $set->count()) {
|
||||
$this->info('All budget limits are OK.');
|
||||
return 0;
|
||||
}
|
||||
$count = BudgetLimit::where('amount', '<', '0')->update(['amount' => DB::raw('amount * -1')]);
|
||||
|
||||
$this->info(sprintf('Fixed %d budget limit(s)', $count));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
@@ -25,7 +25,9 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class FixIbans
|
||||
@@ -53,6 +55,63 @@ class FixIbans extends Command
|
||||
public function handle(): int
|
||||
{
|
||||
$accounts = Account::whereNotNull('iban')->get();
|
||||
$this->filterIbans($accounts);
|
||||
$this->countAndCorrectIbans($accounts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @return void
|
||||
*/
|
||||
private function countAndCorrectIbans(Collection $accounts): void
|
||||
{
|
||||
$set = [];
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$userId = (int)$account->user_id;
|
||||
$set[$userId] = $set[$userId] ?? [];
|
||||
$iban = (string)$account->iban;
|
||||
if ('' === $iban) {
|
||||
continue;
|
||||
}
|
||||
$type = $account->accountType->type;
|
||||
if (in_array($type, [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true)) {
|
||||
$type = 'liabilities';
|
||||
}
|
||||
if (array_key_exists($iban, $set[$userId])) {
|
||||
// iban already in use! two exceptions exist:
|
||||
if (
|
||||
!(AccountType::EXPENSE === $set[$userId][$iban] && AccountType::REVENUE === $type) && // allowed combination
|
||||
!(AccountType::REVENUE === $set[$userId][$iban] && AccountType::EXPENSE === $type) // also allowed combination.
|
||||
) {
|
||||
$this->line(
|
||||
sprintf(
|
||||
'IBAN "%s" is used more than once and will be removed from %s #%d ("%s")',
|
||||
$iban,
|
||||
$account->accountType->type,
|
||||
$account->id,
|
||||
$account->name
|
||||
)
|
||||
);
|
||||
$account->iban = null;
|
||||
$account->save();
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists($iban, $set[$userId])) {
|
||||
$set[$userId][$iban] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @return void
|
||||
*/
|
||||
private function filterIbans(Collection $accounts): void
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$iban = $account->iban;
|
||||
@@ -65,7 +124,5 @@ class FixIbans extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@@ -70,19 +70,6 @@ class FixRecurringTransactions extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*
|
||||
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->recurringRepos = app(RecurringRepositoryInterface::class);
|
||||
$this->userRepos = app(UserRepositoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -95,19 +82,6 @@ class FixRecurringTransactions extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
private function processUser(User $user): void
|
||||
{
|
||||
$this->recurringRepos->setUser($user);
|
||||
$recurrences = $this->recurringRepos->get();
|
||||
/** @var Recurrence $recurrence */
|
||||
foreach ($recurrences as $recurrence) {
|
||||
$this->processRecurrence($recurrence);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Recurrence $recurrence
|
||||
*/
|
||||
@@ -140,4 +114,30 @@ class FixRecurringTransactions extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
private function processUser(User $user): void
|
||||
{
|
||||
$this->recurringRepos->setUser($user);
|
||||
$recurrences = $this->recurringRepos->get();
|
||||
/** @var Recurrence $recurrence */
|
||||
foreach ($recurrences as $recurrence) {
|
||||
$this->processRecurrence($recurrence);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*
|
||||
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->recurringRepos = app(RecurringRepositoryInterface::class);
|
||||
$this->userRepos = app(UserRepositoryInterface::class);
|
||||
}
|
||||
}
|
||||
|
@@ -78,6 +78,19 @@ class FixTransactionTypes extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param string $expectedType
|
||||
*/
|
||||
private function changeJournal(TransactionJournal $journal, string $expectedType): void
|
||||
{
|
||||
$type = TransactionType::whereType($expectedType)->first();
|
||||
if (null !== $type) {
|
||||
$journal->transaction_type_id = $type->id;
|
||||
$journal->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all transaction journals.
|
||||
*
|
||||
@@ -125,36 +138,6 @@ class FixTransactionTypes extends Command
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return Account
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getSourceAccount(TransactionJournal $journal): Account
|
||||
{
|
||||
$collection = $journal->transactions->filter(
|
||||
static function (Transaction $transaction) {
|
||||
return $transaction->amount < 0;
|
||||
}
|
||||
);
|
||||
if (0 === $collection->count()) {
|
||||
throw new FireflyException(sprintf('300001: Journal #%d has no source transaction.', $journal->id));
|
||||
}
|
||||
if (1 !== $collection->count()) {
|
||||
throw new FireflyException(sprintf('300002: Journal #%d has multiple source transactions.', $journal->id));
|
||||
}
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $collection->first();
|
||||
/** @var Account|null $account */
|
||||
$account = $transaction->account;
|
||||
if (null === $account) {
|
||||
throw new FireflyException(sprintf('300003: Journal #%d, transaction #%d has no source account.', $journal->id, $transaction->id));
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
@@ -187,14 +170,31 @@ class FixTransactionTypes extends Command
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param string $expectedType
|
||||
*
|
||||
* @return Account
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function changeJournal(TransactionJournal $journal, string $expectedType): void
|
||||
private function getSourceAccount(TransactionJournal $journal): Account
|
||||
{
|
||||
$type = TransactionType::whereType($expectedType)->first();
|
||||
if (null !== $type) {
|
||||
$journal->transaction_type_id = $type->id;
|
||||
$journal->save();
|
||||
$collection = $journal->transactions->filter(
|
||||
static function (Transaction $transaction) {
|
||||
return $transaction->amount < 0;
|
||||
}
|
||||
);
|
||||
if (0 === $collection->count()) {
|
||||
throw new FireflyException(sprintf('300001: Journal #%d has no source transaction.', $journal->id));
|
||||
}
|
||||
if (1 !== $collection->count()) {
|
||||
throw new FireflyException(sprintf('300002: Journal #%d has multiple source transactions.', $journal->id));
|
||||
}
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $collection->first();
|
||||
/** @var Account|null $account */
|
||||
$account = $transaction->account;
|
||||
if (null === $account) {
|
||||
throw new FireflyException(sprintf('300003: Journal #%d, transaction #%d has no source account.', $journal->id, $transaction->id));
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
}
|
||||
|
@@ -39,17 +39,6 @@ class TriggerCreditCalculation extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function processAccounts(): void
|
||||
{
|
||||
$accounts = Account::leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
|
||||
->whereIn('account_types.type', config('firefly.valid_liabilities'))
|
||||
->get(['accounts.*']);
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Processing account #%d ("%s")', $account->id, $account->name));
|
||||
$this->processAccount($account);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @return void
|
||||
@@ -61,4 +50,15 @@ class TriggerCreditCalculation extends Command
|
||||
$object->setAccount($account);
|
||||
$object->recalculate();
|
||||
}
|
||||
|
||||
private function processAccounts(): void
|
||||
{
|
||||
$accounts = Account::leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
|
||||
->whereIn('account_types.type', config('firefly.valid_liabilities'))
|
||||
->get(['accounts.*']);
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Processing account #%d ("%s")', $account->id, $account->name));
|
||||
$this->processAccount($account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -51,39 +51,6 @@ class CreateGroupMemberships extends Command
|
||||
*/
|
||||
protected $signature = 'firefly-iii:create-group-memberships';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$start = microtime(true);
|
||||
|
||||
$this->createGroupMemberships();
|
||||
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Validated group memberships in %s seconds.', $end));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function createGroupMemberships(): void
|
||||
{
|
||||
$users = User::get();
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
Log::debug(sprintf('Manage group memberships for user #%d', $user->id));
|
||||
self::createGroupMembership($user);
|
||||
Log::debug(sprintf('Done with user #%d', $user->id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO move to helper.
|
||||
* @param User $user
|
||||
@@ -125,4 +92,37 @@ class CreateGroupMemberships extends Command
|
||||
|
||||
Log::debug(sprintf('User #%d now has main group.', $user->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$start = microtime(true);
|
||||
|
||||
$this->createGroupMemberships();
|
||||
|
||||
$end = round(microtime(true) - $start, 2);
|
||||
$this->info(sprintf('Validated group memberships in %s seconds.', $end));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function createGroupMemberships(): void
|
||||
{
|
||||
$users = User::get();
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
Log::debug(sprintf('Manage group memberships for user #%d', $user->id));
|
||||
self::createGroupMembership($user);
|
||||
Log::debug(sprintf('Done with user #%d', $user->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -67,6 +67,51 @@ class ReportEmptyObjects extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on accounts with no transactions.
|
||||
*/
|
||||
private function reportAccounts(): void
|
||||
{
|
||||
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('users', 'accounts.user_id', '=', 'users.id')
|
||||
->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
|
||||
->whereNull('transactions.account_id')
|
||||
->get(
|
||||
['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']
|
||||
);
|
||||
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$line = 'User #%d (%s) has account #%d ("%s") which has no transactions.';
|
||||
$line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $entry->name);
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on budgets with no budget limits (which makes them pointless).
|
||||
*/
|
||||
private function reportBudgetLimits(): void
|
||||
{
|
||||
$set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
|
||||
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
|
||||
->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email'])
|
||||
->whereNull('budget_limits.id')
|
||||
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email']);
|
||||
|
||||
/** @var Budget $entry */
|
||||
foreach ($set as $entry) {
|
||||
$line = sprintf(
|
||||
'User #%d (%s) has budget #%d ("%s") which has no budget limits.',
|
||||
$entry->user_id,
|
||||
$entry->email,
|
||||
$entry->id,
|
||||
$entry->name
|
||||
);
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report on budgets with no transactions or journals.
|
||||
*/
|
||||
@@ -141,49 +186,4 @@ class ReportEmptyObjects extends Command
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on accounts with no transactions.
|
||||
*/
|
||||
private function reportAccounts(): void
|
||||
{
|
||||
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('users', 'accounts.user_id', '=', 'users.id')
|
||||
->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
|
||||
->whereNull('transactions.account_id')
|
||||
->get(
|
||||
['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']
|
||||
);
|
||||
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$line = 'User #%d (%s) has account #%d ("%s") which has no transactions.';
|
||||
$line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $entry->name);
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on budgets with no budget limits (which makes them pointless).
|
||||
*/
|
||||
private function reportBudgetLimits(): void
|
||||
{
|
||||
$set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
|
||||
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
|
||||
->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email'])
|
||||
->whereNull('budget_limits.id')
|
||||
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email']);
|
||||
|
||||
/** @var Budget $entry */
|
||||
foreach ($set as $entry) {
|
||||
$line = sprintf(
|
||||
'User #%d (%s) has budget #%d ("%s") which has no budget limits.',
|
||||
$entry->user_id,
|
||||
$entry->email,
|
||||
$entry->id,
|
||||
$entry->name
|
||||
);
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -57,14 +57,15 @@ class ReportIntegrity extends Command
|
||||
return 1;
|
||||
}
|
||||
$commands = [
|
||||
'firefly-iii:create-group-memberships',
|
||||
'firefly-iii:report-empty-objects',
|
||||
'firefly-iii:report-sum',
|
||||
'firefly-iii:restore-oauth-keys',
|
||||
'firefly-iii:upgrade-group-information',
|
||||
];
|
||||
foreach ($commands as $command) {
|
||||
$this->line(sprintf('Now executing %s', $command));
|
||||
Artisan::call($command);
|
||||
$result = Artisan::output();
|
||||
echo $result;
|
||||
$this->call($command);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@@ -58,6 +58,38 @@ class RestoreOAuthKeys extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function generateKeys(): void
|
||||
{
|
||||
OAuthKeys::generateKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function keysInDatabase(): bool
|
||||
{
|
||||
return OAuthKeys::keysInDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function keysOnDrive(): bool
|
||||
{
|
||||
return OAuthKeys::hasKeyFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function restoreKeysFromDB(): bool
|
||||
{
|
||||
return OAuthKeys::restoreKeysFromDB();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -97,30 +129,6 @@ class RestoreOAuthKeys extends Command
|
||||
$this->line('OAuth keys are OK');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function keysInDatabase(): bool
|
||||
{
|
||||
return OAuthKeys::keysInDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function keysOnDrive(): bool
|
||||
{
|
||||
return OAuthKeys::hasKeyFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function generateKeys(): void
|
||||
{
|
||||
OAuthKeys::generateKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -128,12 +136,4 @@ class RestoreOAuthKeys extends Command
|
||||
{
|
||||
OAuthKeys::storeKeysInDB();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function restoreKeysFromDB(): bool
|
||||
{
|
||||
return OAuthKeys::restoreKeysFromDB();
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
/*
|
||||
* CreateDatabase.php
|
||||
* Copyright (c) 2020 james@firefly-iii.org
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use PDO;
|
@@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
* CreateFirstUser.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
551
app/Console/Commands/System/ForceDecimalSize.php
Normal file
551
app/Console/Commands/System/ForceDecimalSize.php
Normal file
@@ -0,0 +1,551 @@
|
||||
<?php
|
||||
/*
|
||||
* ForceDecimalSize.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AutoBudget;
|
||||
use FireflyIII\Models\AvailableBudget;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\PiggyBankRepetition;
|
||||
use FireflyIII\Models\RecurrenceTransaction;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class ForceDecimalSize
|
||||
*
|
||||
* This command was inspired by https://github.com/elliot-gh. It will check all amount fields
|
||||
* and their values and correct them to the correct number of decimal places. This fixes issues where
|
||||
* Firefly III would store 0.01 as 0.01000000000000000020816681711721685132943093776702880859375.
|
||||
*/
|
||||
class ForceDecimalSize extends Command
|
||||
{
|
||||
protected $description = 'This command resizes DECIMAL columns in MySQL or PostgreSQL and correct amounts (only MySQL).';
|
||||
protected $signature = 'firefly-iii:force-decimal-size';
|
||||
private string $cast;
|
||||
private array $classes = [
|
||||
'accounts' => Account::class,
|
||||
'auto_budgets' => AutoBudget::class,
|
||||
'available_budgets' => AvailableBudget::class,
|
||||
'bills' => Bill::class,
|
||||
'budget_limits' => BudgetLimit::class,
|
||||
'piggy_bank_events' => PiggyBankEvent::class,
|
||||
'piggy_bank_repetitions' => PiggyBankRepetition::class,
|
||||
'piggy_banks' => PiggyBank::class,
|
||||
'recurrences_transactions' => RecurrenceTransaction::class,
|
||||
'transactions' => Transaction::class,
|
||||
];
|
||||
|
||||
private string $operator;
|
||||
private string $regularExpression;
|
||||
private array $tables = [
|
||||
'accounts' => ['virtual_balance'],
|
||||
'auto_budgets' => ['amount'],
|
||||
'available_budgets' => ['amount'],
|
||||
'bills' => ['amount_min', 'amount_max'],
|
||||
'budget_limits' => ['amount'],
|
||||
'currency_exchange_rates' => ['rate', 'user_rate'],
|
||||
'limit_repetitions' => ['amount'],
|
||||
'piggy_bank_events' => ['amount'],
|
||||
'piggy_bank_repetitions' => ['currentamount'],
|
||||
'piggy_banks' => ['targetamount'],
|
||||
'recurrences_transactions' => ['amount', 'foreign_amount'],
|
||||
'transactions' => ['amount', 'foreign_amount'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
Log::debug('Now in ForceDecimalSize::handle()');
|
||||
$this->determineDatabaseType();
|
||||
|
||||
$this->error('Running this command is dangerous and can cause data loss.');
|
||||
$this->error('Please do not continue.');
|
||||
$question = $this->confirm('Do you want to continue?');
|
||||
if (true === $question) {
|
||||
$this->correctAmounts();
|
||||
$this->updateDecimals();
|
||||
}
|
||||
$this->line('Done!');
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method loops over all accounts and validates the amounts.
|
||||
*
|
||||
* @param TransactionCurrency $currency
|
||||
* @param array $fields
|
||||
* @return void
|
||||
*/
|
||||
private function correctAccountAmounts(TransactionCurrency $currency, array $fields): void
|
||||
{
|
||||
$operator = $this->operator;
|
||||
$cast = $this->cast;
|
||||
$regularExpression = $this->regularExpression;
|
||||
|
||||
/** @var Builder $query */
|
||||
$query = Account::leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
|
||||
->where('account_meta.name', 'currency_id')
|
||||
->where('account_meta.data', json_encode((string)$currency->id));
|
||||
$query->where(static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression) {
|
||||
foreach ($fields as $field) {
|
||||
$q->orWhere(
|
||||
DB::raw(sprintf('CAST(accounts.%s AS %s)', $field, $cast)),
|
||||
$operator,
|
||||
DB::raw(sprintf($regularExpression, $currency->decimal_places))
|
||||
);
|
||||
}
|
||||
});
|
||||
$result = $query->get(['accounts.*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->line(sprintf('Correct: All accounts in %s', $currency->code));
|
||||
return;
|
||||
}
|
||||
/** @var Account $account */
|
||||
foreach ($result as $account) {
|
||||
foreach ($fields as $field) {
|
||||
$value = $account->$field;
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = pow(10, (int)$currency->decimal_places);
|
||||
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
|
||||
$this->line(sprintf('Account #%d has %s with value "%s", this has been corrected to "%s".', $account->id, $field, $value, $correct));
|
||||
Account::find($account->id)->update([$field => $correct]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if a basic check can be done or if it needs to be complicated.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function correctAmounts(): void
|
||||
{
|
||||
// if sqlite, add function?
|
||||
if ('sqlite' === (string)config('database.default')) {
|
||||
DB::connection()->getPdo()->sqliteCreateFunction('REGEXP', function ($pattern, $value) {
|
||||
mb_regex_encoding('UTF-8');
|
||||
$pattern = trim($pattern, '"');
|
||||
return (false !== mb_ereg($pattern, (string) $value)) ? 1 : 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (!in_array((string)config('database.default'), ['mysql', 'pgsql', 'sqlite'], true)) {
|
||||
$this->line(sprintf('Skip correcting amounts, does not support "%s"...', (string)config('database.default')));
|
||||
return;
|
||||
}
|
||||
$this->correctAmountsByCurrency();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method loops all enabled currencies and then calls the method that will fix all objects in this currency.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function correctAmountsByCurrency(): void
|
||||
{
|
||||
$this->line('Going to correct amounts.');
|
||||
/** @var Collection $enabled */
|
||||
$enabled = TransactionCurrency::whereEnabled(1)->get();
|
||||
/** @var TransactionCurrency $currency */
|
||||
foreach ($enabled as $currency) {
|
||||
$this->correctByCurrency($currency);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method loops the available tables that may need fixing, and calls for the right method that can fix them.
|
||||
*
|
||||
* @param TransactionCurrency $currency
|
||||
* @return void
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function correctByCurrency(TransactionCurrency $currency): void
|
||||
{
|
||||
$this->line(sprintf('Going to correct amounts in currency %s ("%s").', $currency->code, $currency->name));
|
||||
/**
|
||||
* @var string $name
|
||||
* @var array $fields
|
||||
*/
|
||||
foreach ($this->tables as $name => $fields) {
|
||||
switch ($name) {
|
||||
default:
|
||||
$message = sprintf('Cannot handle table "%s"', $name);
|
||||
$this->line($message);
|
||||
throw new FireflyException($message);
|
||||
case 'accounts':
|
||||
$this->correctAccountAmounts($currency, $fields);
|
||||
break;
|
||||
case 'auto_budgets':
|
||||
case 'available_budgets':
|
||||
case 'bills':
|
||||
case 'budget_limits':
|
||||
case 'recurrences_transactions':
|
||||
$this->correctGeneric($currency, $name);
|
||||
break;
|
||||
case 'currency_exchange_rates':
|
||||
case 'limit_repetitions':
|
||||
// do nothing
|
||||
break;
|
||||
case 'piggy_bank_events':
|
||||
$this->correctPiggyEventAmounts($currency, $fields);
|
||||
break;
|
||||
case 'piggy_bank_repetitions':
|
||||
$this->correctPiggyRepetitionAmounts($currency, $fields);
|
||||
break;
|
||||
case 'piggy_banks':
|
||||
$this->correctPiggyAmounts($currency, $fields);
|
||||
break;
|
||||
case 'transactions':
|
||||
$this->correctTransactionAmounts($currency);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fixes all auto budgets in currency $currency.
|
||||
* @param TransactionCurrency $currency
|
||||
* @param string $table
|
||||
* @return void
|
||||
*/
|
||||
private function correctGeneric(TransactionCurrency $currency, string $table): void
|
||||
{
|
||||
$class = $this->classes[$table];
|
||||
$fields = $this->tables[$table];
|
||||
$operator = $this->operator;
|
||||
$cast = $this->cast;
|
||||
$regularExpression = $this->regularExpression;
|
||||
|
||||
/** @var Builder $query */
|
||||
$query = $class::where('transaction_currency_id', $currency->id)->where(
|
||||
static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression) {
|
||||
foreach ($fields as $field) {
|
||||
$q->orWhere(
|
||||
DB::raw(sprintf('CAST(%s AS %s)', $field, $cast)),
|
||||
$operator,
|
||||
DB::raw(sprintf($regularExpression, $currency->decimal_places))
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$result = $query->get(['*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->line(sprintf('Correct: All %s in %s', $table, $currency->code));
|
||||
return;
|
||||
}
|
||||
/** @var Model $item */
|
||||
foreach ($result as $item) {
|
||||
foreach ($fields as $field) {
|
||||
$value = $item->$field;
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = pow(10, (int)$currency->decimal_places);
|
||||
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
|
||||
$this->line(sprintf('%s #%d has %s with value "%s", this has been corrected to "%s".', $table, $item->id, $field, $value, $correct));
|
||||
$class::find($item->id)->update([$field => $correct]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fixes all piggy banks in currency $currency.
|
||||
*
|
||||
* @param TransactionCurrency $currency
|
||||
* @param array $fields
|
||||
* @return void
|
||||
*/
|
||||
private function correctPiggyAmounts(TransactionCurrency $currency, array $fields): void
|
||||
{
|
||||
$operator = $this->operator;
|
||||
$cast = $this->cast;
|
||||
$regularExpression = $this->regularExpression;
|
||||
|
||||
/** @var Builder $query */
|
||||
$query = PiggyBank::leftJoin('accounts', 'piggy_banks.account_id', '=', 'accounts.id')
|
||||
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
|
||||
->where('account_meta.name', 'currency_id')
|
||||
->where('account_meta.data', json_encode((string)$currency->id))
|
||||
->where(static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression) {
|
||||
foreach ($fields as $field) {
|
||||
$q->orWhere(
|
||||
DB::raw(sprintf('CAST(piggy_banks.%s AS %s)', $field, $cast)),
|
||||
$operator,
|
||||
DB::raw(sprintf($regularExpression, $currency->decimal_places))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$result = $query->get(['piggy_banks.*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->line(sprintf('Correct: All piggy banks in %s', $currency->code));
|
||||
return;
|
||||
}
|
||||
/** @var PiggyBank $item */
|
||||
foreach ($result as $item) {
|
||||
foreach ($fields as $field) {
|
||||
$value = $item->$field;
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = pow(10, (int)$currency->decimal_places);
|
||||
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
|
||||
$this->line(sprintf('Piggy bank #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct));
|
||||
PiggyBank::find($item->id)->update([$field => $correct]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fixes all piggy bank events in currency $currency.
|
||||
* @param TransactionCurrency $currency
|
||||
* @param array $fields
|
||||
* @return void
|
||||
*/
|
||||
private function correctPiggyEventAmounts(TransactionCurrency $currency, array $fields): void
|
||||
{
|
||||
$operator = $this->operator;
|
||||
$cast = $this->cast;
|
||||
$regularExpression = $this->regularExpression;
|
||||
|
||||
/** @var Builder $query */
|
||||
$query = PiggyBankEvent::leftJoin('piggy_banks', 'piggy_bank_events.piggy_bank_id', '=', 'piggy_banks.id')
|
||||
->leftJoin('accounts', 'piggy_banks.account_id', '=', 'accounts.id')
|
||||
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
|
||||
->where('account_meta.name', 'currency_id')
|
||||
->where('account_meta.data', json_encode((string)$currency->id))
|
||||
->where(static function (Builder $q) use ($fields, $currency, $cast, $operator, $regularExpression) {
|
||||
foreach ($fields as $field) {
|
||||
$q->orWhere(
|
||||
DB::raw(sprintf('CAST(piggy_bank_events.%s AS %s)', $field, $cast)),
|
||||
$operator,
|
||||
DB::raw(sprintf($regularExpression, $currency->decimal_places))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$result = $query->get(['piggy_bank_events.*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->line(sprintf('Correct: All piggy bank events in %s', $currency->code));
|
||||
return;
|
||||
}
|
||||
/** @var PiggyBankEvent $item */
|
||||
foreach ($result as $item) {
|
||||
foreach ($fields as $field) {
|
||||
$value = $item->$field;
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = pow(10, (int)$currency->decimal_places);
|
||||
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
|
||||
$this->line(sprintf('Piggy bank event #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct));
|
||||
PiggyBankEvent::find($item->id)->update([$field => $correct]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fixes all piggy bank repetitions in currency $currency.
|
||||
*
|
||||
* @param TransactionCurrency $currency
|
||||
* @param array $fields
|
||||
* @return void
|
||||
*/
|
||||
private function correctPiggyRepetitionAmounts(TransactionCurrency $currency, array $fields)
|
||||
{
|
||||
$operator = $this->operator;
|
||||
$cast = $this->cast;
|
||||
$regularExpression = $this->regularExpression;
|
||||
// select all piggy bank repetitions with this currency and issue.
|
||||
/** @var Builder $query */
|
||||
$query = PiggyBankRepetition::leftJoin('piggy_banks', 'piggy_bank_repetitions.piggy_bank_id', '=', 'piggy_banks.id')
|
||||
->leftJoin('accounts', 'piggy_banks.account_id', '=', 'accounts.id')
|
||||
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
|
||||
->where('account_meta.name', 'currency_id')
|
||||
->where('account_meta.data', json_encode((string)$currency->id))
|
||||
->where(static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression) {
|
||||
foreach ($fields as $field) {
|
||||
$q->orWhere(
|
||||
DB::raw(sprintf('CAST(piggy_bank_repetitions.%s AS %s)', $field, $cast)),
|
||||
$operator,
|
||||
DB::raw(sprintf($regularExpression, $currency->decimal_places))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$result = $query->get(['piggy_bank_repetitions.*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->line(sprintf('Correct: All piggy bank repetitions in %s', $currency->code));
|
||||
return;
|
||||
}
|
||||
/** @var PiggyBankRepetition $item */
|
||||
foreach ($result as $item) {
|
||||
foreach ($fields as $field) {
|
||||
$value = $item->$field;
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = pow(10, (int)$currency->decimal_places);
|
||||
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
|
||||
$this->line(sprintf('Piggy bank repetition #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct));
|
||||
PiggyBankRepetition::find($item->id)->update([$field => $correct]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fixes all transactions in currency $currency.
|
||||
*
|
||||
* @param TransactionCurrency $currency
|
||||
* @return void
|
||||
*/
|
||||
private function correctTransactionAmounts(TransactionCurrency $currency): void
|
||||
{
|
||||
// select all transactions with this currency and issue.
|
||||
/** @var Builder $query */
|
||||
$query = Transaction::where('transaction_currency_id', $currency->id)->where(
|
||||
DB::raw(sprintf('CAST(amount as %s)', $this->cast)),
|
||||
$this->operator,
|
||||
DB::raw(sprintf($this->regularExpression, $currency->decimal_places))
|
||||
);
|
||||
|
||||
$result = $query->get(['transactions.*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->line(sprintf('Correct: All transactions in %s', $currency->code));
|
||||
}
|
||||
|
||||
/** @var Transaction $item */
|
||||
foreach ($result as $item) {
|
||||
$value = $item->amount;
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = pow(10, (int)$currency->decimal_places);
|
||||
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
|
||||
$this->line(sprintf('Transaction #%d has amount with value "%s", this has been corrected to "%s".', $item->id, $value, $correct));
|
||||
Transaction::find($item->id)->update(['amount' => $correct]);
|
||||
}
|
||||
|
||||
// select all transactions with this FOREIGN currency and issue.
|
||||
/** @var Builder $query */
|
||||
$query = Transaction::where('foreign_currency_id', $currency->id)->where(
|
||||
DB::raw(sprintf('CAST(foreign_amount as %s)', $this->cast)),
|
||||
$this->operator,
|
||||
DB::raw(sprintf($this->regularExpression, $currency->decimal_places))
|
||||
);
|
||||
|
||||
$result = $query->get(['*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->line(sprintf('Correct: All transactions in foreign currency %s', $currency->code));
|
||||
return;
|
||||
}
|
||||
/** @var Transaction $item */
|
||||
foreach ($result as $item) {
|
||||
$value = $item->foreign_amount;
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = pow(10, (int)$currency->decimal_places);
|
||||
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
|
||||
$this->line(sprintf('Transaction #%d has foreign amount with value "%s", this has been corrected to "%s".', $item->id, $value, $correct));
|
||||
Transaction::find($item->id)->update(['foreign_amount' => $correct]);
|
||||
}
|
||||
}
|
||||
|
||||
private function determineDatabaseType(): void
|
||||
{
|
||||
// switch stuff based on database connection:
|
||||
$this->operator = 'REGEXP';
|
||||
$this->regularExpression = '\'\\\\.[\\\\d]{%d}[1-9]+\'';
|
||||
$this->cast = 'CHAR';
|
||||
if ('pgsql' === config('database.default')) {
|
||||
$this->operator = 'SIMILAR TO';
|
||||
$this->regularExpression = '\'%%\.[\d]{%d}[1-9]+%%\'';
|
||||
$this->cast = 'TEXT';
|
||||
}
|
||||
if ('sqlite' === config('database.default')) {
|
||||
$this->regularExpression = '"\\.[\d]{%d}[1-9]+"';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function updateDecimals(): void
|
||||
{
|
||||
$this->info('Going to force the size of DECIMAL columns. Please hold.');
|
||||
$type = (string)config('database.default');
|
||||
|
||||
/**
|
||||
* @var string $name
|
||||
* @var array $fields
|
||||
*/
|
||||
foreach ($this->tables as $name => $fields) {
|
||||
/** @var string $field */
|
||||
foreach ($fields as $field) {
|
||||
$this->line(sprintf('Updating table "%s", field "%s"...', $name, $field));
|
||||
|
||||
switch ($type) {
|
||||
default:
|
||||
$this->error(sprintf('Cannot handle database type "%s".', $type));
|
||||
return;
|
||||
case 'pgsql':
|
||||
$query = sprintf('ALTER TABLE %s ALTER COLUMN %s TYPE DECIMAL(32,12);', $name, $field);
|
||||
break;
|
||||
case 'mysql':
|
||||
$query = sprintf('ALTER TABLE %s CHANGE COLUMN %s %s DECIMAL(32, 12);', $name, $field, $field);
|
||||
break;
|
||||
}
|
||||
|
||||
DB::select($query);
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,29 @@
|
||||
<?php
|
||||
/*
|
||||
* ForceMigration.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use FireflyIII\Console\Commands\VerifiesAccessToken;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
@@ -15,6 +35,12 @@ class ForceMigration extends Command
|
||||
{
|
||||
use VerifiesAccessToken;
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This command will force-run all database migrations.';
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
@@ -24,13 +50,6 @@ class ForceMigration extends Command
|
||||
{--user=1 : The user ID.}
|
||||
{--token= : The user\'s access token.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This command will force-run all database migrations.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* @throws FireflyException
|
||||
@@ -57,6 +76,7 @@ class ForceMigration extends Command
|
||||
|
||||
private function forceMigration(): void
|
||||
{
|
||||
DB::commit();
|
||||
$this->line('Dropping "migrations" table...');
|
||||
sleep(2);
|
||||
Schema::dropIfExists('migrations');
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
/*
|
||||
* ScanAttachments.php
|
||||
* Copyright (c) 2020 james@firefly-iii.org
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use Crypt;
|
||||
use FireflyIII\Models\Attachment;
|
||||
@@ -42,7 +42,7 @@ class ScanAttachments extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Rescan all attachments and re-set the MD5 hash and mime.';
|
||||
protected $description = 'Rescan all attachments and re-set the correct MD5 hash and mime.';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
/*
|
||||
* SetLatestVersion.php
|
||||
* Copyright (c) 2020 james@firefly-iii.org
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
/*
|
||||
* UpgradeFireflyInstructions.php
|
||||
* Copyright (c) 2020 james@firefly-iii.org
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -21,11 +21,13 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use FireflyIII\Support\System\GeneratesInstallationId;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use function FireflyIII\Console\Commands\str_starts_with;
|
||||
|
||||
/**
|
||||
* Class UpgradeFireflyInstructions.
|
||||
*
|
||||
@@ -64,51 +66,6 @@ class UpgradeFireflyInstructions extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render upgrade instructions.
|
||||
*/
|
||||
private function updateInstructions(): void
|
||||
{
|
||||
/** @var string $version */
|
||||
$version = config('firefly.version');
|
||||
$config = config('upgrade.text.upgrade');
|
||||
$text = '';
|
||||
foreach (array_keys($config) as $compare) {
|
||||
// if string starts with:
|
||||
if (str_starts_with($version, $compare)) {
|
||||
$text = $config[$compare];
|
||||
}
|
||||
}
|
||||
|
||||
$this->showLine();
|
||||
$this->boxed('');
|
||||
if (null === $text) {
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s', $version));
|
||||
$this->boxedInfo('There are no extra upgrade instructions.');
|
||||
$this->boxed('Firefly III should be ready for use.');
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s!', $version));
|
||||
$this->boxedInfo($text);
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a line.
|
||||
*/
|
||||
private function showLine(): void
|
||||
{
|
||||
$line = '+';
|
||||
$line .= str_repeat('-', 78);
|
||||
$line .= '+';
|
||||
$this->line($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a nice box.
|
||||
*
|
||||
@@ -146,13 +103,13 @@ class UpgradeFireflyInstructions extends Command
|
||||
$text = '';
|
||||
foreach (array_keys($config) as $compare) {
|
||||
// if string starts with:
|
||||
if (str_starts_with($version, $compare)) {
|
||||
if (\str_starts_with($version, $compare)) {
|
||||
$text = $config[$compare];
|
||||
}
|
||||
}
|
||||
$this->showLine();
|
||||
$this->boxed('');
|
||||
if (null === $text) {
|
||||
if (null === $text || '' === $text) {
|
||||
$this->boxed(sprintf('Thank you for installing Firefly III, v%s!', $version));
|
||||
$this->boxedInfo('There are no extra installation instructions.');
|
||||
$this->boxed('Firefly III should be ready for use.');
|
||||
@@ -167,4 +124,49 @@ class UpgradeFireflyInstructions extends Command
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a line.
|
||||
*/
|
||||
private function showLine(): void
|
||||
{
|
||||
$line = '+';
|
||||
$line .= str_repeat('-', 78);
|
||||
$line .= '+';
|
||||
$this->line($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render upgrade instructions.
|
||||
*/
|
||||
private function updateInstructions(): void
|
||||
{
|
||||
/** @var string $version */
|
||||
$version = config('firefly.version');
|
||||
$config = config('upgrade.text.upgrade');
|
||||
$text = '';
|
||||
foreach (array_keys($config) as $compare) {
|
||||
// if string starts with:
|
||||
if (\str_starts_with($version, $compare)) {
|
||||
$text = $config[$compare];
|
||||
}
|
||||
}
|
||||
|
||||
$this->showLine();
|
||||
$this->boxed('');
|
||||
if (null === $text || '' === $text) {
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s', $version));
|
||||
$this->boxedInfo('There are no extra upgrade instructions.');
|
||||
$this->boxed('Firefly III should be ready for use.');
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s!', $version));
|
||||
$this->boxedInfo($text);
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
* VerifySecurityAlerts.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\QueryException;
|
||||
use League\Flysystem\FilesystemException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Flysystem\FilesystemException;
|
||||
use Storage;
|
||||
|
||||
/**
|
@@ -31,8 +31,8 @@ use FireflyIII\Support\Cronjobs\BillWarningCronjob;
|
||||
use FireflyIII\Support\Cronjobs\ExchangeRatesCronjob;
|
||||
use FireflyIII\Support\Cronjobs\RecurringCronjob;
|
||||
use Illuminate\Console\Command;
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
@@ -123,62 +123,6 @@ class Cron extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $force
|
||||
* @param Carbon|null $date
|
||||
*/
|
||||
private function exchangeRatesCronJob(bool $force, ?Carbon $date): void
|
||||
{
|
||||
$exchangeRates = new ExchangeRatesCronjob();
|
||||
$exchangeRates->setForce($force);
|
||||
// set date in cron job:
|
||||
if (null !== $date) {
|
||||
$exchangeRates->setDate($date);
|
||||
}
|
||||
|
||||
$exchangeRates->fire();
|
||||
|
||||
if ($exchangeRates->jobErrored) {
|
||||
$this->error(sprintf('Error in "exchange rates" cron: %s', $exchangeRates->message));
|
||||
}
|
||||
if ($exchangeRates->jobFired) {
|
||||
$this->line(sprintf('"Exchange rates" cron fired: %s', $exchangeRates->message));
|
||||
}
|
||||
if ($exchangeRates->jobSucceeded) {
|
||||
$this->info(sprintf('"Exchange rates" cron ran with success: %s', $exchangeRates->message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $force
|
||||
* @param Carbon|null $date
|
||||
*
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws FireflyException
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function recurringCronJob(bool $force, ?Carbon $date): void
|
||||
{
|
||||
$recurring = new RecurringCronjob();
|
||||
$recurring->setForce($force);
|
||||
|
||||
// set date in cron job:
|
||||
if (null !== $date) {
|
||||
$recurring->setDate($date);
|
||||
}
|
||||
|
||||
$recurring->fire();
|
||||
if ($recurring->jobErrored) {
|
||||
$this->error(sprintf('Error in "create recurring transactions" cron: %s', $recurring->message));
|
||||
}
|
||||
if ($recurring->jobFired) {
|
||||
$this->line(sprintf('"Create recurring transactions" cron fired: %s', $recurring->message));
|
||||
}
|
||||
if ($recurring->jobSucceeded) {
|
||||
$this->info(sprintf('"Create recurring transactions" cron ran with success: %s', $recurring->message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $force
|
||||
* @param Carbon|null $date
|
||||
@@ -234,4 +178,60 @@ class Cron extends Command
|
||||
$this->info(sprintf('"Send bill warnings" cron ran with success: %s', $autoBudget->message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $force
|
||||
* @param Carbon|null $date
|
||||
*/
|
||||
private function exchangeRatesCronJob(bool $force, ?Carbon $date): void
|
||||
{
|
||||
$exchangeRates = new ExchangeRatesCronjob();
|
||||
$exchangeRates->setForce($force);
|
||||
// set date in cron job:
|
||||
if (null !== $date) {
|
||||
$exchangeRates->setDate($date);
|
||||
}
|
||||
|
||||
$exchangeRates->fire();
|
||||
|
||||
if ($exchangeRates->jobErrored) {
|
||||
$this->error(sprintf('Error in "exchange rates" cron: %s', $exchangeRates->message));
|
||||
}
|
||||
if ($exchangeRates->jobFired) {
|
||||
$this->line(sprintf('"Exchange rates" cron fired: %s', $exchangeRates->message));
|
||||
}
|
||||
if ($exchangeRates->jobSucceeded) {
|
||||
$this->info(sprintf('"Exchange rates" cron ran with success: %s', $exchangeRates->message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $force
|
||||
* @param Carbon|null $date
|
||||
*
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws FireflyException
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function recurringCronJob(bool $force, ?Carbon $date): void
|
||||
{
|
||||
$recurring = new RecurringCronjob();
|
||||
$recurring->setForce($force);
|
||||
|
||||
// set date in cron job:
|
||||
if (null !== $date) {
|
||||
$recurring->setDate($date);
|
||||
}
|
||||
|
||||
$recurring->fire();
|
||||
if ($recurring->jobErrored) {
|
||||
$this->error(sprintf('Error in "create recurring transactions" cron: %s', $recurring->message));
|
||||
}
|
||||
if ($recurring->jobFired) {
|
||||
$this->line(sprintf('"Create recurring transactions" cron fired: %s', $recurring->message));
|
||||
}
|
||||
if ($recurring->jobSucceeded) {
|
||||
$this->info(sprintf('"Create recurring transactions" cron ran with success: %s', $recurring->message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -96,20 +96,6 @@ class AccountCurrencies extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*
|
||||
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->accountRepos = app(AccountRepositoryInterface::class);
|
||||
$this->userRepos = app(UserRepositoryInterface::class);
|
||||
$this->count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
@@ -128,50 +114,23 @@ class AccountCurrencies extends Command
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function updateAccountCurrencies(): void
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
Log::debug('Now in updateAccountCurrencies()');
|
||||
$users = $this->userRepos->all();
|
||||
$defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR');
|
||||
Log::debug(sprintf('Default currency is %s', $defaultCurrencyCode));
|
||||
foreach ($users as $user) {
|
||||
$this->updateCurrenciesForUser($user, $defaultCurrencyCode);
|
||||
}
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param string $systemCurrencyCode
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*
|
||||
* @throws FireflyException
|
||||
|
||||
*/
|
||||
private function updateCurrenciesForUser(User $user, string $systemCurrencyCode): void
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
Log::debug(sprintf('Now in updateCurrenciesForUser(%s, %s)', $user->email, $systemCurrencyCode));
|
||||
$this->accountRepos->setUser($user);
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
|
||||
// get user's currency preference:
|
||||
$defaultCurrencyCode = app('preferences')->getForUser($user, 'currencyPreference', $systemCurrencyCode)->data;
|
||||
if (!is_string($defaultCurrencyCode)) {
|
||||
$defaultCurrencyCode = $systemCurrencyCode;
|
||||
}
|
||||
Log::debug(sprintf('Users currency pref is %s', $defaultCurrencyCode));
|
||||
|
||||
/** @var TransactionCurrency|null $defaultCurrency */
|
||||
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
|
||||
|
||||
if (null === $defaultCurrency) {
|
||||
Log::error(sprintf('Users currency pref "%s" does not exist!', $defaultCurrencyCode));
|
||||
$this->error(sprintf('User has a preference for "%s", but this currency does not exist.', $defaultCurrencyCode));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->updateAccount($account, $defaultCurrency);
|
||||
}
|
||||
$this->accountRepos = app(AccountRepositoryInterface::class);
|
||||
$this->userRepos = app(UserRepositoryInterface::class);
|
||||
$this->count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,8 +196,49 @@ class AccountCurrencies extends Command
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function markAsExecuted(): void
|
||||
private function updateAccountCurrencies(): void
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
Log::debug('Now in updateAccountCurrencies()');
|
||||
$users = $this->userRepos->all();
|
||||
$defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR');
|
||||
Log::debug(sprintf('Default currency is %s', $defaultCurrencyCode));
|
||||
foreach ($users as $user) {
|
||||
$this->updateCurrenciesForUser($user, $defaultCurrencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param string $systemCurrencyCode
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function updateCurrenciesForUser(User $user, string $systemCurrencyCode): void
|
||||
{
|
||||
Log::debug(sprintf('Now in updateCurrenciesForUser(%s, %s)', $user->email, $systemCurrencyCode));
|
||||
$this->accountRepos->setUser($user);
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
|
||||
// get user's currency preference:
|
||||
$defaultCurrencyCode = app('preferences')->getForUser($user, 'currencyPreference', $systemCurrencyCode)->data;
|
||||
if (!is_string($defaultCurrencyCode)) {
|
||||
$defaultCurrencyCode = $systemCurrencyCode;
|
||||
}
|
||||
Log::debug(sprintf('Users currency pref is %s', $defaultCurrencyCode));
|
||||
|
||||
/** @var TransactionCurrency|null $defaultCurrency */
|
||||
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
|
||||
|
||||
if (null === $defaultCurrency) {
|
||||
Log::error(sprintf('Users currency pref "%s" does not exist!', $defaultCurrencyCode));
|
||||
$this->error(sprintf('User has a preference for "%s", but this currency does not exist.', $defaultCurrencyCode));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->updateAccount($account, $defaultCurrency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -73,30 +73,6 @@ class AppendBudgetLimitPeriods extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function theresNoLimit(): void
|
||||
{
|
||||
$limits = BudgetLimit::whereNull('period')->get();
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($limits as $limit) {
|
||||
$this->fixLimit($limit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BudgetLimit $limit
|
||||
*/
|
||||
@@ -182,6 +158,18 @@ class AppendBudgetLimitPeriods extends Command
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -189,4 +177,16 @@ class AppendBudgetLimitPeriods extends Command
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function theresNoLimit(): void
|
||||
{
|
||||
$limits = BudgetLimit::whereNull('period')->get();
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($limits as $limit) {
|
||||
$this->fixLimit($limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -87,15 +87,39 @@ class BackToJournals extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @return array
|
||||
*/
|
||||
private function isMigrated(): bool
|
||||
private function getIdsForBudgets(): array
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(MigrateToGroups::CONFIG_NAME, false);
|
||||
$transactions = DB::table('budget_transaction')->distinct()->pluck('transaction_id')->toArray();
|
||||
$array = [];
|
||||
$chunks = array_chunk($transactions, 500);
|
||||
|
||||
return (bool)$configVar->data;
|
||||
foreach ($chunks as $chunk) {
|
||||
$set = DB::table('transactions')->whereIn('transactions.id', $chunk)->pluck('transaction_journal_id')->toArray();
|
||||
$array = array_merge($array, $set);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getIdsForCategories(): array
|
||||
{
|
||||
$transactions = DB::table('category_transaction')->distinct()->pluck('transaction_id')->toArray();
|
||||
$array = [];
|
||||
$chunks = array_chunk($transactions, 500);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$set = DB::table('transactions')
|
||||
->whereIn('transactions.id', $chunk)
|
||||
->pluck('transaction_journal_id')->toArray();
|
||||
$array = array_merge($array, $set);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,6 +134,26 @@ class BackToJournals extends Command
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function isMigrated(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(MigrateToGroups::CONFIG_NAME, false);
|
||||
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -143,23 +187,6 @@ class BackToJournals extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getIdsForBudgets(): array
|
||||
{
|
||||
$transactions = DB::table('budget_transaction')->distinct()->pluck('transaction_id')->toArray();
|
||||
$array = [];
|
||||
$chunks = array_chunk($transactions, 500);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$set = DB::table('transactions')->whereIn('transactions.id', $chunk)->pluck('transaction_journal_id')->toArray();
|
||||
$array = array_merge($array, $set);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*/
|
||||
@@ -217,25 +244,6 @@ class BackToJournals extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getIdsForCategories(): array
|
||||
{
|
||||
$transactions = DB::table('category_transaction')->distinct()->pluck('transaction_id')->toArray();
|
||||
$array = [];
|
||||
$chunks = array_chunk($transactions, 500);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$set = DB::table('transactions')
|
||||
->whereIn('transactions.id', $chunk)
|
||||
->pluck('transaction_journal_id')->toArray();
|
||||
$array = array_merge($array, $set);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*/
|
||||
@@ -265,12 +273,4 @@ class BackToJournals extends Command
|
||||
$journal->categories()->sync([(int)$category->id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
/*
|
||||
* DecryptDatabase.php
|
||||
* Copyright (c) 2020 james@firefly-iii.org
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands;
|
||||
namespace FireflyIII\Console\Commands\Upgrade;
|
||||
|
||||
use Crypt;
|
||||
use DB;
|
||||
@@ -30,8 +30,8 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Preference;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use JsonException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use JsonException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use stdClass;
|
||||
@@ -87,6 +87,81 @@ class DecryptDatabase extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param string $field
|
||||
*/
|
||||
private function decryptField(string $table, string $field): void
|
||||
{
|
||||
$rows = DB::table($table)->get(['id', $field]);
|
||||
/** @var stdClass $row */
|
||||
foreach ($rows as $row) {
|
||||
$this->decryptRow($table, $field, $row);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string $value
|
||||
*/
|
||||
private function decryptPreferencesRow(int $id, string $value): void
|
||||
{
|
||||
// try to json_decrypt the value.
|
||||
try {
|
||||
$newValue = json_decode($value, true, 512, JSON_THROW_ON_ERROR) ?? $value;
|
||||
} catch (JsonException $e) {
|
||||
$message = sprintf('Could not JSON decode preference row #%d: %s. This does not have to be a problem.', $id, $e->getMessage());
|
||||
$this->error($message);
|
||||
app('log')->warning($message);
|
||||
app('log')->warning($value);
|
||||
app('log')->warning($e->getTraceAsString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Preference $object */
|
||||
$object = Preference::find((int)$id);
|
||||
if (null !== $object) {
|
||||
$object->data = $newValue;
|
||||
$object->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param string $field
|
||||
* @param stdClass $row
|
||||
*/
|
||||
private function decryptRow(string $table, string $field, stdClass $row): void
|
||||
{
|
||||
$original = $row->$field;
|
||||
if (null === $original) {
|
||||
return;
|
||||
}
|
||||
$id = (int)$row->id;
|
||||
$value = '';
|
||||
|
||||
try {
|
||||
$value = $this->tryDecrypt($original);
|
||||
} catch (FireflyException $e) {
|
||||
$message = sprintf('Could not decrypt field "%s" in row #%d of table "%s": %s', $field, $id, $table, $e->getMessage());
|
||||
$this->error($message);
|
||||
Log::error($message);
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
|
||||
// A separate routine for preferences table:
|
||||
if ('preferences' === $table) {
|
||||
$this->decryptPreferencesRow($id, $value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value !== $original) {
|
||||
DB::table($table)->where('id', $id)->update([$field => $value]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param array $fields
|
||||
@@ -132,54 +207,6 @@ class DecryptDatabase extends Command
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param string $field
|
||||
*/
|
||||
private function decryptField(string $table, string $field): void
|
||||
{
|
||||
$rows = DB::table($table)->get(['id', $field]);
|
||||
/** @var stdClass $row */
|
||||
foreach ($rows as $row) {
|
||||
$this->decryptRow($table, $field, $row);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param string $field
|
||||
* @param stdClass $row
|
||||
*/
|
||||
private function decryptRow(string $table, string $field, stdClass $row): void
|
||||
{
|
||||
$original = $row->$field;
|
||||
if (null === $original) {
|
||||
return;
|
||||
}
|
||||
$id = (int)$row->id;
|
||||
$value = '';
|
||||
|
||||
try {
|
||||
$value = $this->tryDecrypt($original);
|
||||
} catch (FireflyException $e) {
|
||||
$message = sprintf('Could not decrypt field "%s" in row #%d of table "%s": %s', $field, $id, $table, $e->getMessage());
|
||||
$this->error($message);
|
||||
Log::error($message);
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
|
||||
// A separate routine for preferences table:
|
||||
if ('preferences' === $table) {
|
||||
$this->decryptPreferencesRow($id, $value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value !== $original) {
|
||||
DB::table($table)->where('id', $id)->update([$field => $value]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to decrypt data. Will only throw an exception when the MAC is invalid.
|
||||
*
|
||||
@@ -200,31 +227,4 @@ class DecryptDatabase extends Command
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string $value
|
||||
*/
|
||||
private function decryptPreferencesRow(int $id, string $value): void
|
||||
{
|
||||
// try to json_decrypt the value.
|
||||
try {
|
||||
$newValue = json_decode($value, true, 512, JSON_THROW_ON_ERROR) ?? $value;
|
||||
} catch (JsonException $e) {
|
||||
$message = sprintf('Could not JSON decode preference row #%d: %s. This does not have to be a problem.', $id, $e->getMessage());
|
||||
$this->error($message);
|
||||
app('log')->warning($message);
|
||||
app('log')->warning($value);
|
||||
app('log')->warning($e->getTraceAsString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Preference $object */
|
||||
$object = Preference::find((int)$id);
|
||||
if (null !== $object) {
|
||||
$object->data = $newValue;
|
||||
$object->save();
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
* FixPostgresSequences.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
namespace FireflyIII\Console\Commands\Upgrade;
|
||||
|
||||
use DB;
|
||||
use Illuminate\Console\Command;
|
||||
@@ -76,10 +76,8 @@ class FixPostgresSequences extends Command
|
||||
'category_transaction_journal',
|
||||
'configuration',
|
||||
'currency_exchange_rates',
|
||||
'export_jobs',
|
||||
'failed_jobs',
|
||||
'group_journals',
|
||||
'import_jobs',
|
||||
'jobs',
|
||||
'journal_links',
|
||||
'journal_meta',
|
@@ -102,20 +102,11 @@ class MigrateRecurrenceMeta extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws JsonException
|
||||
*
|
||||
*/
|
||||
private function migrateMetaData(): int
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
$count = 0;
|
||||
// get all recurrence meta data:
|
||||
$collection = RecurrenceMeta::with('recurrence')->get();
|
||||
/** @var RecurrenceMeta $meta */
|
||||
foreach ($collection as $meta) {
|
||||
$count += $this->migrateEntry($meta);
|
||||
}
|
||||
|
||||
return $count;
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,10 +146,19 @@ class MigrateRecurrenceMeta extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return int
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function markAsExecuted(): void
|
||||
private function migrateMetaData(): int
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
$count = 0;
|
||||
// get all recurrence meta data:
|
||||
$collection = RecurrenceMeta::with('recurrence')->get();
|
||||
/** @var RecurrenceMeta $meta */
|
||||
foreach ($collection as $meta) {
|
||||
$count += $this->migrateEntry($meta);
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
|
@@ -78,6 +78,14 @@ class MigrateRecurrenceType extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function getInvalidType(): TransactionType
|
||||
{
|
||||
return TransactionType::whereType(TransactionType::INVALID)->firstOrCreate(['type' => TransactionType::INVALID]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
@@ -96,15 +104,9 @@ class MigrateRecurrenceType extends Command
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function migrateTypes(): void
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
$set = Recurrence::get();
|
||||
/** @var Recurrence $recurrence */
|
||||
foreach ($set as $recurrence) {
|
||||
if ($recurrence->transactionType->type !== TransactionType::INVALID) {
|
||||
$this->migrateRecurrence($recurrence);
|
||||
}
|
||||
}
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function migrateRecurrence(Recurrence $recurrence): void
|
||||
@@ -124,16 +126,14 @@ class MigrateRecurrenceType extends Command
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function getInvalidType(): TransactionType
|
||||
private function migrateTypes(): void
|
||||
{
|
||||
return TransactionType::whereType(TransactionType::INVALID)->firstOrCreate(['type' => TransactionType::INVALID]);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
$set = Recurrence::get();
|
||||
/** @var Recurrence $recurrence */
|
||||
foreach ($set as $recurrence) {
|
||||
if ($recurrence->transactionType->type !== TransactionType::INVALID) {
|
||||
$this->migrateRecurrence($recurrence);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -75,6 +75,16 @@ class MigrateTagLocations extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function hasLocationDetails(Tag $tag): bool
|
||||
{
|
||||
return null !== $tag->latitude && null !== $tag->longitude && null !== $tag->zoomLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
@@ -90,25 +100,12 @@ class MigrateTagLocations extends Command
|
||||
return false;
|
||||
}
|
||||
|
||||
private function migrateTagLocations(): void
|
||||
{
|
||||
$tags = Tag::get();
|
||||
/** @var Tag $tag */
|
||||
foreach ($tags as $tag) {
|
||||
if ($this->hasLocationDetails($tag)) {
|
||||
$this->migrateLocationDetails($tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function hasLocationDetails(Tag $tag): bool
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
return null !== $tag->latitude && null !== $tag->longitude && null !== $tag->zoomLevel;
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,11 +126,14 @@ class MigrateTagLocations extends Command
|
||||
$tag->save();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function markAsExecuted(): void
|
||||
private function migrateTagLocations(): void
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
$tags = Tag::get();
|
||||
/** @var Tag $tag */
|
||||
foreach ($tags as $tag) {
|
||||
if ($this->hasLocationDetails($tag)) {
|
||||
$this->migrateLocationDetails($tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -105,22 +105,6 @@ class MigrateToRules extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*
|
||||
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->userRepository = app(UserRepositoryInterface::class);
|
||||
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
|
||||
$this->billRepository = app(BillRepositoryInterface::class);
|
||||
$this->ruleRepository = app(RuleRepositoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
@@ -137,38 +121,11 @@ class MigrateToRules extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate bills to new rule structure for a specific user.
|
||||
*
|
||||
* @param User $user
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function migrateUser(User $user): void
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
$this->ruleGroupRepository->setUser($user);
|
||||
$this->billRepository->setUser($user);
|
||||
$this->ruleRepository->setUser($user);
|
||||
|
||||
/** @var Preference $lang */
|
||||
$lang = app('preferences')->getForUser($user, 'language', 'en_US');
|
||||
$groupTitle = (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data);
|
||||
$ruleGroup = $this->ruleGroupRepository->findByTitle($groupTitle);
|
||||
|
||||
if (null === $ruleGroup) {
|
||||
$ruleGroup = $this->ruleGroupRepository->store(
|
||||
[
|
||||
'title' => (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data),
|
||||
'description' => (string)trans('firefly.rulegroup_for_bills_description', [], $lang->data),
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
$bills = $this->billRepository->getBills();
|
||||
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
$this->migrateBill($ruleGroup, $bill, $lang);
|
||||
}
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,10 +200,53 @@ class MigrateToRules extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate bills to new rule structure for a specific user.
|
||||
*
|
||||
* @param User $user
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function markAsExecuted(): void
|
||||
private function migrateUser(User $user): void
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
$this->ruleGroupRepository->setUser($user);
|
||||
$this->billRepository->setUser($user);
|
||||
$this->ruleRepository->setUser($user);
|
||||
|
||||
/** @var Preference $lang */
|
||||
$lang = app('preferences')->getForUser($user, 'language', 'en_US');
|
||||
$groupTitle = (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data);
|
||||
$ruleGroup = $this->ruleGroupRepository->findByTitle($groupTitle);
|
||||
|
||||
if (null === $ruleGroup) {
|
||||
$ruleGroup = $this->ruleGroupRepository->store(
|
||||
[
|
||||
'title' => (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data),
|
||||
'description' => (string)trans('firefly.rulegroup_for_bills_description', [], $lang->data),
|
||||
'active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
$bills = $this->billRepository->getBills();
|
||||
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
$this->migrateBill($ruleGroup, $bill, $lang);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*
|
||||
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->userRepository = app(UserRepositoryInterface::class);
|
||||
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
|
||||
$this->billRepository = app(BillRepositoryInterface::class);
|
||||
$this->ruleRepository = app(RuleRepositoryInterface::class);
|
||||
}
|
||||
}
|
||||
|
@@ -107,63 +107,6 @@ class TransactionIdentifier extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*
|
||||
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->cliRepository = app(JournalCLIRepositoryInterface::class);
|
||||
$this->count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
if (null !== $configVar) {
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab all positive transactions from this journal that are not deleted. for each one, grab the negative opposing one
|
||||
* which has 0 as an identifier and give it the same identifier.
|
||||
*
|
||||
* @param TransactionJournal $transactionJournal
|
||||
*/
|
||||
private function updateJournalIdentifiers(TransactionJournal $transactionJournal): void
|
||||
{
|
||||
$identifier = 0;
|
||||
$exclude = []; // transactions already processed.
|
||||
$transactions = $transactionJournal->transactions()->where('amount', '>', 0)->get();
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$opposing = $this->findOpposing($transaction, $exclude);
|
||||
if (null !== $opposing) {
|
||||
// give both a new identifier:
|
||||
$transaction->identifier = $identifier;
|
||||
$opposing->identifier = $identifier;
|
||||
$transaction->save();
|
||||
$opposing->save();
|
||||
$exclude[] = $transaction->id;
|
||||
$exclude[] = $opposing->id;
|
||||
$this->count++;
|
||||
}
|
||||
++$identifier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Transaction $transaction
|
||||
* @param array $exclude
|
||||
@@ -194,6 +137,21 @@ class TransactionIdentifier extends Command
|
||||
return $opposing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
if (null !== $configVar) {
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -201,4 +159,46 @@ class TransactionIdentifier extends Command
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*
|
||||
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->cliRepository = app(JournalCLIRepositoryInterface::class);
|
||||
$this->count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab all positive transactions from this journal that are not deleted. for each one, grab the negative opposing one
|
||||
* which has 0 as an identifier and give it the same identifier.
|
||||
*
|
||||
* @param TransactionJournal $transactionJournal
|
||||
*/
|
||||
private function updateJournalIdentifiers(TransactionJournal $transactionJournal): void
|
||||
{
|
||||
$identifier = 0;
|
||||
$exclude = []; // transactions already processed.
|
||||
$transactions = $transactionJournal->transactions()->where('amount', '>', 0)->get();
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$opposing = $this->findOpposing($transaction, $exclude);
|
||||
if (null !== $opposing) {
|
||||
// give both a new identifier:
|
||||
$transaction->identifier = $identifier;
|
||||
$opposing->identifier = $identifier;
|
||||
$transaction->save();
|
||||
$opposing->save();
|
||||
$exclude[] = $transaction->id;
|
||||
$exclude[] = $opposing->id;
|
||||
$this->count++;
|
||||
}
|
||||
++$identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -57,7 +57,6 @@ class UpgradeDatabase extends Command
|
||||
{
|
||||
$this->callInitialCommands();
|
||||
$commands = [
|
||||
// there are 14 upgrade commands.
|
||||
'firefly-iii:transaction-identifiers',
|
||||
'firefly-iii:migrate-to-groups',
|
||||
'firefly-iii:account-currencies',
|
||||
@@ -75,41 +74,7 @@ class UpgradeDatabase extends Command
|
||||
'firefly-iii:migrate-recurrence-type',
|
||||
'firefly-iii:upgrade-liabilities',
|
||||
'firefly-iii:liabilities-600',
|
||||
|
||||
// there are 16 verify commands.
|
||||
'firefly-iii:fix-piggies',
|
||||
'firefly-iii:create-link-types',
|
||||
'firefly-iii:create-access-tokens',
|
||||
'firefly-iii:remove-bills',
|
||||
'firefly-iii:fix-negative-limits',
|
||||
'firefly-iii:enable-currencies',
|
||||
'firefly-iii:fix-transfer-budgets',
|
||||
'firefly-iii:fix-uneven-amount',
|
||||
'firefly-iii:delete-zero-amount',
|
||||
'firefly-iii:delete-orphaned-transactions',
|
||||
'firefly-iii:delete-empty-journals',
|
||||
'firefly-iii:delete-empty-groups',
|
||||
'firefly-iii:fix-account-types',
|
||||
'firefly-iii:fix-account-order',
|
||||
'firefly-iii:rename-meta-fields',
|
||||
'firefly-iii:fix-ob-currencies',
|
||||
'firefly-iii:fix-long-descriptions',
|
||||
'firefly-iii:fix-recurring-transactions',
|
||||
'firefly-iii:unify-group-accounts',
|
||||
'firefly-iii:fix-transaction-types',
|
||||
'firefly-iii:fix-frontpage-accounts',
|
||||
'firefly-iii:fix-ibans',
|
||||
'firefly-iii:create-group-memberships',
|
||||
'firefly-iii:upgrade-group-information',
|
||||
|
||||
// two report commands
|
||||
'firefly-iii:report-empty-objects',
|
||||
'firefly-iii:report-sum',
|
||||
'firefly-iii:restore-oauth-keys',
|
||||
|
||||
// instructions
|
||||
'firefly:instructions update',
|
||||
'firefly-iii:verify-security-alerts',
|
||||
'firefly-iii:budget-limit-periods',
|
||||
];
|
||||
$args = [];
|
||||
if ($this->option('force')) {
|
||||
@@ -117,9 +82,7 @@ class UpgradeDatabase extends Command
|
||||
}
|
||||
foreach ($commands as $command) {
|
||||
$this->line(sprintf('Now executing %s', $command));
|
||||
Artisan::call($command, $args);
|
||||
$result = Artisan::output();
|
||||
echo $result;
|
||||
$this->call($command, $args);
|
||||
}
|
||||
// set new DB version.
|
||||
app('fireflyconfig')->set('db_version', (int)config('firefly.db_version'));
|
||||
@@ -129,22 +92,19 @@ class UpgradeDatabase extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function callInitialCommands(): void
|
||||
{
|
||||
$this->line('Now seeding the database...');
|
||||
Artisan::call('migrate', ['--seed' => true, '--force' => true]);
|
||||
$result = Artisan::output();
|
||||
echo $result;
|
||||
$this->call('migrate', ['--seed' => true, '--force' => true, '--no-interaction' => true]);
|
||||
|
||||
$this->line('Fix PostgreSQL sequences.');
|
||||
Artisan::call('firefly-iii:fix-pgsql-sequences');
|
||||
$result = Artisan::output();
|
||||
echo $result;
|
||||
$this->call('firefly-iii:fix-pgsql-sequences');
|
||||
|
||||
$this->line('Now decrypting the database (if necessary)...');
|
||||
Artisan::call('firefly-iii:decrypt-all');
|
||||
$result = Artisan::output();
|
||||
echo $result;
|
||||
$this->call('firefly-iii:decrypt-all');
|
||||
|
||||
$this->line('Done!');
|
||||
}
|
||||
|
@@ -82,79 +82,6 @@ class UpgradeLiabilities extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
if (null !== $configVar) {
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function upgradeLiabilities(): void
|
||||
{
|
||||
Log::debug('Upgrading liabilities.');
|
||||
$users = User::get();
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$this->upgradeForUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
private function upgradeForUser(User $user): void
|
||||
{
|
||||
Log::debug(sprintf('Upgrading liabilities for user #%d', $user->id));
|
||||
$accounts = $user->accounts()
|
||||
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', config('firefly.valid_liabilities'))
|
||||
->get(['accounts.*']);
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->upgradeLiability($account);
|
||||
$service = app(CreditRecalculateService::class);
|
||||
$service->setAccount($account);
|
||||
$service->recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
*/
|
||||
private function upgradeLiability(Account $account): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser($account->user);
|
||||
Log::debug(sprintf('Upgrade liability #%d', $account->id));
|
||||
|
||||
// get opening balance, and correct if necessary.
|
||||
$openingBalance = $repository->getOpeningBalance($account);
|
||||
if (null !== $openingBalance) {
|
||||
// correct if necessary
|
||||
$this->correctOpeningBalance($account, $openingBalance);
|
||||
}
|
||||
|
||||
// add liability direction property (if it does not yet exist!)
|
||||
$value = $repository->getMetaValue($account, 'liability_direction');
|
||||
if (null === $value) {
|
||||
/** @var AccountMetaFactory $factory */
|
||||
$factory = app(AccountMetaFactory::class);
|
||||
$factory->crud($account, 'liability_direction', 'debit');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @param TransactionJournal $openingBalance
|
||||
@@ -185,9 +112,9 @@ class UpgradeLiabilities extends Command
|
||||
*
|
||||
* @return Transaction|null
|
||||
*/
|
||||
private function getSourceTransaction(TransactionJournal $journal): ?Transaction
|
||||
private function getDestinationTransaction(TransactionJournal $journal): ?Transaction
|
||||
{
|
||||
return $journal->transactions()->where('amount', '<', 0)->first();
|
||||
return $journal->transactions()->where('amount', '>', 0)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,9 +122,24 @@ class UpgradeLiabilities extends Command
|
||||
*
|
||||
* @return Transaction|null
|
||||
*/
|
||||
private function getDestinationTransaction(TransactionJournal $journal): ?Transaction
|
||||
private function getSourceTransaction(TransactionJournal $journal): ?Transaction
|
||||
{
|
||||
return $journal->transactions()->where('amount', '>', 0)->first();
|
||||
return $journal->transactions()->where('amount', '<', 0)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
if (null !== $configVar) {
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,4 +149,62 @@ class UpgradeLiabilities extends Command
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
private function upgradeForUser(User $user): void
|
||||
{
|
||||
Log::debug(sprintf('Upgrading liabilities for user #%d', $user->id));
|
||||
$accounts = $user->accounts()
|
||||
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', config('firefly.valid_liabilities'))
|
||||
->get(['accounts.*']);
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->upgradeLiability($account);
|
||||
$service = app(CreditRecalculateService::class);
|
||||
$service->setAccount($account);
|
||||
$service->recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function upgradeLiabilities(): void
|
||||
{
|
||||
Log::debug('Upgrading liabilities.');
|
||||
$users = User::get();
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$this->upgradeForUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
*/
|
||||
private function upgradeLiability(Account $account): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser($account->user);
|
||||
Log::debug(sprintf('Upgrade liability #%d', $account->id));
|
||||
|
||||
// get opening balance, and correct if necessary.
|
||||
$openingBalance = $repository->getOpeningBalance($account);
|
||||
if (null !== $openingBalance) {
|
||||
// correct if necessary
|
||||
$this->correctOpeningBalance($account, $openingBalance);
|
||||
}
|
||||
|
||||
// add liability direction property (if it does not yet exist!)
|
||||
$value = $repository->getMetaValue($account, 'liability_direction');
|
||||
if (null === $value) {
|
||||
/** @var AccountMetaFactory $factory */
|
||||
$factory = app(AccountMetaFactory::class);
|
||||
$factory->crud($account, 'liability_direction', 'debit');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -83,115 +83,6 @@ class UpgradeLiabilitiesEight extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
if (null !== $configVar) {
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function upgradeLiabilities(): void
|
||||
{
|
||||
Log::debug('Upgrading liabilities.');
|
||||
$users = User::get();
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$this->upgradeForUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
private function upgradeForUser(User $user): void
|
||||
{
|
||||
Log::debug(sprintf('Upgrading liabilities for user #%d', $user->id));
|
||||
$accounts = $user->accounts()
|
||||
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', config('firefly.valid_liabilities'))
|
||||
->get(['accounts.*']);
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->upgradeLiability($account);
|
||||
$service = app(CreditRecalculateService::class);
|
||||
$service->setAccount($account);
|
||||
$service->recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
*/
|
||||
private function upgradeLiability(Account $account): void
|
||||
{
|
||||
Log::debug(sprintf('Upgrade liability #%d ("%s")', $account->id, $account->name));
|
||||
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser($account->user);
|
||||
|
||||
$direction = $repository->getMetaValue($account, 'liability_direction');
|
||||
if ('debit' === $direction) {
|
||||
Log::debug('Direction is debit ("I owe this amount"), so no need to upgrade.');
|
||||
}
|
||||
if ('credit' === $direction && $this->hasBadOpening($account)) {
|
||||
$this->deleteCreditTransaction($account);
|
||||
$this->reverseOpeningBalance($account);
|
||||
$this->line(sprintf('Corrected opening balance for liability #%d ("%s")', $account->id, $account->name));
|
||||
}
|
||||
if ('credit' === $direction) {
|
||||
$count = $this->deleteTransactions($account);
|
||||
if ($count > 0) {
|
||||
$this->line(sprintf('Removed %d old format transaction(s) for liability #%d ("%s")', $count, $account->id, $account->name));
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Done upgrading liability #%d ("%s")', $account->id, $account->name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @return bool
|
||||
*/
|
||||
private function hasBadOpening(Account $account): bool
|
||||
{
|
||||
$openingBalanceType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
|
||||
$liabilityType = TransactionType::whereType(TransactionType::LIABILITY_CREDIT)->first();
|
||||
$openingJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transactions.account_id', $account->id)
|
||||
->where('transaction_journals.transaction_type_id', $openingBalanceType->id)
|
||||
->first(['transaction_journals.*']);
|
||||
if (null === $openingJournal) {
|
||||
Log::debug('Account has no opening balance and can be skipped.');
|
||||
return false;
|
||||
}
|
||||
$liabilityJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transactions.account_id', $account->id)
|
||||
->where('transaction_journals.transaction_type_id', $liabilityType->id)
|
||||
->first(['transaction_journals.*']);
|
||||
if (null === $liabilityJournal) {
|
||||
Log::debug('Account has no liability credit and can be skipped.');
|
||||
return false;
|
||||
}
|
||||
if (!$openingJournal->date->isSameDay($liabilityJournal->date)) {
|
||||
Log::debug('Account has opening/credit not on the same day.');
|
||||
return false;
|
||||
}
|
||||
Log::debug('Account has bad opening balance data.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @return void
|
||||
@@ -214,35 +105,6 @@ class UpgradeLiabilitiesEight extends Command
|
||||
Log::debug('No liability credit journal found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @return void
|
||||
*/
|
||||
private function reverseOpeningBalance(Account $account): void
|
||||
{
|
||||
$openingBalanceType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
|
||||
/** @var TransactionJournal $openingJournal */
|
||||
$openingJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transactions.account_id', $account->id)
|
||||
->where('transaction_journals.transaction_type_id', $openingBalanceType->id)
|
||||
->first(['transaction_journals.*']);
|
||||
/** @var Transaction|null $source */
|
||||
$source = $openingJournal->transactions()->where('amount', '<', 0)->first();
|
||||
/** @var Transaction|null $dest */
|
||||
$dest = $openingJournal->transactions()->where('amount', '>', 0)->first();
|
||||
if ($source && $dest) {
|
||||
$sourceId = $source->account_id;
|
||||
$destId = $dest->account_id;
|
||||
$dest->account_id = $sourceId;
|
||||
$source->account_id = $destId;
|
||||
$source->save();
|
||||
$dest->save();
|
||||
Log::debug(sprintf('Opening balance transaction journal #%d reversed.', $openingJournal->id));
|
||||
return;
|
||||
}
|
||||
Log::warning('Did not find opening balance.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $account
|
||||
* @return int
|
||||
@@ -293,6 +155,54 @@ class UpgradeLiabilitiesEight extends Command
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @return bool
|
||||
*/
|
||||
private function hasBadOpening(Account $account): bool
|
||||
{
|
||||
$openingBalanceType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
|
||||
$liabilityType = TransactionType::whereType(TransactionType::LIABILITY_CREDIT)->first();
|
||||
$openingJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transactions.account_id', $account->id)
|
||||
->where('transaction_journals.transaction_type_id', $openingBalanceType->id)
|
||||
->first(['transaction_journals.*']);
|
||||
if (null === $openingJournal) {
|
||||
Log::debug('Account has no opening balance and can be skipped.');
|
||||
return false;
|
||||
}
|
||||
$liabilityJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transactions.account_id', $account->id)
|
||||
->where('transaction_journals.transaction_type_id', $liabilityType->id)
|
||||
->first(['transaction_journals.*']);
|
||||
if (null === $liabilityJournal) {
|
||||
Log::debug('Account has no liability credit and can be skipped.');
|
||||
return false;
|
||||
}
|
||||
if (!$openingJournal->date->isSameDay($liabilityJournal->date)) {
|
||||
Log::debug('Account has opening/credit not on the same day.');
|
||||
return false;
|
||||
}
|
||||
Log::debug('Account has bad opening balance data.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
if (null !== $configVar) {
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -300,4 +210,94 @@ class UpgradeLiabilitiesEight extends Command
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @return void
|
||||
*/
|
||||
private function reverseOpeningBalance(Account $account): void
|
||||
{
|
||||
$openingBalanceType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
|
||||
/** @var TransactionJournal $openingJournal */
|
||||
$openingJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transactions.account_id', $account->id)
|
||||
->where('transaction_journals.transaction_type_id', $openingBalanceType->id)
|
||||
->first(['transaction_journals.*']);
|
||||
/** @var Transaction|null $source */
|
||||
$source = $openingJournal->transactions()->where('amount', '<', 0)->first();
|
||||
/** @var Transaction|null $dest */
|
||||
$dest = $openingJournal->transactions()->where('amount', '>', 0)->first();
|
||||
if ($source && $dest) {
|
||||
$sourceId = $source->account_id;
|
||||
$destId = $dest->account_id;
|
||||
$dest->account_id = $sourceId;
|
||||
$source->account_id = $destId;
|
||||
$source->save();
|
||||
$dest->save();
|
||||
Log::debug(sprintf('Opening balance transaction journal #%d reversed.', $openingJournal->id));
|
||||
return;
|
||||
}
|
||||
Log::warning('Did not find opening balance.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
private function upgradeForUser(User $user): void
|
||||
{
|
||||
Log::debug(sprintf('Upgrading liabilities for user #%d', $user->id));
|
||||
$accounts = $user->accounts()
|
||||
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', config('firefly.valid_liabilities'))
|
||||
->get(['accounts.*']);
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->upgradeLiability($account);
|
||||
$service = app(CreditRecalculateService::class);
|
||||
$service->setAccount($account);
|
||||
$service->recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function upgradeLiabilities(): void
|
||||
{
|
||||
Log::debug('Upgrading liabilities.');
|
||||
$users = User::get();
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$this->upgradeForUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
*/
|
||||
private function upgradeLiability(Account $account): void
|
||||
{
|
||||
Log::debug(sprintf('Upgrade liability #%d ("%s")', $account->id, $account->name));
|
||||
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser($account->user);
|
||||
|
||||
$direction = $repository->getMetaValue($account, 'liability_direction');
|
||||
if ('debit' === $direction) {
|
||||
Log::debug('Direction is debit ("I owe this amount"), so no need to upgrade.');
|
||||
}
|
||||
if ('credit' === $direction && $this->hasBadOpening($account)) {
|
||||
$this->deleteCreditTransaction($account);
|
||||
$this->reverseOpeningBalance($account);
|
||||
$this->line(sprintf('Corrected opening balance for liability #%d ("%s")', $account->id, $account->name));
|
||||
}
|
||||
if ('credit' === $direction) {
|
||||
$count = $this->deleteTransactions($account);
|
||||
if ($count > 0) {
|
||||
$this->line(sprintf('Removed %d old format transaction(s) for liability #%d ("%s")', $count, $account->id, $account->name));
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Done upgrading liability #%d ("%s")', $account->id, $account->name));
|
||||
}
|
||||
}
|
||||
|
46
app/Events/Model/BudgetLimit/Created.php
Normal file
46
app/Events/Model/BudgetLimit/Created.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* Created.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Events\Model\BudgetLimit;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class Created
|
||||
*/
|
||||
class Created extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public BudgetLimit $budgetLimit;
|
||||
|
||||
/**
|
||||
* @param BudgetLimit $budgetLimit
|
||||
*/
|
||||
public function __construct(BudgetLimit $budgetLimit)
|
||||
{
|
||||
$this->budgetLimit = $budgetLimit;
|
||||
}
|
||||
}
|
46
app/Events/Model/BudgetLimit/Deleted.php
Normal file
46
app/Events/Model/BudgetLimit/Deleted.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* Created.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Events\Model\BudgetLimit;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class Deleted
|
||||
*/
|
||||
class Deleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public BudgetLimit $budgetLimit;
|
||||
|
||||
/**
|
||||
* @param BudgetLimit $budgetLimit
|
||||
*/
|
||||
public function __construct(BudgetLimit $budgetLimit)
|
||||
{
|
||||
$this->budgetLimit = $budgetLimit;
|
||||
}
|
||||
}
|
46
app/Events/Model/BudgetLimit/Updated.php
Normal file
46
app/Events/Model/BudgetLimit/Updated.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* Created.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Events\Model\BudgetLimit;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class Updated
|
||||
*/
|
||||
class Updated extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public BudgetLimit $budgetLimit;
|
||||
|
||||
/**
|
||||
* @param BudgetLimit $budgetLimit
|
||||
*/
|
||||
public function __construct(BudgetLimit $budgetLimit)
|
||||
{
|
||||
$this->budgetLimit = $budgetLimit;
|
||||
}
|
||||
}
|
@@ -122,7 +122,7 @@ class ChartJsGenerator implements GeneratorInterface
|
||||
|
||||
foreach ($data as $set) {
|
||||
$currentSet = [
|
||||
'label' => $set['label'],
|
||||
'label' => $set['label'] ?? '(no label)',
|
||||
'type' => $set['type'] ?? 'line',
|
||||
'data' => array_values($set['entries']),
|
||||
];
|
||||
|
245
app/Handlers/Events/Model/BudgetLimitHandler.php
Normal file
245
app/Handlers/Events/Model/BudgetLimitHandler.php
Normal file
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* BudgetLimitHandler.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Handlers\Events\Model;
|
||||
|
||||
use FireflyIII\Events\Model\BudgetLimit\Created;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Deleted;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Updated;
|
||||
use FireflyIII\Models\AvailableBudget;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Spatie\Period\Boundaries;
|
||||
use Spatie\Period\Period;
|
||||
use Spatie\Period\Precision;
|
||||
|
||||
/**
|
||||
* Class BudgetLimitHandler
|
||||
*/
|
||||
class BudgetLimitHandler
|
||||
{
|
||||
/**
|
||||
* @param Created $event
|
||||
* @return void
|
||||
*/
|
||||
public function created(Created $event): void
|
||||
{
|
||||
Log::debug(sprintf('BudgetLimitHandler::created(#%s)', $event->budgetLimit->id));
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Updated $event
|
||||
* @return void
|
||||
*/
|
||||
public function updated(Updated $event): void
|
||||
{
|
||||
Log::debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id));
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Deleted $event
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(Deleted $event): void
|
||||
{
|
||||
Log::debug(sprintf('BudgetLimitHandler::deleted(#%s)', $event->budgetLimit->id));
|
||||
$budgetLimit = $event->budgetLimit;
|
||||
$budgetLimit->id = null;
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AvailableBudget $availableBudget
|
||||
* @return void
|
||||
*/
|
||||
private function calculateAmount(AvailableBudget $availableBudget): void
|
||||
{
|
||||
$repository = app(BudgetLimitRepositoryInterface::class);
|
||||
$repository->setUser($availableBudget->user);
|
||||
$newAmount = '0';
|
||||
$abPeriod = Period::make($availableBudget->start_date, $availableBudget->end_date, Precision::DAY());
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Now at AB #%d, ("%s" to "%s")',
|
||||
$availableBudget->id,
|
||||
$availableBudget->start_date->format('Y-m-d'),
|
||||
$availableBudget->end_date->format('Y-m-d')
|
||||
)
|
||||
);
|
||||
// have to recalc everything just in case.
|
||||
$set = $repository->getAllBudgetLimitsByCurrency($availableBudget->transactionCurrency, $availableBudget->start_date, $availableBudget->end_date);
|
||||
Log::debug(sprintf('Found %d interesting budget limit(s).', $set->count()));
|
||||
/** @var BudgetLimit $budgetLimit */
|
||||
foreach ($set as $budgetLimit) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Found interesting budget limit #%d ("%s" to "%s")',
|
||||
$budgetLimit->id,
|
||||
$budgetLimit->start_date->format('Y-m-d'),
|
||||
$budgetLimit->end_date->format('Y-m-d')
|
||||
)
|
||||
);
|
||||
// overlap in days:
|
||||
$limitPeriod = Period::make(
|
||||
$budgetLimit->start_date,
|
||||
$budgetLimit->end_date,
|
||||
precision: Precision::DAY(),
|
||||
boundaries: Boundaries::EXCLUDE_NONE()
|
||||
);
|
||||
// if both equal eachother, amount from this BL must be added to the AB
|
||||
if ($limitPeriod->equals($abPeriod)) {
|
||||
$newAmount = bcadd($newAmount, $budgetLimit->amount);
|
||||
}
|
||||
// if budget limit period inside AB period, can be added in full.
|
||||
if (!$limitPeriod->equals($abPeriod) && $abPeriod->contains($limitPeriod)) {
|
||||
$newAmount = bcadd($newAmount, $budgetLimit->amount);
|
||||
}
|
||||
if (!$limitPeriod->equals($abPeriod) && $abPeriod->overlapsWith($limitPeriod)) {
|
||||
$overlap = $abPeriod->overlap($limitPeriod);
|
||||
if (null !== $overlap) {
|
||||
$length = $overlap->length();
|
||||
$daily = bcmul($this->getDailyAmount($budgetLimit), (string)$length);
|
||||
$newAmount = bcadd($newAmount, $daily);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (0 === bccomp('0', $newAmount)) {
|
||||
Log::debug('New amount is zero, deleting AB.');
|
||||
$availableBudget->delete();
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Concluded new amount for this AB must be %s', $newAmount));
|
||||
$availableBudget->amount = $newAmount;
|
||||
$availableBudget->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BudgetLimit $budgetLimit
|
||||
* @return string
|
||||
*/
|
||||
private function getDailyAmount(BudgetLimit $budgetLimit): string
|
||||
{
|
||||
if (0 === (int)$budgetLimit->id) {
|
||||
return '0';
|
||||
}
|
||||
$limitPeriod = Period::make(
|
||||
$budgetLimit->start_date,
|
||||
$budgetLimit->end_date,
|
||||
precision: Precision::DAY(),
|
||||
boundaries: Boundaries::EXCLUDE_NONE()
|
||||
);
|
||||
$days = $limitPeriod->length();
|
||||
$amount = bcdiv((string)$budgetLimit->amount, (string)$days, 12);
|
||||
Log::debug(
|
||||
sprintf('Total amount for budget limit #%d is %s. Nr. of days is %d. Amount per day is %s', $budgetLimit->id, $budgetLimit->amount, $days, $amount)
|
||||
);
|
||||
return $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BudgetLimit $budgetLimit
|
||||
* @return void
|
||||
*/
|
||||
private function updateAvailableBudget(BudgetLimit $budgetLimit): void
|
||||
{
|
||||
Log::debug(sprintf('Now in updateAvailableBudget(#%d)', $budgetLimit->id));
|
||||
|
||||
// based on the view range of the user (month week quarter etc) the budget limit could
|
||||
// either overlap multiple available budget periods or be contained in a single one.
|
||||
// all have to be created or updated.
|
||||
try {
|
||||
$viewRange = app('preferences')->get('viewRange', '1M')->data;
|
||||
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
|
||||
$viewRange = '1M';
|
||||
}
|
||||
$start = app('navigation')->startOfPeriod($budgetLimit->start_date, $viewRange);
|
||||
$end = app('navigation')->startOfPeriod($budgetLimit->end_date, $viewRange);
|
||||
$end = app('navigation')->endOfPeriod($end, $viewRange);
|
||||
$budget = Budget::withTrashed()->find($budgetLimit->budget_id);
|
||||
$user = $budget->user;
|
||||
|
||||
// sanity check. It happens when the budget has been deleted so the original user is unknown.
|
||||
if (null === $user) {
|
||||
Log::warning('User is null, cannot continue.');
|
||||
$budgetLimit->forceDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
// limit period in total is:
|
||||
$limitPeriod = Period::make($start, $end, precision: Precision::DAY(), boundaries: Boundaries::EXCLUDE_NONE());
|
||||
|
||||
// from the start until the end of the budget limit, need to loop!
|
||||
$current = clone $start;
|
||||
while ($current <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($current, $viewRange);
|
||||
|
||||
// create or find AB for this particular period, and set the amount accordingly.
|
||||
/** @var AvailableBudget $availableBudget */
|
||||
$availableBudget = $user->availableBudgets()->where('start_date', $current->format('Y-m-d'))->where(
|
||||
'end_date',
|
||||
$currentEnd->format('Y-m-d')
|
||||
)->where('transaction_currency_id', $budgetLimit->transaction_currency_id)->first();
|
||||
if (null !== $availableBudget) {
|
||||
Log::debug('Found 1 AB, will update.');
|
||||
$this->calculateAmount($availableBudget);
|
||||
}
|
||||
if (null === $availableBudget) {
|
||||
// if not exists:
|
||||
$currentPeriod = Period::make($current, $currentEnd, precision: Precision::DAY(), boundaries: Boundaries::EXCLUDE_NONE());
|
||||
$daily = $this->getDailyAmount($budgetLimit);
|
||||
$amount = bcmul($daily, (string)$currentPeriod->length(), 12);
|
||||
|
||||
// no need to calculate if period is equal.
|
||||
if ($currentPeriod->equals($limitPeriod)) {
|
||||
$amount = 0 === (int)$budgetLimit->id ? '0' : $budgetLimit->amount;
|
||||
}
|
||||
if (0 === bccomp($amount, '0')) {
|
||||
Log::debug('Amount is zero, will not create AB.');
|
||||
}
|
||||
if (0 !== bccomp($amount, '0')) {
|
||||
Log::debug(sprintf('Will create AB for period %s to %s', $current->format('Y-m-d'), $currentEnd->format('Y-m-d')));
|
||||
$availableBudget = new AvailableBudget(
|
||||
[
|
||||
'user_id' => $budgetLimit->budget->user->id,
|
||||
'transaction_currency_id' => $budgetLimit->transaction_currency_id,
|
||||
'start_date' => $current,
|
||||
'end_date' => $currentEnd,
|
||||
'amount' => $amount,
|
||||
]
|
||||
);
|
||||
$availableBudget->save();
|
||||
}
|
||||
}
|
||||
|
||||
// prep for next loop
|
||||
$current = app('navigation')->addPeriod($current, $viewRange, 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -92,7 +92,8 @@ trait AttachmentCollection
|
||||
->where(
|
||||
static function (EloquentBuilder $q1) {
|
||||
$q1->where('attachments.attachable_type', TransactionJournal::class);
|
||||
//$q1->where('attachments.uploaded', true);
|
||||
$q1->where('attachments.uploaded', true);
|
||||
$q1->whereNull('attachments.deleted_at');
|
||||
$q1->orWhereNull('attachments.attachable_type');
|
||||
}
|
||||
);
|
||||
|
@@ -41,7 +41,6 @@ trait UpdateTrait
|
||||
* 'level' => 'info' / 'success' / 'error'
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
|
@@ -80,7 +80,10 @@ class IndexController extends Controller
|
||||
|
||||
$defaultCurrency = app('amount')->getDefaultCurrency();
|
||||
$parameters = new ParameterBag();
|
||||
$parameters->set('start', $start);
|
||||
// sub one day from temp start so the last paid date is one day before it should be.
|
||||
$tempStart = clone $start;
|
||||
$tempStart->subDay();
|
||||
$parameters->set('start', $tempStart);
|
||||
$parameters->set('end', $end);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
@@ -108,18 +111,6 @@ class IndexController extends Controller
|
||||
'object_group_title' => $array['object_group_title'],
|
||||
'bills' => [],
|
||||
];
|
||||
// var_dump($array);exit;
|
||||
// // expected today? default:
|
||||
// $array['next_expected_match_diff'] = trans('firefly.not_expected_period');
|
||||
// $nextExpectedMatch = new Carbon($array['next_expected_match']);
|
||||
// if ($nextExpectedMatch->isToday()) {
|
||||
// $array['next_expected_match_diff'] = trans('firefly.today');
|
||||
// }
|
||||
// $current = $array['pay_dates'][0] ?? null;
|
||||
// if (null !== $current && !$nextExpectedMatch->isToday()) {
|
||||
// $currentExpectedMatch = Carbon::createFromFormat('Y-m-d\TH:i:sP', $current);
|
||||
// $array['next_expected_match_diff'] = $currentExpectedMatch->diffForHumans(today(), Carbon::DIFF_RELATIVE_TO_NOW);
|
||||
// }
|
||||
|
||||
$currency = $bill->transactionCurrency ?? $defaultCurrency;
|
||||
$array['currency_id'] = $currency->id;
|
||||
|
@@ -89,12 +89,13 @@ class BudgetLimitController extends Controller
|
||||
$collection = $this->currencyRepos->get();
|
||||
$budgetLimits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||
|
||||
// remove already budgeted currencies:
|
||||
// remove already budgeted currencies with the same date range
|
||||
$currencies = $collection->filter(
|
||||
static function (TransactionCurrency $currency) use ($budgetLimits) {
|
||||
/** @var AvailableBudget $budget */
|
||||
foreach ($budgetLimits as $budget) {
|
||||
if ($budget->transaction_currency_id === $currency->id) {
|
||||
static function (TransactionCurrency $currency) use ($budgetLimits, $start, $end) {
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($budgetLimits as $limit) {
|
||||
if ($limit->transaction_currency_id === $currency->id && $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -40,9 +40,9 @@ use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
use JsonException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
@@ -101,12 +101,23 @@ class IndexController extends Controller
|
||||
*/
|
||||
public function index(Request $request, Carbon $start = null, Carbon $end = null)
|
||||
{
|
||||
Log::debug('Start of IndexController::index()');
|
||||
Log::debug(sprintf('Start of IndexController::index("%s", "%s")', $start?->format('Y-m-d'), $end?->format('Y-m-d')));
|
||||
|
||||
// collect some basic vars:
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$start = $start ?? session('start', today(config('app.timezone'))->startOfMonth());
|
||||
$end = $end ?? app('navigation')->endOfPeriod($start, $range);
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$isCustomRange = session('is_custom_range', false);
|
||||
if (false === $isCustomRange) {
|
||||
$start = $start ?? session('start', today(config('app.timezone'))->startOfMonth());
|
||||
$end = $end ?? app('navigation')->endOfPeriod($start, $range);
|
||||
}
|
||||
|
||||
// overrule start and end if necessary:
|
||||
if (true === $isCustomRange) {
|
||||
$start = $start ?? session('start', today(config('app.timezone'))->startOfMonth());
|
||||
$end = $end ?? session('end', today(config('app.timezone'))->endOfMonth());
|
||||
}
|
||||
|
||||
|
||||
$defaultCurrency = app('amount')->getDefaultCurrency();
|
||||
$currencies = $this->currencyRepository->get();
|
||||
$budgeted = '0';
|
||||
|
@@ -34,8 +34,8 @@ use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
|
||||
/**
|
||||
@@ -121,6 +121,8 @@ class DebugController extends Controller
|
||||
$now = today(config('app.timezone'))->format('Y-m-d H:i:s e');
|
||||
$buildNr = '(unknown)';
|
||||
$buildDate = '(unknown)';
|
||||
$baseBuildNr = '(unknown)';
|
||||
$baseBuildDate = '(unknown)';
|
||||
$expectedDBversion = config('firefly.db_version');
|
||||
$foundDBversion = FireflyConfig::get('db_version', 1)->data;
|
||||
if (file_exists('/var/www/counter-main.txt')) {
|
||||
@@ -129,6 +131,13 @@ class DebugController extends Controller
|
||||
if (file_exists('/var/www/build-date-main.txt')) {
|
||||
$buildDate = trim(file_get_contents('/var/www/build-date-main.txt'));
|
||||
}
|
||||
if('' !== (string)env('BASE_IMAGE_BUILD')) {
|
||||
$baseBuildNr = env('BASE_IMAGE_BUILD');
|
||||
}
|
||||
if('' !== (string)env('BASE_IMAGE_DATE')) {
|
||||
$baseBuildDate = env('BASE_IMAGE_DATE');
|
||||
}
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$phpOs = PHP_OS;
|
||||
|
||||
@@ -220,6 +229,8 @@ class DebugController extends Controller
|
||||
'loginProvider',
|
||||
'buildNr',
|
||||
'buildDate',
|
||||
'baseBuildNr',
|
||||
'baseBuildDate',
|
||||
'bcscale',
|
||||
'userAgent',
|
||||
'displayErrors',
|
||||
|
@@ -82,8 +82,10 @@ class BudgetController extends Controller
|
||||
$percentage = '0';
|
||||
|
||||
if (null !== $availableBudget) {
|
||||
$available = $availableBudget->amount;
|
||||
$percentage = bcmul(bcdiv($budgeted, $available), '100');
|
||||
$available = $availableBudget->amount;
|
||||
if (0 !== bccomp($available, '0')) {
|
||||
$percentage = bcmul(bcdiv($budgeted, $available), '100');
|
||||
}
|
||||
}
|
||||
|
||||
// if available, get the AB for this period + currency, so the bar can be redrawn.
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\System;
|
||||
|
||||
use FireflyIII\User;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
@@ -38,6 +39,7 @@ class HealthcheckController extends Controller
|
||||
*/
|
||||
public function check(): Response
|
||||
{
|
||||
User::count(); // sanity check for database health. Will crash if not OK.
|
||||
return response('OK', 200);
|
||||
}
|
||||
}
|
||||
|
@@ -33,9 +33,9 @@ use FireflyIII\Support\Http\Controllers\GetConfigurationData;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
use Laravel\Passport\Passport;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use phpseclib3\Crypt\RSA;
|
||||
|
||||
/**
|
||||
@@ -60,61 +60,15 @@ class InstallController extends Controller
|
||||
{
|
||||
// empty on purpose.
|
||||
$this->upgradeCommands = [
|
||||
// there are 3 initial commands
|
||||
'migrate' => ['--seed' => true, '--force' => true],
|
||||
'firefly-iii:fix-pgsql-sequences' => [],
|
||||
'firefly-iii:decrypt-all' => [],
|
||||
'firefly-iii:restore-oauth-keys' => [],
|
||||
'generate-keys' => [], // an exception :(
|
||||
|
||||
// upgrade commands
|
||||
'firefly-iii:transaction-identifiers' => [],
|
||||
'firefly-iii:migrate-to-groups' => [],
|
||||
'firefly-iii:account-currencies' => [],
|
||||
'firefly-iii:transfer-currencies' => [],
|
||||
'firefly-iii:other-currencies' => [],
|
||||
'firefly-iii:migrate-notes' => [],
|
||||
'firefly-iii:migrate-attachments' => [],
|
||||
'firefly-iii:bills-to-rules' => [],
|
||||
'firefly-iii:bl-currency' => [],
|
||||
'firefly-iii:cc-liabilities' => [],
|
||||
'firefly-iii:back-to-journals' => [],
|
||||
'firefly-iii:rename-account-meta' => [],
|
||||
'firefly-iii:migrate-recurrence-meta' => [],
|
||||
'firefly-iii:migrate-tag-locations' => [],
|
||||
'firefly-iii:migrate-recurrence-type' => [],
|
||||
'firefly-iii:upgrade-liabilities' => [],
|
||||
'firefly-iii:liabilities-600' => [],
|
||||
|
||||
// verify commands
|
||||
'firefly-iii:fix-piggies' => [],
|
||||
'firefly-iii:create-link-types' => [],
|
||||
'firefly-iii:create-access-tokens' => [],
|
||||
'firefly-iii:remove-bills' => [],
|
||||
'firefly-iii:fix-negative-limits' => [],
|
||||
'firefly-iii:enable-currencies' => [],
|
||||
'firefly-iii:fix-transfer-budgets' => [],
|
||||
'firefly-iii:fix-uneven-amount' => [],
|
||||
'firefly-iii:delete-zero-amount' => [],
|
||||
'firefly-iii:delete-orphaned-transactions' => [],
|
||||
'firefly-iii:delete-empty-journals' => [],
|
||||
'firefly-iii:delete-empty-groups' => [],
|
||||
'firefly-iii:fix-account-types' => [],
|
||||
'firefly-iii:fix-account-order' => [],
|
||||
'firefly-iii:rename-meta-fields' => [],
|
||||
'firefly-iii:fix-ob-currencies' => [],
|
||||
'firefly-iii:fix-long-descriptions' => [],
|
||||
'firefly-iii:fix-recurring-transactions' => [],
|
||||
'firefly-iii:unify-group-accounts' => [],
|
||||
'firefly-iii:fix-transaction-types' => [],
|
||||
'firefly-iii:fix-frontpage-accounts' => [],
|
||||
'firefly-iii:fix-ibans' => [],
|
||||
'firefly-iii:create-group-memberships' => [],
|
||||
'firefly-iii:upgrade-group-information' => [],
|
||||
|
||||
// final command to set the latest version in DB
|
||||
'firefly-iii:set-latest-version' => ['--james-is-cool' => true],
|
||||
'firefly-iii:verify-security-alerts' => [],
|
||||
// there are 5 initial commands
|
||||
// Check 4 places: InstallController, Docker image, UpgradeDatabase, composer.json
|
||||
'migrate' => ['--seed' => true, '--force' => true],
|
||||
'generate-keys' => [], // an exception :(
|
||||
'firefly-iii:upgrade-database' => [],
|
||||
'firefly-iii:correct-database' => [],
|
||||
'firefly-iii:report-integrity' => [],
|
||||
'firefly-iii:set-latest-version' => ['--james-is-cool' => true],
|
||||
'firefly-iii:verify-security-alerts' => [],
|
||||
];
|
||||
|
||||
$this->lastError = '';
|
||||
@@ -127,6 +81,7 @@ class InstallController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
app('view')->share('FF_VERSION', config('firefly.version'));
|
||||
// index will set FF3 version.
|
||||
app('fireflyconfig')->set('ff3_version', (string)config('firefly.version'));
|
||||
|
||||
@@ -147,27 +102,19 @@ class InstallController extends Controller
|
||||
$response = [
|
||||
'hasNextCommand' => false,
|
||||
'done' => true,
|
||||
'next' => 0,
|
||||
'previous' => null,
|
||||
'error' => false,
|
||||
'errorMessage' => null,
|
||||
];
|
||||
|
||||
Log::debug(sprintf('Will now run commands. Request index is %d', $requestIndex));
|
||||
$index = 0;
|
||||
/**
|
||||
* @var string $command
|
||||
* @var array $args
|
||||
*/
|
||||
foreach ($this->upgradeCommands as $command => $args) {
|
||||
Log::debug(sprintf('Current command is "%s", index is %d', $command, $index));
|
||||
if ($index < $requestIndex) {
|
||||
Log::debug('Will not execute.');
|
||||
$index++;
|
||||
continue;
|
||||
}
|
||||
$indexes = array_values(array_keys($this->upgradeCommands));
|
||||
if (array_key_exists($requestIndex, $indexes)) {
|
||||
$command = $indexes[$requestIndex];
|
||||
$parameters = $this->upgradeCommands[$command];
|
||||
Log::debug(sprintf('Will now execute command "%s" with parameters', $command), $parameters);
|
||||
try {
|
||||
$result = $this->executeCommand($command, $args);
|
||||
$result = $this->executeCommand($command, $parameters);
|
||||
} catch (FireflyException $e) {
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
@@ -180,15 +127,11 @@ class InstallController extends Controller
|
||||
if (false === $result) {
|
||||
$response['errorMessage'] = $this->lastError;
|
||||
$response['error'] = true;
|
||||
|
||||
return response()->json($response);
|
||||
}
|
||||
$index++;
|
||||
$response['hasNextCommand'] = true;
|
||||
$response['hasNextCommand'] = array_key_exists($requestIndex + 1, $indexes);
|
||||
$response['previous'] = $command;
|
||||
}
|
||||
$response['next'] = $index;
|
||||
|
||||
return response()->json($response);
|
||||
}
|
||||
|
||||
|
@@ -87,13 +87,10 @@ class Authenticate
|
||||
*/
|
||||
protected function authenticate($request, array $guards)
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
if (0 === count($guards)) {
|
||||
Log::debug('No guards present.');
|
||||
// go for default guard:
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
if ($this->auth->check()) {
|
||||
Log::debug('Default guard says user is authenticated.');
|
||||
// do an extra check on user object.
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
/** @var User $user */
|
||||
@@ -104,18 +101,13 @@ class Authenticate
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
return $this->auth->authenticate();
|
||||
}
|
||||
Log::debug('Guard array is not empty.');
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
Log::debug(sprintf('Now in guard loop, guard is "%s"', $guard));
|
||||
if ('api' !== $guard) {
|
||||
Log::debug('Guard is "api", call authenticate()');
|
||||
$this->auth->guard($guard)->authenticate();
|
||||
}
|
||||
$result = $this->auth->guard($guard)->check();
|
||||
Log::debug(sprintf('Result is %s', var_export($result, true)));
|
||||
if ($result) {
|
||||
Log::debug('Guard says user is authenticated.');
|
||||
$user = $this->auth->guard($guard)->user();
|
||||
$this->validateBlockedUser($user, $guards);
|
||||
// According to PHPstan the method returns void, but we'll see.
|
||||
@@ -134,7 +126,6 @@ class Authenticate
|
||||
*/
|
||||
private function validateBlockedUser(?User $user, array $guards): void
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
if (null === $user) {
|
||||
Log::warning('User is null, throw exception?');
|
||||
}
|
||||
|
@@ -110,7 +110,7 @@ class AccountFormRequest extends FormRequest
|
||||
$ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes')));
|
||||
$rules = [
|
||||
'administration_id' => 'min:1|max:16777216|numeric',
|
||||
'name' => 'required|min:1|uniqueAccountForUser',
|
||||
'name' => 'required|max:1024|min:1|uniqueAccountForUser',
|
||||
'opening_balance' => 'numeric|nullable|max:1000000000',
|
||||
'opening_balance_date' => 'date|required_with:opening_balance|nullable',
|
||||
'iban' => ['iban', 'nullable', new UniqueIban(null, $this->convertString('objectType'))],
|
||||
@@ -133,7 +133,7 @@ class AccountFormRequest extends FormRequest
|
||||
if (null !== $account) {
|
||||
// add rules:
|
||||
$rules['id'] = 'belongsToUser:accounts';
|
||||
$rules['name'] = 'required|min:1|uniqueAccountForUser:'.$account->id;
|
||||
$rules['name'] = 'required|max:1024|min:1|uniqueAccountForUser:'.$account->id;
|
||||
$rules['iban'] = ['iban', 'nullable', new UniqueIban($account, $account->accountType->type)];
|
||||
}
|
||||
|
||||
|
@@ -81,6 +81,7 @@ class BillUpdateRequest extends FormRequest
|
||||
'repeat_freq' => sprintf('required|in:%s', join(',', config('firefly.bill_periods'))),
|
||||
'skip' => 'required|integer|gte:0|lte:31',
|
||||
'active' => 'boolean',
|
||||
'notes' => 'between:1,65536|nullable',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@ class LinkTypeFormRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
// fixed
|
||||
$nameRule = 'required|min:1|unique:link_types,name';
|
||||
$nameRule = 'required|max:255|min:1|unique:link_types,name';
|
||||
$idRule = '';
|
||||
|
||||
// get parameter link:
|
||||
@@ -51,14 +51,14 @@ class LinkTypeFormRequest extends FormRequest
|
||||
|
||||
if (null !== $link) {
|
||||
$idRule = 'exists:link_types,id';
|
||||
$nameRule = 'required|min:1';
|
||||
$nameRule = 'required|max:255|min:1';
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $idRule,
|
||||
'name' => $nameRule,
|
||||
'inward' => 'required|min:1|different:outward',
|
||||
'outward' => 'required|min:1|different:inward',
|
||||
'inward' => 'required|max:255|min:1|different:outward',
|
||||
'outward' => 'required|max:255|min:1|different:inward',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ class MassEditJournalRequest extends FormRequest
|
||||
// fixed
|
||||
|
||||
return [
|
||||
'description.*' => 'required|min:1,max:255',
|
||||
'description.*' => 'required|min:1|max:255',
|
||||
'source_id.*' => 'numeric|belongsToUser:accounts,id',
|
||||
'destination_id.*' => 'numeric|belongsToUser:accounts,id',
|
||||
'journals.*' => 'numeric|belongsToUser:transaction_journals,id',
|
||||
|
@@ -70,7 +70,7 @@ class PiggyBankUpdateRequest extends FormRequest
|
||||
'targetamount' => 'nullable|numeric|max:1000000000',
|
||||
'startdate' => 'date',
|
||||
'targetdate' => 'date|nullable',
|
||||
'order' => 'integer|min:1',
|
||||
'order' => 'integer|max:65536|min:1',
|
||||
'object_group' => 'min:0|max:255',
|
||||
];
|
||||
}
|
||||
|
@@ -157,9 +157,9 @@ class RuleFormRequest extends FormRequest
|
||||
'rule_group_id' => 'required|belongsToUser:rule_groups',
|
||||
'trigger' => 'required|in:store-journal,update-journal',
|
||||
'triggers.*.type' => 'required|in:'.implode(',', $validTriggers),
|
||||
'triggers.*.value' => sprintf('required_if:triggers.*.type,%s|min:1|ruleTriggerValue', $contextTriggers),
|
||||
'triggers.*.value' => sprintf('required_if:triggers.*.type,%s|max:1024|min:1|ruleTriggerValue', $contextTriggers),
|
||||
'actions.*.type' => 'required|in:'.implode(',', $validActions),
|
||||
'actions.*.value' => sprintf('required_if:actions.*.type,%s|min:0|max:255|ruleActionValue', $contextActions),
|
||||
'actions.*.value' => sprintf('required_if:actions.*.type,%s|min:0|max:1024|ruleActionValue', $contextActions),
|
||||
'strict' => 'in:0,1',
|
||||
];
|
||||
|
||||
|
@@ -66,17 +66,18 @@ class TagFormRequest extends FormRequest
|
||||
|
||||
/** @var Tag $tag */
|
||||
$tag = $this->route()->parameter('tag');
|
||||
$tagRule = 'required|min:1|uniqueObjectForUser:tags,tag';
|
||||
$tagRule = 'required|max:1024|min:1|uniqueObjectForUser:tags,tag';
|
||||
if (null !== $tag) {
|
||||
$idRule = 'belongsToUser:tags';
|
||||
$tagRule = 'required|min:1|uniqueObjectForUser:tags,tag,'.$tag->id;
|
||||
$tagRule = 'required|max:1024|min:1|uniqueObjectForUser:tags,tag,'.$tag->id;
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'tag' => $tagRule,
|
||||
'id' => $idRule,
|
||||
'description' => 'min:1|nullable',
|
||||
'description' => 'max:65536|min:1|nullable',
|
||||
'date' => 'date|nullable',
|
||||
|
||||
];
|
||||
|
||||
return Location::requestRules($rules);
|
||||
|
@@ -49,8 +49,8 @@ class TestRuleFormRequest extends FormRequest
|
||||
$validTriggers = $this->getTriggers();
|
||||
|
||||
return [
|
||||
'rule-trigger.*' => 'required|min:1|in:'.implode(',', $validTriggers),
|
||||
'rule-trigger-value.*' => 'required|min:1|ruleTriggerValue',
|
||||
'rule-trigger.*' => 'required|max:1024|min:1|in:'.implode(',', $validTriggers),
|
||||
'rule-trigger-value.*' => 'required|max:1024|min:1|ruleTriggerValue',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -70,6 +70,8 @@ class AutoBudget extends Model
|
||||
public const AUTO_BUDGET_RESET = 1;
|
||||
public const AUTO_BUDGET_ROLLOVER = 2;
|
||||
|
||||
protected $fillable = ['budget_id','amount','period'];
|
||||
|
||||
/**
|
||||
* @return BelongsTo
|
||||
*/
|
||||
|
@@ -24,6 +24,9 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Eloquent;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Created;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Deleted;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Updated;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -81,6 +84,12 @@ class BudgetLimit extends Model
|
||||
/** @var array Fields that can be filled */
|
||||
protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount', 'transaction_currency_id'];
|
||||
|
||||
protected $dispatchesEvents = [
|
||||
'created' => Created::class,
|
||||
'updated' => Updated::class,
|
||||
'deleted' => Deleted::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Route binder. Converts the key in the URL to the specified object (or throw 404).
|
||||
*
|
||||
|
@@ -52,6 +52,8 @@ use Illuminate\Support\Carbon;
|
||||
* @property-read Collection<int, \FireflyIII\Models\Account> $accounts
|
||||
* @property-read int|null $accounts_count
|
||||
* @property-read Collection<int, \FireflyIII\Models\Account> $accounts
|
||||
* @property-read Collection<int, \FireflyIII\Models\Account> $accounts
|
||||
* @property-read Collection<int, \FireflyIII\Models\Account> $accounts
|
||||
* @mixin Eloquent
|
||||
*/
|
||||
class UserGroup extends Model
|
||||
|
@@ -29,6 +29,9 @@ use FireflyIII\Events\AdminRequestedTestMessage;
|
||||
use FireflyIII\Events\ChangedPiggyBankAmount;
|
||||
use FireflyIII\Events\DestroyedTransactionGroup;
|
||||
use FireflyIII\Events\DetectedNewIPAddress;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Created;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Deleted;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Updated;
|
||||
use FireflyIII\Events\NewVersionAvailable;
|
||||
use FireflyIII\Events\RegisteredUser;
|
||||
use FireflyIII\Events\RequestedNewPassword;
|
||||
@@ -42,6 +45,7 @@ use FireflyIII\Events\UpdatedAccount;
|
||||
use FireflyIII\Events\UpdatedTransactionGroup;
|
||||
use FireflyIII\Events\UserChangedEmail;
|
||||
use FireflyIII\Events\WarnUserAboutBill;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\PiggyBankRepetition;
|
||||
@@ -160,6 +164,17 @@ class EventServiceProvider extends ServiceProvider
|
||||
ChangedPiggyBankAmount::class => [
|
||||
'FireflyIII\Handlers\Events\PiggyBankEventHandler@changePiggyAmount',
|
||||
],
|
||||
// budget related events: CRUD budget limit
|
||||
Created::class => [
|
||||
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@created',
|
||||
],
|
||||
Updated::class => [
|
||||
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@updated',
|
||||
],
|
||||
Deleted::class => [
|
||||
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@deleted',
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -169,7 +184,6 @@ class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
parent::boot();
|
||||
$this->registerCreateEvents();
|
||||
$this->registerBudgetEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,57 +202,4 @@ class EventServiceProvider extends ServiceProvider
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO needs a dedicated method.
|
||||
*/
|
||||
protected function registerBudgetEvents(): void
|
||||
{
|
||||
$func = static function (BudgetLimit $limit) {
|
||||
Log::debug('Trigger budget limit event.');
|
||||
// find available budget with same period and same currency or create it.
|
||||
// then set it or add money:
|
||||
$user = $limit->budget->user;
|
||||
$availableBudget = $user
|
||||
->availableBudgets()
|
||||
->where('start_date', $limit->start_date->format('Y-m-d'))
|
||||
->where('end_date', $limit->end_date->format('Y-m-d'))
|
||||
->where('transaction_currency_id', $limit->transaction_currency_id)
|
||||
->first();
|
||||
// update!
|
||||
if (null !== $availableBudget) {
|
||||
$repository = app(BudgetLimitRepositoryInterface::class);
|
||||
$repository->setUser($user);
|
||||
$set = $repository->getAllBudgetLimitsByCurrency($limit->transactionCurrency, $limit->start_date, $limit->end_date);
|
||||
$sum = (string)$set->sum('amount');
|
||||
|
||||
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Because budget limit #%d had its amount changed to %s, available budget limit #%d will be updated.',
|
||||
$limit->id,
|
||||
$limit->amount,
|
||||
$availableBudget->id
|
||||
)
|
||||
);
|
||||
$availableBudget->amount = $sum;
|
||||
$availableBudget->save();
|
||||
return;
|
||||
}
|
||||
Log::debug('Does not exist, create it.');
|
||||
// create it.
|
||||
$data = [
|
||||
'amount' => $limit->amount,
|
||||
'start' => $limit->start_date,
|
||||
'end' => $limit->end_date,
|
||||
'currency_id' => $limit->transaction_currency_id,
|
||||
];
|
||||
$repository = app(AvailableBudgetRepositoryInterface::class);
|
||||
$repository->setUser($user);
|
||||
$repository->store($data);
|
||||
};
|
||||
|
||||
BudgetLimit::created($func);
|
||||
BudgetLimit::updated($func);
|
||||
}
|
||||
}
|
||||
|
@@ -84,10 +84,10 @@ class AttachmentRepository implements AttachmentRepositoryInterface
|
||||
|
||||
if ($disk->exists($file)) {
|
||||
$encryptedContent = (string)$disk->get($file);
|
||||
|
||||
try {
|
||||
$unencryptedContent = Crypt::decrypt($encryptedContent); // verified
|
||||
} catch (DecryptException $e) {
|
||||
Log::debug(sprintf('Could not decrypt attachment #%d but this is fine: %s', $attachment->id, $e->getMessage()));
|
||||
$unencryptedContent = $encryptedContent;
|
||||
}
|
||||
}
|
||||
|
@@ -298,7 +298,7 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
?Collection $budgets = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
//Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
|
||||
@@ -340,7 +340,7 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
|
||||
// same but for foreign currencies:
|
||||
if (null !== $currency) {
|
||||
Log::debug(sprintf('Currency is "%s".', $currency->name));
|
||||
//Log::debug(sprintf('Currency is "%s".', $currency->name));
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])
|
||||
@@ -350,7 +350,7 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
$collector->setAccounts($accounts);
|
||||
}
|
||||
$result = $collector->getExtractedJournals();
|
||||
Log::debug(sprintf('Found %d journals with currency %s.', count($result), $currency->code));
|
||||
//Log::debug(sprintf('Found %d journals with currency %s.', count($result), $currency->code));
|
||||
// do not use array_merge because you want keys to overwrite (otherwise you get double results):
|
||||
$journals = $result + $journals;
|
||||
}
|
||||
|
@@ -23,18 +23,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules;
|
||||
|
||||
use Closure;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class UniqueIban
|
||||
*/
|
||||
class UniqueIban implements Rule
|
||||
class UniqueIban implements ValidationRule
|
||||
{
|
||||
private ?Account $account;
|
||||
private ?string $expectedType;
|
||||
private array $expectedTypes;
|
||||
|
||||
/**
|
||||
* Create a new rule instance.
|
||||
@@ -45,17 +47,24 @@ class UniqueIban implements Rule
|
||||
*/
|
||||
public function __construct(?Account $account, ?string $expectedType)
|
||||
{
|
||||
$this->account = $account;
|
||||
$this->expectedType = $expectedType;
|
||||
$this->account = $account;
|
||||
$this->expectedTypes = [];
|
||||
if (null === $expectedType) {
|
||||
return;
|
||||
}
|
||||
$this->expectedTypes = [$expectedType];
|
||||
// a very basic fix to make sure we get the correct account type:
|
||||
if ('expense' === $expectedType) {
|
||||
$this->expectedType = AccountType::EXPENSE;
|
||||
$this->expectedTypes = [AccountType::EXPENSE];
|
||||
}
|
||||
if ('revenue' === $expectedType) {
|
||||
$this->expectedType = AccountType::REVENUE;
|
||||
$this->expectedTypes = [AccountType::REVENUE];
|
||||
}
|
||||
if ('asset' === $expectedType) {
|
||||
$this->expectedType = AccountType::ASSET;
|
||||
$this->expectedTypes = [AccountType::ASSET];
|
||||
}
|
||||
if ('liabilities' === $expectedType) {
|
||||
$this->expectedTypes = [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +93,7 @@ class UniqueIban implements Rule
|
||||
if (!auth()->check()) {
|
||||
return true;
|
||||
}
|
||||
if (null === $this->expectedType) {
|
||||
if (0 === count($this->expectedTypes)) {
|
||||
return true;
|
||||
}
|
||||
$maxCounts = $this->getMaxOccurrences();
|
||||
@@ -95,11 +104,11 @@ class UniqueIban implements Rule
|
||||
if ($count > $max) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'IBAN "%s" is in use with %d account(s) of type "%s", which is too much for expected type "%s"',
|
||||
'IBAN "%s" is in use with %d account(s) of type "%s", which is too much for expected types "%s"',
|
||||
$value,
|
||||
$count,
|
||||
$type,
|
||||
$this->expectedType
|
||||
join(', ', $this->expectedTypes)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -120,14 +129,15 @@ class UniqueIban implements Rule
|
||||
AccountType::ASSET => 0,
|
||||
AccountType::EXPENSE => 0,
|
||||
AccountType::REVENUE => 0,
|
||||
'liabilities' => 0,
|
||||
];
|
||||
|
||||
if ('expense' === $this->expectedType || AccountType::EXPENSE === $this->expectedType) {
|
||||
if (in_array('expense', $this->expectedTypes, true) || in_array(AccountType::EXPENSE, $this->expectedTypes, true)) {
|
||||
// IBAN should be unique amongst expense and asset accounts.
|
||||
// may appear once in revenue accounts
|
||||
$maxCounts[AccountType::REVENUE] = 1;
|
||||
}
|
||||
if ('revenue' === $this->expectedType || AccountType::REVENUE === $this->expectedType) {
|
||||
if (in_array('revenue', $this->expectedTypes, true) || in_array(AccountType::REVENUE, $this->expectedTypes, true)) {
|
||||
// IBAN should be unique amongst revenue and asset accounts.
|
||||
// may appear once in expense accounts
|
||||
$maxCounts[AccountType::EXPENSE] = 1;
|
||||
@@ -144,12 +154,16 @@ class UniqueIban implements Rule
|
||||
*/
|
||||
private function countHits(string $type, string $iban): int
|
||||
{
|
||||
$typesArray = [$type];
|
||||
if ('liabilities' === $type) {
|
||||
$typesArray = [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE];
|
||||
}
|
||||
$query
|
||||
= auth()->user()
|
||||
->accounts()
|
||||
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->where('accounts.iban', $iban)
|
||||
->where('account_types.type', $type);
|
||||
->whereIn('account_types.type', $typesArray);
|
||||
|
||||
if (null !== $this->account) {
|
||||
$query->where('accounts.id', '!=', $this->account->id);
|
||||
@@ -157,4 +171,14 @@ class UniqueIban implements Rule
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
if (!$this->passes($attribute, $value)) {
|
||||
$fail((string)trans('validation.unique_iban_for_user'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -52,6 +52,8 @@ class BudgetDestroyService
|
||||
DB::table('budget_transaction')->where('budget_id', (int)$budget->id)->delete();
|
||||
|
||||
// also delete all budget limits
|
||||
$budget->budgetlimits()->delete();
|
||||
foreach($budget->budgetlimits()->get() as $limit) {
|
||||
$limit->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -280,6 +280,23 @@ class Navigation
|
||||
|
||||
return $currentEnd;
|
||||
}
|
||||
|
||||
$result = match ($repeatFreq) {
|
||||
'last7' => $currentEnd->addDays(7)->startOfDay(),
|
||||
'last30' => $currentEnd->addDays(30)->startOfDay(),
|
||||
'last90' => $currentEnd->addDays(90)->startOfDay(),
|
||||
'last365' => $currentEnd->addDays(365)->startOfDay(),
|
||||
'MTD' => $currentEnd->startOfMonth()->startOfDay(),
|
||||
'QTD' => $currentEnd->firstOfQuarter()->startOfDay(),
|
||||
'YTD' => $currentEnd->startOfYear()->startOfDay(),
|
||||
default => null,
|
||||
};
|
||||
if (null !== $result) {
|
||||
return $result;
|
||||
}
|
||||
unset($result);
|
||||
|
||||
|
||||
if (!array_key_exists($repeatFreq, $functionMap)) {
|
||||
Log::error(sprintf('Cannot do endOfPeriod for $repeat_freq "%s"', $repeatFreq));
|
||||
|
||||
|
@@ -856,12 +856,12 @@ class OperatorQuerySearch implements SearchInterface
|
||||
break;
|
||||
case '-tag_is_not':
|
||||
case 'tag_is':
|
||||
$result = $this->tagRepository->searchTag($value);
|
||||
if ($result->count() > 0) {
|
||||
$this->collector->setTags($result);
|
||||
$result = $this->tagRepository->findByTag($value);
|
||||
if (null !== $result) {
|
||||
$this->collector->setTags(new Collection([$result]));
|
||||
}
|
||||
// no tags found means search must result in nothing.
|
||||
if (0 === $result->count()) {
|
||||
if (null === $result) {
|
||||
Log::info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
|
||||
$this->collector->findNothing();
|
||||
}
|
||||
|
@@ -273,6 +273,7 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
if (10 === strlen($string)) {
|
||||
return Carbon::createFromFormat('Y-m-d', $string, config('app.timezone'));
|
||||
}
|
||||
return Carbon::createFromFormat('Y-m-d H:i:s', $string, config('app.timezone'));
|
||||
// 2022-01-01 01:01:01
|
||||
return Carbon::createFromFormat('Y-m-d H:i:s', substr($string, 0, 19), config('app.timezone'));
|
||||
}
|
||||
}
|
||||
|
@@ -130,11 +130,13 @@ trait WithdrawalValidation
|
||||
{
|
||||
$accountId = array_key_exists('id', $array) ? $array['id'] : null;
|
||||
$accountName = array_key_exists('name', $array) ? $array['name'] : null;
|
||||
$accountIban = array_key_exists('iban', $array) ? $array['iban'] : null;
|
||||
$accountNumber =array_key_exists('number', $array) ? $array['number'] : null;
|
||||
|
||||
Log::debug('Now in validateWithdrawalSource', $array);
|
||||
// source can be any of the following types.
|
||||
$validTypes = array_keys($this->combinations[$this->transactionType]);
|
||||
if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) {
|
||||
if (null === $accountId && null === $accountName && null === $accountNumber && null === $accountIban && false === $this->canCreateTypes($validTypes)) {
|
||||
// if both values are NULL we return false,
|
||||
// because the source of a withdrawal can't be created.
|
||||
$this->sourceError = (string)trans('validation.withdrawal_source_need_data');
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Validation;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use Illuminate\Validation\Validator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -53,6 +54,9 @@ trait GroupValidation
|
||||
];
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $index => $transaction) {
|
||||
if(!is_array($transaction)) {
|
||||
throw new FireflyException('Invalid data submitted: transaction is not array.');
|
||||
}
|
||||
$hasAccountInfo = false;
|
||||
$hasJournalId = array_key_exists('transaction_journal_id', $transaction);
|
||||
foreach ($keys as $key) {
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Validation;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
@@ -30,8 +31,8 @@ use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use Illuminate\Validation\Validator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
* Trait TransactionValidation
|
||||
@@ -74,19 +75,22 @@ trait TransactionValidation
|
||||
*/
|
||||
protected function getTransactionsArray(Validator $validator): array
|
||||
{
|
||||
Log::debug('Now in getTransactionsArray');
|
||||
$data = $validator->getData();
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
$transactions = [];
|
||||
if (is_array($data) && array_key_exists('transactions', $data) && is_array($data['transactions'])) {
|
||||
Log::debug('Transactions key exists and is array.');
|
||||
$transactions = $data['transactions'];
|
||||
}
|
||||
if (is_array($data) && array_key_exists('transactions', $data) && !is_array($data['transactions'])) {
|
||||
Log::debug(sprintf('Transactions key exists but is NOT array, its a %s', gettype($data['transactions'])));
|
||||
}
|
||||
// should be impossible to hit this:
|
||||
if (!is_countable($transactions)) {
|
||||
Log::error(sprintf('Transactions array is not countable, because its a %s', gettype($transactions)));
|
||||
|
||||
return [];
|
||||
}
|
||||
// a superfluous check but you never know.
|
||||
if (!is_array($transactions)) {
|
||||
Log::error(sprintf('Transactions array is not an array, because its a %s', gettype($transactions)));
|
||||
|
||||
return [];
|
||||
}
|
||||
//Log::debug('Returning transactions.', $transactions);
|
||||
|
||||
return $transactions;
|
||||
}
|
||||
@@ -355,6 +359,9 @@ trait TransactionValidation
|
||||
* @var array $transaction
|
||||
*/
|
||||
foreach ($transactions as $index => $transaction) {
|
||||
if(!is_int($index)) {
|
||||
throw new FireflyException('Invalid data submitted: transaction is not array.');
|
||||
}
|
||||
$this->validateSingleUpdate($validator, $index, $transaction, $transactionGroup);
|
||||
}
|
||||
}
|
||||
@@ -489,10 +496,11 @@ trait TransactionValidation
|
||||
*/
|
||||
public function validateOneTransaction(Validator $validator): void
|
||||
{
|
||||
Log::debug('Now in validateOneTransaction');
|
||||
if ($validator->errors()->count() > 0) {
|
||||
Log::debug('Validator already has errors, so return.');
|
||||
return;
|
||||
}
|
||||
Log::debug('Now in validateOneTransaction()');
|
||||
$transactions = $this->getTransactionsArray($validator);
|
||||
// need at least one transaction
|
||||
if (0 === count($transactions)) {
|
||||
|
52
changelog.md
52
changelog.md
@@ -2,6 +2,58 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## v6.0.10 - 2023-05-14
|
||||
|
||||
### Added
|
||||
- The debug screen will also report on the build version of the BASE image.
|
||||
|
||||
### Changed
|
||||
- Health check will also check if the database is up.
|
||||
- [Issue 7461](https://github.com/firefly-iii/firefly-iii/issues/7461) MFA field will now autofocus, thanks @eandersons!
|
||||
|
||||
### Removed
|
||||
- IBAN check no longer triggers on empty IBANs
|
||||
|
||||
### Fixed
|
||||
- Account validation when you only submit an IBAN.
|
||||
- [Issue 7478](https://github.com/firefly-iii/firefly-iii/issues/7478) [issue 7457](https://github.com/firefly-iii/firefly-iii/issues/7457) Various fixes in budget limit and available amount management.
|
||||
- [Issue 7446](https://github.com/firefly-iii/firefly-iii/issues/7446) Bills "Next expected match" was incorrect
|
||||
- [Issue 7456](https://github.com/firefly-iii/firefly-iii/issues/7456) Missing date calculation fields.
|
||||
- [Issue 7448](https://github.com/firefly-iii/firefly-iii/issues/7448) [issue 7444](https://github.com/firefly-iii/firefly-iii/issues/7444) Dark mode bad CSS
|
||||
|
||||
## 6.0.9 - 2023-04-29
|
||||
|
||||
### Added
|
||||
- Better length validation for text fields.
|
||||
|
||||
### Changed
|
||||
- Better calculation of available budget
|
||||
|
||||
### Fixed
|
||||
- [Issue 7377](https://github.com/firefly-iii/firefly-iii/issues/7377) Tag search was broken
|
||||
- [Issue 7389](https://github.com/firefly-iii/firefly-iii/issues/7389) Bug in charts
|
||||
- [Issue 7394](https://github.com/firefly-iii/firefly-iii/issues/7394) unique iban check was broken
|
||||
- [Issue 7427](https://github.com/firefly-iii/firefly-iii/issues/7427) API would not accept page 18 and up.
|
||||
- [Issue 7410](https://github.com/firefly-iii/firefly-iii/issues/7410) Various dark mode color fixes
|
||||
- Old documentation links fixed by @mindlessroman and @noxonad!
|
||||
|
||||
## 6.0.8 - 2023-04-16
|
||||
|
||||
### Added
|
||||
- [Issue 7351](https://github.com/firefly-iii/firefly-iii/issues/7351) Optional command to force the decimal size.
|
||||
- [Issue 7352](https://github.com/firefly-iii/firefly-iii/issues/7352) Optional command to force the migrations.
|
||||
- [Issue 7354](https://github.com/firefly-iii/firefly-iii/issues/7354) The new v3 layout will redirect to the index when unauthenticated, thanks @corcom!
|
||||
|
||||
### Fixed
|
||||
- [Issue 7349](https://github.com/firefly-iii/firefly-iii/issues/7349) Missing tables in PostgreSQL script.
|
||||
- [Issue 7358](https://github.com/firefly-iii/firefly-iii/issues/7358) Could not create liabilities with a pre-set amount.
|
||||
- Fix date field in bill warning mail.
|
||||
- Fix installer script.
|
||||
- Remove attachment paperclip from transactions with deleted attachments.
|
||||
|
||||
### API
|
||||
- [Issue 7347](https://github.com/firefly-iii/firefly-iii/issues/7347) API made rules would be inactive by default.
|
||||
|
||||
## v6.0.7 - 2023-04-09
|
||||
|
||||
### Added
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user