mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-12-03 11:32:00 +00:00
Compare commits
142 Commits
v6.1.10
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d13263482 | ||
|
|
d9ff252915 | ||
|
|
51ba550251 | ||
|
|
fd21c467ad | ||
|
|
9aa90650b4 | ||
|
|
db0dbcfcf1 | ||
|
|
f591996f04 | ||
|
|
b08d385586 | ||
|
|
20ef22f67e | ||
|
|
c888baf542 | ||
|
|
8b0af3f666 | ||
|
|
7043e1e7c0 | ||
|
|
c5854eba23 | ||
|
|
ddf1a8cebb | ||
|
|
7dcaf167e9 | ||
|
|
b359d51d3a | ||
|
|
3913fa5086 | ||
|
|
ab2772abe0 | ||
|
|
bc7875b17b | ||
|
|
4938fa9990 | ||
|
|
84df2c80ee | ||
|
|
dc17060754 | ||
|
|
e2fa81dddc | ||
|
|
182dfc95fe | ||
|
|
c8979b6c33 | ||
|
|
ab872e8912 | ||
|
|
d36b94fabf | ||
|
|
e3d4ceaecb | ||
|
|
e3a6e5b788 | ||
|
|
57235c0e00 | ||
|
|
2298c3ddaf | ||
|
|
7224f1be6f | ||
|
|
1bd3019c16 | ||
|
|
f0fa21dead | ||
|
|
845eaed8d7 | ||
|
|
b3649cd4d0 | ||
|
|
55f14c587b | ||
|
|
441a8a8408 | ||
|
|
060c9648f1 | ||
|
|
7680c8733f | ||
|
|
5a0af5c93b | ||
|
|
f4b066add1 | ||
|
|
9ecb414b02 | ||
|
|
ad4f908c24 | ||
|
|
025f739442 | ||
|
|
6df7354c48 | ||
|
|
3f77c845ca | ||
|
|
d4771f7a5c | ||
|
|
ec4e2bfa4f | ||
|
|
dfdbfae4b5 | ||
|
|
349d38b956 | ||
|
|
2267aa3ac4 | ||
|
|
2323aa454e | ||
|
|
8b3317b665 | ||
|
|
15f893c343 | ||
|
|
309b3e765e | ||
|
|
d3fad06e00 | ||
|
|
834f24c99c | ||
|
|
35291e1298 | ||
|
|
ac4e9dcbc5 | ||
|
|
d57806f2ba | ||
|
|
3b005c317d | ||
|
|
e91903fed2 | ||
|
|
fee2002b0f | ||
|
|
f12e502eb8 | ||
|
|
24e62b1cee | ||
|
|
f559ec73e0 | ||
|
|
530b501fcf | ||
|
|
d5ea78025e | ||
|
|
3413b9b5b5 | ||
|
|
0b45c1aa76 | ||
|
|
5718d1690a | ||
|
|
67b16cc070 | ||
|
|
5746ac3247 | ||
|
|
8a2c520b11 | ||
|
|
f46c14df8c | ||
|
|
009fbba491 | ||
|
|
53d84347c2 | ||
|
|
1961487055 | ||
|
|
c9ce5df74b | ||
|
|
1371b6773e | ||
|
|
b9f1baf150 | ||
|
|
66b322e844 | ||
|
|
487b65b669 | ||
|
|
9078781d61 | ||
|
|
1ec830521a | ||
|
|
c4bf2aae7d | ||
|
|
69ca88d9f8 | ||
|
|
b38b7b2534 | ||
|
|
f19bfc3b4b | ||
|
|
d22f9c09d7 | ||
|
|
fc2da9eb42 | ||
|
|
f2c9e20aef | ||
|
|
16b8ca2746 | ||
|
|
46ea074821 | ||
|
|
d2c89781e2 | ||
|
|
e54d711891 | ||
|
|
84d3ad4764 | ||
|
|
b908951a2d | ||
|
|
8b87deea58 | ||
|
|
0d7325b3dc | ||
|
|
a3fd99a498 | ||
|
|
0ff405d1e0 | ||
|
|
46a60af966 | ||
|
|
591c9e3b39 | ||
|
|
c30461b20b | ||
|
|
2c3f86d9bc | ||
|
|
34349e4475 | ||
|
|
6acd5be5dc | ||
|
|
55a2b4e789 | ||
|
|
f41397eb43 | ||
|
|
41fc1e8f82 | ||
|
|
bee219ebf7 | ||
|
|
438f602961 | ||
|
|
429e72e681 | ||
|
|
7a134781f2 | ||
|
|
b572c1dcd3 | ||
|
|
95593f847b | ||
|
|
b82fcbd97b | ||
|
|
daddee7806 | ||
|
|
930a08ec90 | ||
|
|
fd2edf3b23 | ||
|
|
0597255c08 | ||
|
|
955ab38a85 | ||
|
|
1311a0db8b | ||
|
|
0ce9ee6a6c | ||
|
|
3a339382d4 | ||
|
|
a5b15bbc16 | ||
|
|
fbf89fd514 | ||
|
|
b3223feba2 | ||
|
|
88a9bc379e | ||
|
|
b442b91b7c | ||
|
|
9fadbbe087 | ||
|
|
1ef7239276 | ||
|
|
ea573e9434 | ||
|
|
34fa24e4a8 | ||
|
|
a1be4a4d8a | ||
|
|
b8e8af1e2a | ||
|
|
c13a3fb30c | ||
|
|
cb8fa4e1f4 | ||
|
|
bf7f4f9887 | ||
|
|
af48548e81 |
@@ -22,6 +22,9 @@
|
||||
|
||||
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
echo "Running PHP CS Fixer"
|
||||
$SCRIPT_DIR/phpcs.sh
|
||||
echo "Running PHPStan"
|
||||
$SCRIPT_DIR/phpstan.sh
|
||||
echo "Running PHPMD"
|
||||
$SCRIPT_DIR/phpmd.sh
|
||||
|
||||
24
.ci/php-cs-fixer/composer.lock
generated
24
.ci/php-cs-fixer/composer.lock
generated
@@ -8,16 +8,16 @@
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/pcre.git",
|
||||
"reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9"
|
||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9",
|
||||
"reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -59,7 +59,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/pcre/issues",
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.1"
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -75,7 +75,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-10-11T07:11:09+00:00"
|
||||
"time": "2024-03-07T15:38:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
@@ -226,16 +226,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.51.0",
|
||||
"version": "v3.52.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "127fa74f010da99053e3f5b62672615b72dd6efd"
|
||||
"reference": "a3564bd66f4bce9bc871ef18b690e2dc67a7f969"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/127fa74f010da99053e3f5b62672615b72dd6efd",
|
||||
"reference": "127fa74f010da99053e3f5b62672615b72dd6efd",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a3564bd66f4bce9bc871ef18b690e2dc67a7f969",
|
||||
"reference": "a3564bd66f4bce9bc871ef18b690e2dc67a7f969",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -306,7 +306,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.51.0"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.52.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -314,7 +314,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-28T19:50:06+00:00"
|
||||
"time": "2024-03-18T18:40:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
|
||||
12
.ci/phpmd/composer.lock
generated
12
.ci/phpmd/composer.lock
generated
@@ -9,16 +9,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/pcre.git",
|
||||
"reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9"
|
||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9",
|
||||
"reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -60,7 +60,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/pcre/issues",
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.1"
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -76,7 +76,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-10-11T07:11:09+00:00"
|
||||
"time": "2024-03-07T15:38:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/xdebug-handler",
|
||||
|
||||
@@ -184,6 +184,11 @@ SEND_REPORT_JOURNALS=true
|
||||
# Since this involves an external service, it's optional and disabled by default.
|
||||
ENABLE_EXTERNAL_MAP=false
|
||||
|
||||
#
|
||||
# Enable or disable exchange rate conversion. This function isn't used yet by Firefly III
|
||||
#
|
||||
ENABLE_EXCHANGE_RATES=false
|
||||
|
||||
# Set this value to true if you want Firefly III to download currency exchange rates
|
||||
# from the internet. These rates are hosted by the creator of Firefly III inside
|
||||
# an Azure Storage Container.
|
||||
|
||||
2
.github/CODEOWNERS
vendored
Normal file
2
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# code owners for this Firefly III related repository
|
||||
* @JC5 @SDx3
|
||||
4
.github/funding.yml
vendored
4
.github/funding.yml
vendored
@@ -1,4 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
# Firefly III sponsor options
|
||||
|
||||
github: jc5
|
||||
patreon: JC5
|
||||
ko_fi: jamesc5
|
||||
liberapay: JC5
|
||||
|
||||
18
.github/workflows/lock.yml
vendored
18
.github/workflows/lock.yml
vendored
@@ -5,15 +5,25 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
concurrency:
|
||||
group: lock-threads
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: JC5/lock-threads@main
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: 7
|
||||
pr-inactive-days: 7
|
||||
issue-inactive-days: 21
|
||||
pr-inactive-days: 21
|
||||
discussion-inactive-days: 21
|
||||
log-output: true
|
||||
|
||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -115,7 +115,8 @@ jobs:
|
||||
GH_TOKEN: ''
|
||||
- name: Build new JS
|
||||
run: |
|
||||
npm upgrade
|
||||
pwd
|
||||
npm install
|
||||
npm run build
|
||||
- name: Build old JS
|
||||
id: old-js
|
||||
|
||||
9
.github/workflows/sonarcloud.yml
vendored
9
.github/workflows/sonarcloud.yml
vendored
@@ -45,15 +45,6 @@ jobs:
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --prefer-dist --no-interaction --no-progress --no-scripts
|
||||
|
||||
- name: PHPStan
|
||||
run: .ci/phpstan.sh
|
||||
|
||||
- name: PHPMD
|
||||
run: .ci/phpmd.sh
|
||||
|
||||
- name: PHP CS Fixer
|
||||
run: .ci/phpcs.sh
|
||||
|
||||
- name: "Create database file"
|
||||
run: touch storage/database/database.sqlite
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,3 +7,6 @@ yarn-error.log
|
||||
.env
|
||||
/.ci/php-cs-fixer/vendor
|
||||
coverage.xml
|
||||
|
||||
# ignore generated files.
|
||||
#public/build
|
||||
|
||||
47
app/Api/V1/Controllers/Models/Rule/ExpressionController.php
Normal file
47
app/Api/V1/Controllers/Models/Rule/ExpressionController.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/*
|
||||
* ExpressionController.php
|
||||
* Copyright (c) 2024 Michael Thomas
|
||||
*
|
||||
* 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\Api\V1\Controllers\Models\Rule;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Rule\ValidateExpressionRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* Class ExpressionController
|
||||
*/
|
||||
class ExpressionController extends Controller
|
||||
{
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/rules/validateExpression
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function validateExpression(ValidateExpressionRequest $request): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'valid' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -281,7 +281,7 @@ class BasicController extends Controller
|
||||
$spentInCurrency = $row['sum'];
|
||||
$leftToSpend = bcadd($amount, $spentInCurrency);
|
||||
|
||||
$days = $today->diffInDays($end) + 1;
|
||||
$days = (int)$today->diffInDays($end, true) + 1;
|
||||
$perDay = '0';
|
||||
if (0 !== $days && bccomp($leftToSpend, '0') > -1) {
|
||||
$perDay = bcdiv($leftToSpend, (string)$days);
|
||||
|
||||
@@ -46,7 +46,7 @@ class DateRequest extends FormRequest
|
||||
{
|
||||
$start = $this->getCarbonDate('start');
|
||||
$end = $this->getCarbonDate('end');
|
||||
if ($start->diffInYears($end) > 5) {
|
||||
if ($start->diffInYears($end, true) > 5) {
|
||||
throw new FireflyException('Date range out of range.');
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Requests\Models\Rule;
|
||||
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Rules\IsValidActionExpression;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetRuleConfiguration;
|
||||
@@ -57,7 +58,6 @@ class StoreRequest extends FormRequest
|
||||
'active' => ['active', 'boolean'],
|
||||
];
|
||||
$data = $this->getAllData($fields);
|
||||
|
||||
$data['triggers'] = $this->getRuleTriggers();
|
||||
$data['actions'] = $this->getRuleActions();
|
||||
|
||||
@@ -123,7 +123,7 @@ class StoreRequest extends FormRequest
|
||||
'triggers.*.stop_processing' => [new IsBoolean()],
|
||||
'triggers.*.active' => [new IsBoolean()],
|
||||
'actions.*.type' => 'required|in:'.implode(',', $validActions),
|
||||
'actions.*.value' => 'required_if:actions.*.type,'.$contextActions.'|ruleActionValue',
|
||||
'actions.*.value' => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'],
|
||||
'actions.*.stop_processing' => [new IsBoolean()],
|
||||
'actions.*.active' => [new IsBoolean()],
|
||||
'strict' => [new IsBoolean()],
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace FireflyIII\Api\V1\Requests\Models\Rule;
|
||||
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Rules\IsValidActionExpression;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetRuleConfiguration;
|
||||
@@ -140,7 +141,7 @@ class UpdateRequest extends FormRequest
|
||||
'triggers.*.stop_processing' => [new IsBoolean()],
|
||||
'triggers.*.active' => [new IsBoolean()],
|
||||
'actions.*.type' => 'required|in:'.implode(',', $validActions),
|
||||
'actions.*.value' => 'required_if:actions.*.type,'.$contextActions.'|ruleActionValue',
|
||||
'actions.*.value' => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'],
|
||||
'actions.*.stop_processing' => [new IsBoolean()],
|
||||
'actions.*.active' => [new IsBoolean()],
|
||||
'strict' => [new IsBoolean()],
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ValidateExpressionRequest.php
|
||||
* Copyright (c) 2024 Michael Thomas
|
||||
*
|
||||
* 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\Api\V1\Requests\Models\Rule;
|
||||
|
||||
use FireflyIII\Rules\IsValidActionExpression;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Class ValidateExpressionRequest
|
||||
*/
|
||||
class ValidateExpressionRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return ['expression' => ['required', new IsValidActionExpression()]];
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,8 @@ class Controller extends BaseController
|
||||
|
||||
// the transformer, at this point, needs to collect information that ALL items in the collection
|
||||
// require, like meta-data and stuff like that, and save it for later.
|
||||
$transformer->collectMetaData($objects);
|
||||
$objects = $transformer->collectMetaData($objects);
|
||||
$paginator->setCollection($objects);
|
||||
|
||||
$resource = new FractalCollection($objects, $transformer, $key);
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
104
app/Api/V2/Controllers/Model/Account/IndexController.php
Normal file
104
app/Api/V2/Controllers/Model/Account/IndexController.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/*
|
||||
* IndexController.php
|
||||
* Copyright (c) 2024 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\Api\V2\Controllers\Model\Account;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Account\IndexRequest;
|
||||
use FireflyIII\Api\V2\Request\Model\Transaction\InfiniteListRequest;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\AccountTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
public const string RESOURCE_KEY = 'accounts';
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO see autocomplete/accountcontroller for list.
|
||||
*/
|
||||
public function index(IndexRequest $request): JsonResponse
|
||||
{
|
||||
$this->repository->resetAccountOrder();
|
||||
$types = $request->getAccountTypes();
|
||||
$instructions = $request->getSortInstructions('accounts');
|
||||
$accounts = $this->repository->getAccountsByType($types, $instructions);
|
||||
$pageSize = $this->parameters->get('limit');
|
||||
$count = $accounts->count();
|
||||
$accounts = $accounts->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
|
||||
$transformer = new AccountTransformer();
|
||||
|
||||
$this->parameters->set('sort', $instructions);
|
||||
$transformer->setParameters($this->parameters); // give params to transformer
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList('accounts', $paginator, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
public function infiniteList(InfiniteListRequest $request): JsonResponse
|
||||
{
|
||||
$this->repository->resetAccountOrder();
|
||||
|
||||
// get accounts of the specified type, and return.
|
||||
$types = $request->getAccountTypes();
|
||||
|
||||
// get from repository
|
||||
$accounts = $this->repository->getAccountsInOrder($types, $request->getSortInstructions('accounts'), $request->getStartRow(), $request->getEndRow());
|
||||
$total = $this->repository->countAccounts($types);
|
||||
$count = $request->getEndRow() - $request->getStartRow();
|
||||
$paginator = new LengthAwarePaginator($accounts, $total, $count, $this->parameters->get('page'));
|
||||
$transformer = new AccountTransformer();
|
||||
$transformer->setParameters($this->parameters); // give params to transformer
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
}
|
||||
79
app/Api/V2/Controllers/Model/Account/UpdateController.php
Normal file
79
app/Api/V2/Controllers/Model/Account/UpdateController.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/*
|
||||
* UpdateController.php
|
||||
* Copyright (c) 2024 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\Api\V2\Controllers\Model\Account;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Account\UpdateRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\AccountTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class UpdateController extends Controller
|
||||
{
|
||||
public const string RESOURCE_KEY = 'accounts';
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO this endpoint is not yet reachable.
|
||||
*/
|
||||
public function update(UpdateRequest $request, Account $account): JsonResponse
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
$data = $request->getUpdateData();
|
||||
$data['type'] = config('firefly.shortNamesByFullName.'.$account->accountType->type);
|
||||
$account = $this->repository->update($account, $data);
|
||||
$account->refresh();
|
||||
app('preferences')->mark();
|
||||
|
||||
$transformer = new AccountTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()
|
||||
->api($this->jsonApiObject('accounts', $account, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -298,7 +298,7 @@ class BasicController extends Controller
|
||||
app('log')->debug(sprintf('Amount left is %s', $left));
|
||||
|
||||
// how much left per day?
|
||||
$days = $today->diffInDays($end) + 1;
|
||||
$days = (int) $today->diffInDays($end, true) + 1;
|
||||
$perDay = '0';
|
||||
$perDayNative = '0';
|
||||
if (0 !== $days && bccomp($left, '0') > -1) {
|
||||
|
||||
@@ -35,6 +35,47 @@ use Illuminate\Http\JsonResponse;
|
||||
*/
|
||||
class TransactionController extends Controller
|
||||
{
|
||||
public function infiniteList(InfiniteListRequest $request): JsonResponse
|
||||
{
|
||||
// get sort instructions
|
||||
$instructions = $request->getSortInstructions('transactions');
|
||||
|
||||
// collect transactions:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUserGroup(auth()->user()->userGroup)
|
||||
->withAPIInformation()
|
||||
->setStartRow($request->getStartRow())
|
||||
->setEndRow($request->getEndRow())
|
||||
->setTypes($request->getTransactionTypes())
|
||||
->setSorting($instructions)
|
||||
;
|
||||
|
||||
$start = $this->parameters->get('start');
|
||||
$end = $this->parameters->get('end');
|
||||
if (null !== $start) {
|
||||
$collector->setStart($start);
|
||||
}
|
||||
if (null !== $end) {
|
||||
$collector->setEnd($end);
|
||||
}
|
||||
|
||||
$paginator = $collector->getPaginatedGroups();
|
||||
$params = $request->buildParams();
|
||||
$paginator->setPath(
|
||||
sprintf(
|
||||
'%s?%s',
|
||||
route('api.v2.infinite.transactions.list'),
|
||||
$params
|
||||
)
|
||||
);
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer()))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
public function list(ListRequest $request): JsonResponse
|
||||
{
|
||||
// collect transactions:
|
||||
@@ -75,45 +116,4 @@ class TransactionController extends Controller
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
public function infiniteList(InfiniteListRequest $request): JsonResponse
|
||||
{
|
||||
// get sort instructions
|
||||
$instructions = $request->getSortInstructions();
|
||||
|
||||
// collect transactions:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUserGroup(auth()->user()->userGroup)
|
||||
->withAPIInformation()
|
||||
->setStartRow($request->getStartRow())
|
||||
->setEndRow($request->getEndRow())
|
||||
->setTypes($request->getTransactionTypes())
|
||||
->setSorting($instructions)
|
||||
;
|
||||
|
||||
$start = $this->parameters->get('start');
|
||||
$end = $this->parameters->get('end');
|
||||
if (null !== $start) {
|
||||
$collector->setStart($start);
|
||||
}
|
||||
if (null !== $end) {
|
||||
$collector->setEnd($end);
|
||||
}
|
||||
|
||||
$paginator = $collector->getPaginatedGroups();
|
||||
$params = $request->buildParams();
|
||||
$paginator->setPath(
|
||||
sprintf(
|
||||
'%s?%s',
|
||||
route('api.v2.infinite.transactions.list'),
|
||||
$params
|
||||
)
|
||||
);
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer()))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
69
app/Api/V2/Request/Model/Account/IndexRequest.php
Normal file
69
app/Api/V2/Request/Model/Account/IndexRequest.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/*
|
||||
* IndexRequest.php
|
||||
* Copyright (c) 2024 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\Api\V2\Request\Model\Account;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetSortInstructions;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Class IndexRequest
|
||||
*
|
||||
* Lots of code stolen from the SingleDateRequest.
|
||||
*/
|
||||
class IndexRequest extends FormRequest
|
||||
{
|
||||
use AccountFilter;
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use GetSortInstructions;
|
||||
|
||||
public function getAccountTypes(): array
|
||||
{
|
||||
$type = (string)$this->get('type', 'default');
|
||||
|
||||
return $this->mapAccountTypes($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data from the request.
|
||||
*/
|
||||
public function getDate(): Carbon
|
||||
{
|
||||
return $this->getCarbonDate('date');
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
];
|
||||
}
|
||||
}
|
||||
116
app/Api/V2/Request/Model/Account/UpdateRequest.php
Normal file
116
app/Api/V2/Request/Model/Account/UpdateRequest.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/*
|
||||
* UpdateRequest.php
|
||||
* Copyright (c) 2024 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\Api\V2\Request\Model\Account;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Location;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Rules\UniqueAccountNumber;
|
||||
use FireflyIII\Rules\UniqueIban;
|
||||
use FireflyIII\Support\Request\AppendsLocationData;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateRequest extends FormRequest
|
||||
{
|
||||
use AppendsLocationData;
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
|
||||
/**
|
||||
* TODO is a duplicate of the v1 update thing.
|
||||
*/
|
||||
public function getUpdateData(): array
|
||||
{
|
||||
$fields = [
|
||||
'name' => ['name', 'convertString'],
|
||||
'active' => ['active', 'boolean'],
|
||||
'include_net_worth' => ['include_net_worth', 'boolean'],
|
||||
'account_type_name' => ['type', 'convertString'],
|
||||
'virtual_balance' => ['virtual_balance', 'convertString'],
|
||||
'iban' => ['iban', 'convertString'],
|
||||
'BIC' => ['bic', 'convertString'],
|
||||
'account_number' => ['account_number', 'convertString'],
|
||||
'account_role' => ['account_role', 'convertString'],
|
||||
'liability_type' => ['liability_type', 'convertString'],
|
||||
'opening_balance' => ['opening_balance', 'convertString'],
|
||||
'opening_balance_date' => ['opening_balance_date', 'convertDateTime'],
|
||||
'cc_type' => ['credit_card_type', 'convertString'],
|
||||
'cc_monthly_payment_date' => ['monthly_payment_date', 'convertDateTime'],
|
||||
'notes' => ['notes', 'stringWithNewlines'],
|
||||
'interest' => ['interest', 'convertString'],
|
||||
'interest_period' => ['interest_period', 'convertString'],
|
||||
'order' => ['order', 'convertInteger'],
|
||||
'currency_id' => ['currency_id', 'convertInteger'],
|
||||
'currency_code' => ['currency_code', 'convertString'],
|
||||
'liability_direction' => ['liability_direction', 'convertString'],
|
||||
'liability_amount' => ['liability_amount', 'convertString'],
|
||||
'liability_start_date' => ['liability_start_date', 'date'],
|
||||
];
|
||||
$data = $this->getAllData($fields);
|
||||
|
||||
return $this->appendLocationData($data, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO is a duplicate of the v1 UpdateRequest method.
|
||||
*
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
/** @var Account $account */
|
||||
$account = $this->route()->parameter('account');
|
||||
$accountRoles = implode(',', config('firefly.accountRoles'));
|
||||
$types = implode(',', array_keys(config('firefly.subTitlesByIdentifier')));
|
||||
$ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes')));
|
||||
|
||||
$rules = [
|
||||
'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',
|
||||
'account_number' => ['min:1', 'max:255', 'nullable', new UniqueAccountNumber($account, $this->convertString('type'))],
|
||||
'opening_balance' => 'numeric|required_with:opening_balance_date|nullable',
|
||||
'opening_balance_date' => 'date|required_with:opening_balance|nullable',
|
||||
'virtual_balance' => 'numeric|nullable',
|
||||
'order' => 'numeric|nullable',
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'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),
|
||||
'credit_card_type' => sprintf('in:%s|nullable|required_if:account_role,ccAsset', $ccPaymentTypes),
|
||||
'monthly_payment_date' => 'date|nullable|required_if:account_role,ccAsset|required_if:credit_card_type,monthlyFull',
|
||||
'liability_type' => 'required_if:type,liability|in:loan,debt,mortgage',
|
||||
'liability_direction' => 'required_if:type,liability|in:credit,debit',
|
||||
'interest' => 'required_if:type,liability|min:0|max:100|numeric',
|
||||
'interest_period' => 'required_if:type,liability|in:daily,monthly,yearly',
|
||||
'notes' => 'min:0|max:32768',
|
||||
];
|
||||
|
||||
return Location::requestRules($rules);
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,11 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V2\Request\Model\Transaction;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetSortInstructions;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
@@ -36,8 +38,10 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
*/
|
||||
class InfiniteListRequest extends FormRequest
|
||||
{
|
||||
use AccountFilter;
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use GetSortInstructions;
|
||||
use TransactionFilter;
|
||||
|
||||
public function buildParams(): string
|
||||
@@ -81,6 +85,13 @@ class InfiniteListRequest extends FormRequest
|
||||
return $this->getCarbonDate('end');
|
||||
}
|
||||
|
||||
public function getAccountTypes(): array
|
||||
{
|
||||
$type = (string)$this->get('type', 'default');
|
||||
|
||||
return $this->mapAccountTypes($type);
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
$page = $this->convertInteger('page');
|
||||
@@ -88,31 +99,6 @@ class InfiniteListRequest extends FormRequest
|
||||
return 0 === $page || $page > 65536 ? 1 : $page;
|
||||
}
|
||||
|
||||
public function getSortInstructions(): array
|
||||
{
|
||||
$allowed = config('firefly.sorting.allowed.transactions');
|
||||
$set = $this->get('sorting', []);
|
||||
$result = [];
|
||||
if (0 === count($set)) {
|
||||
return [];
|
||||
}
|
||||
foreach ($set as $info) {
|
||||
$column = $info['column'] ?? 'NOPE';
|
||||
$direction = $info['direction'] ?? 'NOPE';
|
||||
if ('asc' !== $direction && 'desc' !== $direction) {
|
||||
// skip invalid direction
|
||||
continue;
|
||||
}
|
||||
if (false === in_array($column, $allowed, true)) {
|
||||
// skip invalid column
|
||||
continue;
|
||||
}
|
||||
$result[$column] = $direction;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getTransactionTypes(): array
|
||||
{
|
||||
$type = (string)$this->get('type', 'default');
|
||||
|
||||
@@ -110,7 +110,7 @@ class AppendBudgetLimitPeriods extends Command
|
||||
return 'daily';
|
||||
}
|
||||
// is weekly
|
||||
if ('1' === $limit->start_date->format('N') && '7' === $limit->end_date->format('N') && 6 === $limit->end_date->diffInDays($limit->start_date)) {
|
||||
if ('1' === $limit->start_date->format('N') && '7' === $limit->end_date->format('N') && 6 === (int)$limit->end_date->diffInDays($limit->start_date, true)) {
|
||||
return 'weekly';
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ class AppendBudgetLimitPeriods extends Command
|
||||
if (
|
||||
in_array($limit->start_date->format('j-n'), $start, true) // start of quarter
|
||||
&& in_array($limit->end_date->format('j-n'), $end, true) // end of quarter
|
||||
&& 2 === $limit->start_date->diffInMonths($limit->end_date)
|
||||
&& 2 === (int)$limit->start_date->diffInMonths($limit->end_date, true)
|
||||
) {
|
||||
return 'quarterly';
|
||||
}
|
||||
@@ -139,7 +139,7 @@ class AppendBudgetLimitPeriods extends Command
|
||||
if (
|
||||
in_array($limit->start_date->format('j-n'), $start, true) // start of quarter
|
||||
&& in_array($limit->end_date->format('j-n'), $end, true) // end of quarter
|
||||
&& 5 === $limit->start_date->diffInMonths($limit->end_date)
|
||||
&& 5 === (int)$limit->start_date->diffInMonths($limit->end_date, true)
|
||||
) {
|
||||
return 'half_year';
|
||||
}
|
||||
|
||||
182
app/Console/Commands/Upgrade/MigrateRuleActions.php
Normal file
182
app/Console/Commands/Upgrade/MigrateRuleActions.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* MigrateRuleActions.php
|
||||
* Copyright (c) 2024 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\Console\Commands\Upgrade;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class MigrateRuleActions extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
|
||||
public const string CONFIG_NAME = '610_migrate_rule_actions';
|
||||
|
||||
protected $description = 'Migrate rule actions away from expression engine';
|
||||
|
||||
protected $signature = 'firefly-iii:migrate-rule-actions {--F|force : Force the execution of this command.}';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
if ($this->isExecuted() && true !== $this->option('force')) {
|
||||
$this->friendlyInfo('This command has already been executed.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (false === config('firefly.feature_flags.expression_engine')) {
|
||||
$this->friendlyInfo('Expression engine is not enabled. Nothing to do.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
$this->replaceEqualSign();
|
||||
$this->replaceObsoleteActions();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
if (null !== $configVar) {
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function replaceEqualSign(): void
|
||||
{
|
||||
$count = 0;
|
||||
$actions = RuleAction::get();
|
||||
|
||||
/** @var RuleAction $action */
|
||||
foreach ($actions as $action) {
|
||||
if (str_starts_with($action->action_value, '=')) {
|
||||
$action->action_value = sprintf('%s%s', '\=', substr($action->action_value, 1));
|
||||
$action->save();
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
if ($count > 0) {
|
||||
$this->friendlyInfo(sprintf('Upgrading %d rule action(s) for the new expression engine.', $count));
|
||||
}
|
||||
if (0 === $count) {
|
||||
$this->friendlyInfo('All rule actions are up to date.');
|
||||
}
|
||||
}
|
||||
|
||||
private function replaceObsoleteActions(): void
|
||||
{
|
||||
$obsolete = [
|
||||
'append_description',
|
||||
'prepend_description',
|
||||
'append_notes',
|
||||
'prepend_notes',
|
||||
'append_descr_to_notes',
|
||||
'append_notes_to_descr',
|
||||
'move_descr_to_notes',
|
||||
'move_notes_to_descr',
|
||||
];
|
||||
$actions = RuleAction::whereIn('action_type', $obsolete)->get();
|
||||
|
||||
/** @var RuleAction $action */
|
||||
foreach ($actions as $action) {
|
||||
$oldType = $action->action_type;
|
||||
|
||||
switch ($action->action_type) {
|
||||
default:
|
||||
$this->friendlyError(sprintf('Cannot deal with action type "%s", skip it.', $action->action_type));
|
||||
|
||||
break;
|
||||
|
||||
case 'append_description':
|
||||
$action->action_type = 'set_description';
|
||||
$action->action_value = sprintf('=description~"%s"', str_replace('"', '\"', $action->action_value));
|
||||
$action->save();
|
||||
$this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type));
|
||||
|
||||
break;
|
||||
|
||||
case 'prepend_description':
|
||||
$action->action_type = 'set_description';
|
||||
$action->action_value = sprintf('="%s"~description', str_replace('"', '\"', $action->action_value));
|
||||
$action->save();
|
||||
$this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type));
|
||||
|
||||
break;
|
||||
|
||||
case 'append_notes':
|
||||
$action->action_type = 'set_notes';
|
||||
$action->action_value = sprintf('=notes~"%s"', str_replace('"', '\"', $action->action_value));
|
||||
$action->save();
|
||||
$this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type));
|
||||
|
||||
break;
|
||||
|
||||
case 'prepend_notes':
|
||||
$action->action_type = 'set_notes';
|
||||
$action->action_value = sprintf('="%s"~notes', str_replace('"', '\"', $action->action_value));
|
||||
$action->save();
|
||||
$this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type));
|
||||
|
||||
break;
|
||||
|
||||
case 'append_descr_to_notes':
|
||||
$action->action_type = 'set_notes';
|
||||
$action->action_value = '=notes~" "~description';
|
||||
$action->save();
|
||||
$this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type));
|
||||
|
||||
break;
|
||||
|
||||
case 'append_notes_to_descr':
|
||||
$action->action_type = 'set_description';
|
||||
$action->action_value = '=description~" "~notes';
|
||||
$action->save();
|
||||
$this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type));
|
||||
|
||||
break;
|
||||
|
||||
case 'move_descr_to_notes':
|
||||
$action->action_type = 'set_notes';
|
||||
$action->action_value = '=description';
|
||||
$action->save();
|
||||
$this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type));
|
||||
|
||||
break;
|
||||
|
||||
case 'move_notes_to_descr':
|
||||
$action->action_type = 'set_description';
|
||||
$action->action_value = '=notes';
|
||||
$action->save();
|
||||
$this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@ class UpgradeDatabase extends Command
|
||||
'firefly-iii:upgrade-liabilities',
|
||||
'firefly-iii:liabilities-600',
|
||||
'firefly-iii:budget-limit-periods',
|
||||
'firefly-iii:migrate-rule-actions',
|
||||
'firefly-iii:restore-oauth-keys',
|
||||
// also just in case, some integrity commands:
|
||||
'firefly-iii:create-group-memberships',
|
||||
|
||||
@@ -26,11 +26,11 @@ class UpgradeSkeleton extends Command
|
||||
{
|
||||
$start = microtime(true);
|
||||
if ($this->isExecuted() && true !== $this->option('force')) {
|
||||
$this->info('FRIENDLY This command has already been executed.');
|
||||
$this->friendlyInfo('This command has already been executed.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
$this->warn('Congrats, you found the skeleton command. Boo!');
|
||||
$this->friendlyWarning('Congrats, you found the skeleton command. Boo!');
|
||||
|
||||
//$this->markAsExecuted();
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
return redirect(route('tags.index'));
|
||||
|
||||
case 'categories.show':
|
||||
case 'categories.edit':
|
||||
case 'categories.show.all':
|
||||
$request->session()->reflash();
|
||||
|
||||
|
||||
@@ -40,12 +40,12 @@ class ReportGeneratorFactory
|
||||
{
|
||||
$period = 'Month';
|
||||
// more than two months date difference means year report.
|
||||
if ($start->diffInMonths($end) > 1) {
|
||||
if ($start->diffInMonths($end, true) > 1) {
|
||||
$period = 'Year';
|
||||
}
|
||||
|
||||
// more than one year date difference means multi year report.
|
||||
if ($start->diffInMonths($end) > 12) {
|
||||
// more than one year date difference means multi-year report.
|
||||
if ($start->diffInMonths($end, true) > 12) {
|
||||
$period = 'MultiYear';
|
||||
}
|
||||
|
||||
|
||||
@@ -72,31 +72,6 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$this->run();
|
||||
}
|
||||
|
||||
public function getVersion(): int
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function setObjects(Collection $objects): void
|
||||
{
|
||||
$this->objects = $objects;
|
||||
}
|
||||
|
||||
public function setTrigger(int $trigger): void
|
||||
{
|
||||
$this->trigger = $trigger;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function setWebhooks(Collection $webhooks): void
|
||||
{
|
||||
$this->webhooks = $webhooks;
|
||||
}
|
||||
|
||||
private function getWebhooks(): Collection
|
||||
{
|
||||
return $this->user->webhooks()->where('active', true)->where('trigger', $this->trigger)->get(['webhooks.*']);
|
||||
@@ -206,6 +181,11 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$this->storeMessage($webhook, $basicMessage);
|
||||
}
|
||||
|
||||
public function getVersion(): int
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
private function collectAccounts(TransactionGroup $transactionGroup): Collection
|
||||
{
|
||||
$accounts = new Collection();
|
||||
@@ -232,4 +212,24 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$webhookMessage->save();
|
||||
app('log')->debug(sprintf('Stored new webhook message #%d', $webhookMessage->id));
|
||||
}
|
||||
|
||||
public function setObjects(Collection $objects): void
|
||||
{
|
||||
$this->objects = $objects;
|
||||
}
|
||||
|
||||
public function setTrigger(int $trigger): void
|
||||
{
|
||||
$this->trigger = $trigger;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function setWebhooks(Collection $webhooks): void
|
||||
{
|
||||
$this->webhooks = $webhooks;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ class UserEventHandler
|
||||
}
|
||||
// clean up old entries (6 months)
|
||||
$carbon = Carbon::createFromFormat('Y-m-d H:i:s', $preference[$index]['time']);
|
||||
if (false !== $carbon && $carbon->diffInMonths(today()) > 6) {
|
||||
if (false !== $carbon && $carbon->diffInMonths(today(), true) > 6) {
|
||||
app('log')->debug(sprintf('Entry for %s is very old, remove it.', $row['ip']));
|
||||
unset($preference[$index]);
|
||||
}
|
||||
|
||||
@@ -236,7 +236,9 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
$fileObject->rewind();
|
||||
|
||||
if (0 === $file->getSize()) {
|
||||
throw new FireflyException('Cannot upload empty or non-existent file.');
|
||||
$this->errors->add('attachments', trans('validation.file_zero_length'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = (string)$fileObject->fread($file->getSize());
|
||||
|
||||
@@ -33,9 +33,10 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
*/
|
||||
trait CollectorProperties
|
||||
{
|
||||
public const string TEST = 'Test';
|
||||
|
||||
/** @var array<int, string> */
|
||||
public array $sorting;
|
||||
public const string TEST = 'Test';
|
||||
private ?int $endRow;
|
||||
private bool $expandGroupSearch;
|
||||
private array $fields;
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace FireflyIII\Helpers\Collector;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\Extensions\AccountCollection;
|
||||
use FireflyIII\Helpers\Collector\Extensions\AmountCollection;
|
||||
@@ -782,6 +783,35 @@ class GroupCollector implements GroupCollectorInterface
|
||||
return $currentCollection;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function sortCollection(Collection $collection): Collection
|
||||
{
|
||||
/**
|
||||
* @var string $field
|
||||
* @var string $direction
|
||||
*/
|
||||
foreach ($this->sorting as $field => $direction) {
|
||||
$func = 'ASC' === $direction ? 'sortBy' : 'sortByDesc';
|
||||
$collection = $collection->{$func}(function (array $product, int $key) use ($field) { // @phpstan-ignore-line
|
||||
// depends on $field:
|
||||
if ('description' === $field) {
|
||||
if (1 === count($product['transactions'])) {
|
||||
return array_values($product['transactions'])[0][$field];
|
||||
}
|
||||
if (count($product['transactions']) > 1) {
|
||||
return $product['title'];
|
||||
}
|
||||
|
||||
return 'zzz';
|
||||
}
|
||||
|
||||
exit('here we are');
|
||||
});
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as getGroups but everything is in a paginator.
|
||||
*/
|
||||
@@ -792,6 +822,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
$this->setLimit(50);
|
||||
}
|
||||
if (null !== $this->startRow && null !== $this->endRow) {
|
||||
/** @var int $total */
|
||||
$total = $this->endRow - $this->startRow;
|
||||
|
||||
return new LengthAwarePaginator($set, $this->total, $total, 1);
|
||||
@@ -931,6 +962,14 @@ class GroupCollector implements GroupCollectorInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function setSorting(array $instructions): GroupCollectorInterface
|
||||
{
|
||||
$this->sorting = $instructions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStartRow(int $startRow): self
|
||||
{
|
||||
$this->startRow = $startRow;
|
||||
@@ -1091,41 +1130,4 @@ class GroupCollector implements GroupCollectorInterface
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function sortCollection(Collection $collection): Collection
|
||||
{
|
||||
/**
|
||||
* @var string $field
|
||||
* @var string $direction
|
||||
*/
|
||||
foreach ($this->sorting as $field => $direction) {
|
||||
$func = 'ASC' === $direction ? 'sortBy' : 'sortByDesc';
|
||||
$collection = $collection->{$func}(function (array $product, int $key) use ($field) { // @phpstan-ignore-line
|
||||
// depends on $field:
|
||||
if ('description' === $field) {
|
||||
if (1 === count($product['transactions'])) {
|
||||
return array_values($product['transactions'])[0][$field];
|
||||
}
|
||||
if (count($product['transactions']) > 1) {
|
||||
return $product['title'];
|
||||
}
|
||||
|
||||
return 'zzz';
|
||||
}
|
||||
|
||||
exit('here we are');
|
||||
});
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function setSorting(array $instructions): GroupCollectorInterface
|
||||
{
|
||||
$this->sorting = $instructions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,13 +285,6 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function getPaginatedGroups(): LengthAwarePaginator;
|
||||
|
||||
public function setSorting(array $instructions): self;
|
||||
|
||||
/**
|
||||
* Sort the collection on a column.
|
||||
*/
|
||||
public function sortCollection(Collection $collection): Collection;
|
||||
|
||||
public function hasAnyTag(): self;
|
||||
|
||||
/**
|
||||
@@ -560,6 +553,8 @@ interface GroupCollectorInterface
|
||||
|
||||
public function setSepaCT(string $sepaCT): self;
|
||||
|
||||
public function setSorting(array $instructions): self;
|
||||
|
||||
/**
|
||||
* Set source accounts.
|
||||
*/
|
||||
@@ -620,6 +615,11 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function setXorAccounts(Collection $accounts): self;
|
||||
|
||||
/**
|
||||
* Sort the collection on a column.
|
||||
*/
|
||||
public function sortCollection(Collection $collection): Collection;
|
||||
|
||||
/**
|
||||
* Automatically include all stuff required to make API calls work.
|
||||
*/
|
||||
|
||||
@@ -75,7 +75,7 @@ class ReconcileController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
* */
|
||||
public function reconcile(Account $account, Carbon $start = null, Carbon $end = null)
|
||||
public function reconcile(Account $account, ?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
if (!$this->isEditableAccount($account)) {
|
||||
return $this->redirectAccountToAccount($account);
|
||||
|
||||
@@ -75,7 +75,7 @@ class ShowController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
* */
|
||||
public function show(Request $request, Account $account, Carbon $start = null, Carbon $end = null)
|
||||
public function show(Request $request, Account $account, ?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
$objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type));
|
||||
|
||||
|
||||
@@ -80,7 +80,18 @@ class LoginController extends Controller
|
||||
Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $request->get($this->username())));
|
||||
app('log')->debug('User is trying to login.');
|
||||
|
||||
try {
|
||||
$this->validateLogin($request);
|
||||
} catch (ValidationException $e) {
|
||||
return redirect(route('login'))
|
||||
->withErrors(
|
||||
[
|
||||
$this->username => trans('auth.failed'),
|
||||
]
|
||||
)
|
||||
->onlyInput($this->username)
|
||||
;
|
||||
}
|
||||
app('log')->debug('Login data is present.');
|
||||
|
||||
// Copied directly from AuthenticatesUsers, but with logging added:
|
||||
@@ -91,7 +102,6 @@ class LoginController extends Controller
|
||||
Log::channel('audit')->warning(sprintf('Login for user "%s" was locked out.', $request->get($this->username())));
|
||||
app('log')->error(sprintf('Login for user "%s" was locked out.', $request->get($this->username())));
|
||||
$this->fireLockoutEvent($request);
|
||||
|
||||
$this->sendLockoutResponse($request);
|
||||
}
|
||||
// Copied directly from AuthenticatesUsers, but with logging added:
|
||||
|
||||
@@ -85,7 +85,7 @@ class IndexController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
* */
|
||||
public function index(Carbon $start = null, Carbon $end = null)
|
||||
public function index(?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
$this->abRepository->cleanup();
|
||||
app('log')->debug(sprintf('Start of IndexController::index("%s", "%s")', $start?->format('Y-m-d'), $end?->format('Y-m-d')));
|
||||
|
||||
@@ -75,7 +75,7 @@ class ShowController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function noBudget(Request $request, Carbon $start = null, Carbon $end = null)
|
||||
public function noBudget(Request $request, ?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
// @var Carbon $start
|
||||
$start ??= session('start');
|
||||
|
||||
@@ -70,7 +70,7 @@ class NoCategoryController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function show(Request $request, Carbon $start = null, Carbon $end = null)
|
||||
public function show(Request $request, ?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
app('log')->debug('Start of noCategory()');
|
||||
// @var Carbon $start
|
||||
|
||||
@@ -71,7 +71,7 @@ class ShowController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function show(Request $request, Category $category, Carbon $start = null, Carbon $end = null)
|
||||
public function show(Request $request, Category $category, ?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
// @var Carbon $start
|
||||
$start ??= session('start', today(config('app.timezone'))->startOfMonth());
|
||||
|
||||
@@ -143,7 +143,7 @@ class ReportController extends Controller
|
||||
$cache->addProperty($accounts);
|
||||
$cache->addProperty($end);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
// return response()->json($cache->get());
|
||||
}
|
||||
|
||||
app('log')->debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
|
||||
@@ -222,8 +222,11 @@ class ReportController extends Controller
|
||||
while ($currentStart <= $currentEnd) {
|
||||
$key = $currentStart->format($format);
|
||||
$title = $currentStart->isoFormat($titleFormat);
|
||||
// #8663 make sure the period exists in the data previously collected.
|
||||
if (array_key_exists($key, $currency)) {
|
||||
$income['entries'][$title] = app('steam')->bcround($currency[$key]['earned'] ?? '0', $currency['currency_decimal_places']);
|
||||
$expense['entries'][$title] = app('steam')->bcround($currency[$key]['spent'] ?? '0', $currency['currency_decimal_places']);
|
||||
}
|
||||
$currentStart = app('navigation')->addPeriod($currentStart, $preferredRange, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,11 @@ abstract class Controller extends BaseController
|
||||
$authGuard = config('firefly.authentication_guard');
|
||||
$logoutUrl = config('firefly.custom_logout_url');
|
||||
|
||||
// overrule v2 layout back to v1.
|
||||
if ('true' === request()->get('force_default_layout') && 'v2' === config('firefly.layout')) {
|
||||
app('view')->getFinder()->setPaths([realpath(base_path('resources/views'))]); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
app('view')->share('authGuard', $authGuard);
|
||||
app('view')->share('logoutUrl', $logoutUrl);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Middleware\IsDemoUser;
|
||||
use FireflyIII\Models\AccountType;
|
||||
|
||||
@@ -97,10 +97,10 @@ class HomeController extends Controller
|
||||
app('log')->debug('Range is now marked as "custom".');
|
||||
}
|
||||
|
||||
$diff = $start->diffInDays($end) + 1;
|
||||
$diff = $start->diffInDays($end, true) + 1;
|
||||
|
||||
if ($diff > 50) {
|
||||
$request->session()->flash('warning', (string)trans('firefly.warning_much_data', ['days' => $diff]));
|
||||
$request->session()->flash('warning', (string)trans('firefly.warning_much_data', ['days' => (int)$diff]));
|
||||
}
|
||||
|
||||
$request->session()->put('is_custom_range', $isCustomRange);
|
||||
|
||||
@@ -113,7 +113,7 @@ class BoxController extends Controller
|
||||
$spentAmount = $spent[$currency->id]['sum'] ?? '0';
|
||||
app('log')->debug(sprintf('Spent for default currency for all budgets in this period: %s', $spentAmount));
|
||||
|
||||
$days = $today->between($start, $end) ? $today->diffInDays($start) + 1 : $end->diffInDays($start) + 1;
|
||||
$days = (int)($today->between($start, $end) ? $today->diffInDays($start, true) + 1 : $end->diffInDays($start, true) + 1);
|
||||
app('log')->debug(sprintf('Number of days left: %d', $days));
|
||||
$spentPerDay = bcdiv($spentAmount, (string)$days);
|
||||
app('log')->debug(sprintf('Available to spend per day: %s', $spentPerDay));
|
||||
|
||||
@@ -38,7 +38,7 @@ class IntroController extends Controller
|
||||
/**
|
||||
* Returns the introduction wizard for a page.
|
||||
*/
|
||||
public function getIntroSteps(string $route, string $specificPage = null): JsonResponse
|
||||
public function getIntroSteps(string $route, ?string $specificPage = null): JsonResponse
|
||||
{
|
||||
app('log')->debug(sprintf('getIntroSteps for route "%s" and page "%s"', $route, $specificPage));
|
||||
$specificPage ??= '';
|
||||
@@ -91,7 +91,7 @@ class IntroController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function postEnable(string $route, string $specialPage = null): JsonResponse
|
||||
public function postEnable(string $route, ?string $specialPage = null): JsonResponse
|
||||
{
|
||||
$specialPage ??= '';
|
||||
$route = str_replace('.', '_', $route);
|
||||
@@ -111,7 +111,7 @@ class IntroController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function postFinished(string $route, string $specialPage = null): JsonResponse
|
||||
public function postFinished(string $route, ?string $specialPage = null): JsonResponse
|
||||
{
|
||||
$specialPage ??= '';
|
||||
$key = 'shown_demo_'.$route;
|
||||
|
||||
@@ -34,6 +34,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class ReconcileController
|
||||
@@ -66,14 +67,13 @@ class ReconcileController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function overview(Request $request, Account $account = null, Carbon $start = null, Carbon $end = null): JsonResponse
|
||||
public function overview(Request $request, ?Account $account = null, ?Carbon $start = null, ?Carbon $end = null): JsonResponse
|
||||
{
|
||||
$startBalance = $request->get('startBalance');
|
||||
$endBalance = $request->get('endBalance');
|
||||
$accountCurrency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
|
||||
$amount = '0';
|
||||
$clearedAmount = '0';
|
||||
$route = '';
|
||||
|
||||
if (null === $start && null === $end) {
|
||||
throw new FireflyException('Invalid dates submitted.');
|
||||
@@ -103,14 +103,11 @@ class ReconcileController extends Controller
|
||||
$clearedJournals = $collector->getExtractedJournals();
|
||||
}
|
||||
|
||||
app('log')->debug('Start transaction loop');
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$amount = $this->processJournal($account, $accountCurrency, $journal, $amount);
|
||||
}
|
||||
app('log')->debug(sprintf('Final amount is %s', $amount));
|
||||
app('log')->debug('End transaction loop');
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($clearedJournals as $journal) {
|
||||
@@ -118,31 +115,17 @@ class ReconcileController extends Controller
|
||||
$clearedAmount = $this->processJournal($account, $accountCurrency, $journal, $clearedAmount);
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Start balance: "%s"', $startBalance));
|
||||
Log::debug(sprintf('End balance: "%s"', $endBalance));
|
||||
Log::debug(sprintf('Cleared amount: "%s"', $clearedAmount));
|
||||
Log::debug(sprintf('Amount: "%s"', $amount));
|
||||
$difference = bcadd(bcadd(bcsub($startBalance, $endBalance), $clearedAmount), $amount);
|
||||
$diffCompare = bccomp($difference, '0');
|
||||
$countCleared = count($clearedJournals);
|
||||
|
||||
$reconSum = bcadd(bcadd($startBalance, $amount), $clearedAmount);
|
||||
|
||||
try {
|
||||
$view = view(
|
||||
'accounts.reconcile.overview',
|
||||
compact(
|
||||
'account',
|
||||
'start',
|
||||
'diffCompare',
|
||||
'difference',
|
||||
'end',
|
||||
'clearedAmount',
|
||||
'startBalance',
|
||||
'endBalance',
|
||||
'amount',
|
||||
'route',
|
||||
'countCleared',
|
||||
'reconSum',
|
||||
'selectedIds'
|
||||
)
|
||||
)->render();
|
||||
$view = view('accounts.reconcile.overview', compact('account', 'start', 'diffCompare', 'difference', 'end', 'clearedAmount', 'startBalance', 'endBalance', 'amount', 'route', 'countCleared', 'reconSum', 'selectedIds'))->render();
|
||||
} catch (\Throwable $e) {
|
||||
app('log')->debug(sprintf('View error: %s', $e->getMessage()));
|
||||
app('log')->error($e->getTraceAsString());
|
||||
@@ -151,14 +134,42 @@ class ReconcileController extends Controller
|
||||
throw new FireflyException($view, 0, $e);
|
||||
}
|
||||
|
||||
$return = [
|
||||
'post_url' => $route,
|
||||
'html' => $view,
|
||||
];
|
||||
$return = ['post_url' => $route, 'html' => $view];
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
private function processJournal(Account $account, TransactionCurrency $currency, array $journal, string $amount): string
|
||||
{
|
||||
$toAdd = '0';
|
||||
app('log')->debug(sprintf('User submitted %s #%d: "%s"', $journal['transaction_type_type'], $journal['transaction_journal_id'], $journal['description']));
|
||||
|
||||
// not much magic below we need to cover using tests.
|
||||
|
||||
if ($account->id === $journal['source_account_id']) {
|
||||
if ($currency->id === $journal['currency_id']) {
|
||||
$toAdd = $journal['amount'];
|
||||
}
|
||||
if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) {
|
||||
$toAdd = $journal['foreign_amount'];
|
||||
}
|
||||
}
|
||||
if ($account->id === $journal['destination_account_id']) {
|
||||
if ($currency->id === $journal['currency_id']) {
|
||||
$toAdd = bcmul($journal['amount'], '-1');
|
||||
}
|
||||
if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) {
|
||||
$toAdd = bcmul($journal['foreign_amount'], '-1');
|
||||
}
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('Going to add %s to %s', $toAdd, $amount));
|
||||
$amount = bcadd($amount, $toAdd);
|
||||
app('log')->debug(sprintf('Result is %s', $amount));
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of transactions in a modal.
|
||||
*
|
||||
@@ -166,7 +177,7 @@ class ReconcileController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function transactions(Account $account, Carbon $start = null, Carbon $end = null)
|
||||
public function transactions(Account $account, ?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
if (null === $start || null === $end) {
|
||||
throw new FireflyException('Invalid dates submitted.');
|
||||
@@ -176,6 +187,7 @@ class ReconcileController extends Controller
|
||||
}
|
||||
$startDate = clone $start;
|
||||
$startDate->subDay();
|
||||
$end->endOfDay();
|
||||
|
||||
$currency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
|
||||
$startBalance = app('steam')->bcround(app('steam')->balance($account, $startDate), $currency->decimal_places);
|
||||
@@ -214,37 +226,6 @@ class ReconcileController extends Controller
|
||||
return response()->json(['html' => $html, 'startBalance' => $startBalance, 'endBalance' => $endBalance]);
|
||||
}
|
||||
|
||||
private function processJournal(Account $account, TransactionCurrency $currency, array $journal, string $amount): string
|
||||
{
|
||||
$toAdd = '0';
|
||||
app('log')->debug(sprintf('User submitted %s #%d: "%s"', $journal['transaction_type_type'], $journal['transaction_journal_id'], $journal['description']));
|
||||
|
||||
// not much magic below we need to cover using tests.
|
||||
|
||||
if ($account->id === $journal['source_account_id']) {
|
||||
if ($currency->id === $journal['currency_id']) {
|
||||
$toAdd = $journal['amount'];
|
||||
}
|
||||
if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) {
|
||||
$toAdd = $journal['foreign_amount'];
|
||||
}
|
||||
}
|
||||
if ($account->id === $journal['destination_account_id']) {
|
||||
if ($currency->id === $journal['currency_id']) {
|
||||
$toAdd = bcmul($journal['amount'], '-1');
|
||||
}
|
||||
if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) {
|
||||
$toAdd = bcmul($journal['foreign_amount'], '-1');
|
||||
}
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('Going to add %s to %s', $toAdd, $amount));
|
||||
$amount = bcadd($amount, $toAdd);
|
||||
app('log')->debug(sprintf('Result is %s', $amount));
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* "fix" amounts to make it easier on the reconciliation overview:
|
||||
*/
|
||||
|
||||
@@ -76,7 +76,7 @@ class CreateController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function create(Request $request, RuleGroup $ruleGroup = null)
|
||||
public function create(Request $request, ?RuleGroup $ruleGroup = null)
|
||||
{
|
||||
$this->createDefaultRuleGroup();
|
||||
$preFilled = [
|
||||
|
||||
@@ -215,7 +215,7 @@ class TagController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function show(Request $request, Tag $tag, Carbon $start = null, Carbon $end = null)
|
||||
public function show(Request $request, Tag $tag, ?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
// default values:
|
||||
$subTitleIcon = 'fa-tag';
|
||||
@@ -312,6 +312,9 @@ class TagController extends Controller
|
||||
if (count($this->attachmentsHelper->getMessages()->get('attachments')) > 0) {
|
||||
$request->session()->flash('info', $this->attachmentsHelper->getMessages()->get('attachments'));
|
||||
}
|
||||
if (count($this->attachmentsHelper->getErrors()->get('attachments')) > 0) {
|
||||
$request->session()->flash('error', $this->attachmentsHelper->getErrors()->get('attachments'));
|
||||
}
|
||||
$redirect = redirect($this->getPreviousUrl('tags.create.url'));
|
||||
if (1 === (int)$request->get('create_another')) {
|
||||
session()->put('tags.create.fromStore', true);
|
||||
@@ -347,6 +350,9 @@ class TagController extends Controller
|
||||
if (count($this->attachmentsHelper->getMessages()->get('attachments')) > 0) {
|
||||
$request->session()->flash('info', $this->attachmentsHelper->getMessages()->get('attachments'));
|
||||
}
|
||||
if (count($this->attachmentsHelper->getErrors()->get('attachments')) > 0) {
|
||||
$request->session()->flash('error', $this->attachmentsHelper->getErrors()->get('attachments'));
|
||||
}
|
||||
$redirect = redirect($this->getPreviousUrl('tags.edit.url'));
|
||||
if (1 === (int)$request->get('return_to_edit')) {
|
||||
session()->put('tags.edit.fromUpdate', true);
|
||||
|
||||
@@ -69,23 +69,17 @@ class IndexController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function index(Request $request, string $objectType, Carbon $start = null, Carbon $end = null)
|
||||
public function index(Request $request, string $objectType, ?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
if ('transfers' === $objectType) {
|
||||
$objectType = 'transfer';
|
||||
}
|
||||
|
||||
// add a split for the (future) v2 release.
|
||||
$periods = [];
|
||||
$groups = [];
|
||||
$subTitle = 'TODO page subtitle in v2';
|
||||
|
||||
$subTitleIcon = config('firefly.transactionIconsByType.'.$objectType);
|
||||
$types = config('firefly.transactionTypesByType.'.$objectType);
|
||||
$page = (int)$request->get('page');
|
||||
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
|
||||
|
||||
if ('v2' !== (string)config('firefly.layout')) {
|
||||
if (null === $start) {
|
||||
$start = session('start');
|
||||
$end = session('end');
|
||||
@@ -120,7 +114,6 @@ class IndexController extends Controller
|
||||
;
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups->setPath($path);
|
||||
}
|
||||
|
||||
return view('transactions.index', compact('subTitle', 'objectType', 'subTitleIcon', 'groups', 'periods', 'start', 'end'));
|
||||
}
|
||||
|
||||
@@ -50,12 +50,12 @@ class SecureHeaders
|
||||
$csp = [
|
||||
"default-src 'none'",
|
||||
"object-src 'none'",
|
||||
sprintf("script-src 'unsafe-eval' 'strict-dynamic' 'self' 'unsafe-inline' 'nonce-%1s' %2s", $nonce, $trackingScriptSrc),
|
||||
sprintf("script-src 'unsafe-eval' 'strict-dynamic' 'nonce-%1s'", $nonce),
|
||||
"style-src 'unsafe-inline' 'self'",
|
||||
"base-uri 'self'",
|
||||
"font-src 'self' data:",
|
||||
sprintf("connect-src 'self' %s", $trackingScriptSrc),
|
||||
sprintf("img-src data: 'strict-dynamic' 'self' *.tile.openstreetmap.org %s", $trackingScriptSrc),
|
||||
sprintf("img-src 'self' 'nonce-%1s'", $nonce),
|
||||
"manifest-src 'self'",
|
||||
];
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Rules\IsValidActionExpression;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetRuleConfiguration;
|
||||
@@ -147,7 +148,7 @@ class RuleFormRequest extends FormRequest
|
||||
'triggers.*.type' => 'required|in:'.implode(',', $validTriggers),
|
||||
'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:1024|ruleActionValue', $contextActions),
|
||||
'actions.*.value' => [sprintf('required_if:actions.*.type,%s|min:0|max:1024', $contextActions), new IsValidActionExpression(), 'ruleActionValue'],
|
||||
'strict' => 'in:0,1',
|
||||
];
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ class WarnAboutBills implements ShouldQueue
|
||||
$today = clone $this->date;
|
||||
$carbon = clone $bill->{$field};
|
||||
|
||||
return $today->diffInDays($carbon, false);
|
||||
return (int) $today->diffInDays($carbon);
|
||||
}
|
||||
|
||||
private function sendWarning(Bill $bill, string $field): void
|
||||
|
||||
@@ -274,6 +274,13 @@ class Account extends Model
|
||||
);
|
||||
}
|
||||
|
||||
protected function iban(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => null === $value ? null : trim(str_replace(' ', '', (string)$value)),
|
||||
);
|
||||
}
|
||||
|
||||
protected function order(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@@ -26,10 +26,13 @@ namespace FireflyIII\Models;
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\TransactionRules\Expressions\ActionExpression;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\ExpressionLanguage\SyntaxError;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\RuleAction
|
||||
@@ -75,6 +78,30 @@ class RuleAction extends Model
|
||||
|
||||
protected $fillable = ['rule_id', 'action_type', 'action_value', 'order', 'active', 'stop_processing'];
|
||||
|
||||
public function getValue(array $journal): string
|
||||
{
|
||||
if (false === config('firefly.feature_flags.expression_engine')) {
|
||||
Log::debug('Expression engine is disabled, returning action value as string.');
|
||||
|
||||
return (string)$this->action_value;
|
||||
}
|
||||
if (true === config('firefly.feature_flags.expression_engine') && str_starts_with($this->action_value, '\=')) {
|
||||
// return literal string.
|
||||
return substr($this->action_value, 1);
|
||||
}
|
||||
$expr = new ActionExpression($this->action_value);
|
||||
|
||||
try {
|
||||
$result = $expr->evaluate($journal);
|
||||
} catch (SyntaxError $e) {
|
||||
Log::error(sprintf('Expression engine failed to evaluate expression "%s" with error "%s".', $this->action_value, $e->getMessage()));
|
||||
$result = (string)$this->action_value;
|
||||
}
|
||||
Log::debug(sprintf('Expression engine is enabled, result of expression "%s" is "%s".', $this->action_value, $result));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function rule(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Rule::class);
|
||||
|
||||
@@ -80,7 +80,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
Passport::ignoreMigrations();
|
||||
Sanctum::ignoreMigrations();
|
||||
// Passport::ignoreMigrations();
|
||||
// Sanctum::ignoreMigrations();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,9 +69,11 @@ use FireflyIII\Support\Preferences;
|
||||
use FireflyIII\Support\Steam;
|
||||
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
|
||||
use FireflyIII\TransactionRules\Engine\SearchRuleEngine;
|
||||
use FireflyIII\TransactionRules\Expressions\ActionExpressionLanguageProvider;
|
||||
use FireflyIII\Validation\FireflyValidator;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
|
||||
/**
|
||||
* Class FireflyServiceProvider.
|
||||
@@ -200,6 +202,17 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
}
|
||||
);
|
||||
|
||||
// rule expression language
|
||||
$this->app->singleton(
|
||||
ExpressionLanguage::class,
|
||||
static function () {
|
||||
$expressionLanguage = new ExpressionLanguage();
|
||||
$expressionLanguage->registerProvider(new ActionExpressionLanguageProvider());
|
||||
|
||||
return $expressionLanguage;
|
||||
}
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
RuleEngineInterface::class,
|
||||
static function (Application $app) {
|
||||
|
||||
@@ -122,7 +122,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
|
||||
$budgetLimit->delete();
|
||||
}
|
||||
|
||||
public function getAllBudgetLimitsByCurrency(TransactionCurrency $currency, Carbon $start = null, Carbon $end = null): Collection
|
||||
public function getAllBudgetLimitsByCurrency(TransactionCurrency $currency, ?Carbon $start = null, ?Carbon $end = null): Collection
|
||||
{
|
||||
return $this->getAllBudgetLimits($start, $end)->filter(
|
||||
static function (BudgetLimit $budgetLimit) use ($currency) {
|
||||
@@ -131,7 +131,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
|
||||
);
|
||||
}
|
||||
|
||||
public function getAllBudgetLimits(Carbon $start = null, Carbon $end = null): Collection
|
||||
public function getAllBudgetLimits(?Carbon $start = null, ?Carbon $end = null): Collection
|
||||
{
|
||||
// both are NULL:
|
||||
if (null === $start && null === $end) {
|
||||
@@ -198,7 +198,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
|
||||
;
|
||||
}
|
||||
|
||||
public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection
|
||||
public function getBudgetLimits(Budget $budget, ?Carbon $start = null, ?Carbon $end = null): Collection
|
||||
{
|
||||
if (null === $end && null === $start) {
|
||||
return $budget->budgetlimits()->with(['transactionCurrency'])->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']);
|
||||
|
||||
@@ -57,11 +57,11 @@ interface BudgetLimitRepositoryInterface
|
||||
/**
|
||||
* TODO this method is not multi currency aware.
|
||||
*/
|
||||
public function getAllBudgetLimits(Carbon $start = null, Carbon $end = null): Collection;
|
||||
public function getAllBudgetLimits(?Carbon $start = null, ?Carbon $end = null): Collection;
|
||||
|
||||
public function getAllBudgetLimitsByCurrency(TransactionCurrency $currency, Carbon $start = null, Carbon $end = null): Collection;
|
||||
public function getAllBudgetLimitsByCurrency(TransactionCurrency $currency, ?Carbon $start = null, ?Carbon $end = null): Collection;
|
||||
|
||||
public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection;
|
||||
public function getBudgetLimits(Budget $budget, ?Carbon $start = null, ?Carbon $end = null): Collection;
|
||||
|
||||
public function setUser(null|Authenticatable|User $user): void;
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
|
||||
continue;
|
||||
}
|
||||
$total = $limit->start_date->diffInDays($limit->end_date) + 1; // include the day itself.
|
||||
$total = $limit->start_date->diffInDays($limit->end_date, true) + 1; // include the day itself.
|
||||
$days = $this->daysInOverlap($limit, $start, $end);
|
||||
$amount = bcmul(bcdiv($limit->amount, (string)$total), (string)$days);
|
||||
$return[$currencyCode]['sum'] = bcadd($return[$currencyCode]['sum'], $amount);
|
||||
@@ -183,21 +183,21 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
// |-----------|
|
||||
// |----------------|
|
||||
if ($start->gte($limit->start_date) && $end->lte($limit->end_date)) {
|
||||
return $start->diffInDays($end) + 1; // add one day
|
||||
return (int) $start->diffInDays($end, true) + 1; // add one day
|
||||
}
|
||||
// limit starts earlier and limit ends first:
|
||||
// |-----------|
|
||||
// |-------|
|
||||
if ($limit->start_date->lte($start) && $limit->end_date->lte($end)) {
|
||||
// return days in the range $start-$limit_end
|
||||
return $start->diffInDays($limit->end_date) + 1; // add one day, the day itself
|
||||
return (int) $start->diffInDays($limit->end_date, true) + 1; // add one day, the day itself
|
||||
}
|
||||
// limit starts later and limit ends earlier
|
||||
// |-----------|
|
||||
// |-------|
|
||||
if ($limit->start_date->gte($start) && $limit->end_date->gte($end)) {
|
||||
// return days in the range $limit_start - $end
|
||||
return $limit->start_date->diffInDays($end) + 1; // add one day, the day itself
|
||||
return (int) $limit->start_date->diffInDays($end, true) + 1; // add one day, the day itself
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -438,7 +438,7 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
*
|
||||
* @param null|int $budgetId |null
|
||||
*/
|
||||
public function find(int $budgetId = null): ?Budget
|
||||
public function find(?int $budgetId = null): ?Budget
|
||||
{
|
||||
return $this->user->budgets()->find($budgetId);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ interface BudgetRepositoryInterface
|
||||
|
||||
public function destroyAutoBudget(Budget $budget): void;
|
||||
|
||||
public function find(int $budgetId = null): ?Budget;
|
||||
public function find(?int $budgetId = null): ?Budget;
|
||||
|
||||
public function findBudget(?int $budgetId, ?string $budgetName): ?Budget;
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
$total = '0';
|
||||
$count = 0;
|
||||
foreach ($budget->budgetlimits as $limit) {
|
||||
$diff = $limit->start_date->diffInDays($limit->end_date);
|
||||
$diff = (int) $limit->start_date->diffInDays($limit->end_date, true);
|
||||
$diff = 0 === $diff ? 1 : $diff;
|
||||
$amount = $limit->amount;
|
||||
$perDay = bcdiv($amount, (string)$diff);
|
||||
|
||||
@@ -44,7 +44,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface
|
||||
return $linkType->transactionJournalLinks()->count();
|
||||
}
|
||||
|
||||
public function destroy(LinkType $linkType, LinkType $moveTo = null): bool
|
||||
public function destroy(LinkType $linkType, ?LinkType $moveTo = null): bool
|
||||
{
|
||||
if (null !== $moveTo) {
|
||||
TransactionJournalLink::where('link_type_id', $linkType->id)->update(['link_type_id' => $moveTo->id]);
|
||||
@@ -113,7 +113,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface
|
||||
/**
|
||||
* Returns all the journal links (of a specific type).
|
||||
*/
|
||||
public function getJournalLinks(LinkType $linkType = null): Collection
|
||||
public function getJournalLinks(?LinkType $linkType = null): Collection
|
||||
{
|
||||
$query = TransactionJournalLink::with(['source', 'destination'])
|
||||
->leftJoin('transaction_journals as source_journals', 'journal_links.source_id', '=', 'source_journals.id')
|
||||
@@ -225,7 +225,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface
|
||||
return LinkType::find($linkTypeId);
|
||||
}
|
||||
|
||||
public function findByName(string $name = null): ?LinkType
|
||||
public function findByName(?string $name = null): ?LinkType
|
||||
{
|
||||
if (null === $name) {
|
||||
return null;
|
||||
|
||||
@@ -37,7 +37,7 @@ interface LinkTypeRepositoryInterface
|
||||
{
|
||||
public function countJournals(LinkType $linkType): int;
|
||||
|
||||
public function destroy(LinkType $linkType, LinkType $moveTo = null): bool;
|
||||
public function destroy(LinkType $linkType, ?LinkType $moveTo = null): bool;
|
||||
|
||||
public function destroyLink(TransactionJournalLink $link): bool;
|
||||
|
||||
@@ -46,7 +46,7 @@ interface LinkTypeRepositoryInterface
|
||||
/**
|
||||
* Find link type by name.
|
||||
*/
|
||||
public function findByName(string $name = null): ?LinkType;
|
||||
public function findByName(?string $name = null): ?LinkType;
|
||||
|
||||
/**
|
||||
* Check if link exists between journals.
|
||||
@@ -65,7 +65,7 @@ interface LinkTypeRepositoryInterface
|
||||
*/
|
||||
public function getJournalIds(LinkType $linkType): array;
|
||||
|
||||
public function getJournalLinks(LinkType $linkType = null): Collection;
|
||||
public function getJournalLinks(?LinkType $linkType = null): Collection;
|
||||
|
||||
/**
|
||||
* Return list of existing connections.
|
||||
|
||||
@@ -301,7 +301,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
|
||||
if (null !== $piggyBank->targetdate && $repetition->currentamount < $piggyBank->targetamount) {
|
||||
$now = today(config('app.timezone'));
|
||||
$startDate = null !== $piggyBank->startdate && $piggyBank->startdate->gte($now) ? $piggyBank->startdate : $now;
|
||||
$diffInMonths = $startDate->diffInMonths($piggyBank->targetdate, false);
|
||||
$diffInMonths = (int) $startDate->diffInMonths($piggyBank->targetdate);
|
||||
$remainingAmount = bcsub($piggyBank->targetamount, $repetition->currentamount);
|
||||
|
||||
// more than 1 month to go and still need money to save:
|
||||
|
||||
@@ -202,7 +202,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
/**
|
||||
* Returns the journals created for this recurrence, possibly limited by time.
|
||||
*/
|
||||
public function getJournalCount(Recurrence $recurrence, Carbon $start = null, Carbon $end = null): int
|
||||
public function getJournalCount(Recurrence $recurrence, ?Carbon $start = null, ?Carbon $end = null): int
|
||||
{
|
||||
$query = TransactionJournal::leftJoin('journal_meta', 'journal_meta.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transaction_journals.user_id', $recurrence->user_id)
|
||||
@@ -375,6 +375,12 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
app('log')->debug('Now in getXOccurrencesSince()');
|
||||
$skipMod = $repetition->repetition_skip + 1;
|
||||
$occurrences = [];
|
||||
|
||||
// to fix #8616, take a few days from both dates, then filter the list to make sure no entries
|
||||
// from today or before are saved.
|
||||
$date->subDays(4);
|
||||
$afterDate->subDays(4);
|
||||
|
||||
if ('daily' === $repetition->repetition_type) {
|
||||
$occurrences = $this->getXDailyOccurrencesSince($date, $afterDate, $count, $skipMod);
|
||||
}
|
||||
@@ -407,7 +413,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
}
|
||||
$filtered = [];
|
||||
foreach ($occurrences as $date) {
|
||||
if ($date->lte($max)) {
|
||||
if ($date->lte($max) && $date->gt(today())) {
|
||||
$filtered[] = $date;
|
||||
}
|
||||
}
|
||||
@@ -470,7 +476,7 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
if (false === $repDate) {
|
||||
$repDate = clone $today;
|
||||
}
|
||||
$diffInYears = $today->diffInYears($repDate);
|
||||
$diffInYears = (int) $today->diffInYears($repDate, true);
|
||||
$repDate->addYears($diffInYears); // technically not necessary.
|
||||
$string = $repDate->isoFormat((string)trans('config.month_and_day_no_year_js'));
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ interface RecurringRepositoryInterface
|
||||
/**
|
||||
* Returns the count of journals created for this recurrence, possibly limited by time.
|
||||
*/
|
||||
public function getJournalCount(Recurrence $recurrence, Carbon $start = null, Carbon $end = null): int;
|
||||
public function getJournalCount(Recurrence $recurrence, ?Carbon $start = null, ?Carbon $end = null): int;
|
||||
|
||||
/**
|
||||
* Get journal ID's for journals created by this recurring transaction.
|
||||
|
||||
@@ -28,6 +28,7 @@ use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Services\Internal\Update\AccountUpdateService;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -39,6 +40,17 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
{
|
||||
use UserGroupTrait;
|
||||
|
||||
#[\Override]
|
||||
public function countAccounts(array $types): int
|
||||
{
|
||||
$query = $this->userGroup->accounts();
|
||||
if (0 !== count($types)) {
|
||||
$query->accountTypeIn($types);
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
public function findByAccountNumber(string $number, array $types): ?Account
|
||||
{
|
||||
$dbQuery = $this->userGroup
|
||||
@@ -161,25 +173,25 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
return $query->get(['accounts.*']);
|
||||
}
|
||||
|
||||
public function getAccountsByType(array $types, ?array $sort = []): Collection
|
||||
#[\Override]
|
||||
public function getAccountsInOrder(array $types, array $sort, int $startRow, int $endRow): Collection
|
||||
{
|
||||
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
|
||||
$query = $this->userGroup->accounts();
|
||||
if (0 !== count($types)) {
|
||||
$query->accountTypeIn($types);
|
||||
}
|
||||
$query->skip($startRow);
|
||||
$query->take($endRow - $startRow);
|
||||
|
||||
// add sort parameters. At this point they're filtered to allowed fields to sort by:
|
||||
if (0 !== count($sort)) {
|
||||
foreach ($sort as $param) {
|
||||
$query->orderBy($param[0], $param[1]);
|
||||
foreach ($sort as $label => $direction) {
|
||||
$query->orderBy(sprintf('accounts.%s', $label), $direction);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === count($sort)) {
|
||||
if (0 !== count($res)) {
|
||||
$query->orderBy('accounts.order', 'ASC');
|
||||
}
|
||||
$query->orderBy('accounts.active', 'DESC');
|
||||
$query->orderBy('accounts.name', 'ASC');
|
||||
}
|
||||
@@ -201,6 +213,60 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
return $query->get(['accounts.*']);
|
||||
}
|
||||
|
||||
public function resetAccountOrder(): void
|
||||
{
|
||||
$sets = [
|
||||
[AccountType::DEFAULT, AccountType::ASSET],
|
||||
[AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE],
|
||||
];
|
||||
foreach ($sets as $set) {
|
||||
$list = $this->getAccountsByType($set);
|
||||
$index = 1;
|
||||
foreach ($list as $account) {
|
||||
if (false === $account->active) {
|
||||
$account->order = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
if ($index !== (int)$account->order) {
|
||||
app('log')->debug(sprintf('Account #%d ("%s"): order should %d be but is %d.', $account->id, $account->name, $index, $account->order));
|
||||
$account->order = $index;
|
||||
$account->save();
|
||||
}
|
||||
++$index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getAccountsByType(array $types, ?array $sort = []): Collection
|
||||
{
|
||||
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
|
||||
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
|
||||
$query = $this->userGroup->accounts();
|
||||
if (0 !== count($types)) {
|
||||
$query->accountTypeIn($types);
|
||||
}
|
||||
|
||||
// add sort parameters. At this point they're filtered to allowed fields to sort by:
|
||||
if (count($sort) > 0) {
|
||||
foreach ($sort as $column => $direction) {
|
||||
if (in_array($column, $sortable, true)) {
|
||||
$query->orderBy(sprintf('accounts.%s', $column), $direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === count($sort)) {
|
||||
if (0 !== count($res)) {
|
||||
$query->orderBy('accounts.order', 'ASC');
|
||||
}
|
||||
$query->orderBy('accounts.active', 'DESC');
|
||||
$query->orderBy('accounts.name', 'ASC');
|
||||
}
|
||||
|
||||
return $query->get(['accounts.*']);
|
||||
}
|
||||
|
||||
public function searchAccount(string $query, array $types, int $limit): Collection
|
||||
{
|
||||
// search by group, not by user
|
||||
@@ -226,4 +292,13 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
|
||||
return $dbQuery->take($limit)->get(['accounts.*']);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function update(Account $account, array $data): Account
|
||||
{
|
||||
/** @var AccountUpdateService $service */
|
||||
$service = app(AccountUpdateService::class);
|
||||
|
||||
return $service->update($account, $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
interface AccountRepositoryInterface
|
||||
{
|
||||
public function countAccounts(array $types): int;
|
||||
|
||||
public function find(int $accountId): ?Account;
|
||||
|
||||
public function findByAccountNumber(string $number, array $types): ?Account;
|
||||
@@ -49,6 +51,11 @@ interface AccountRepositoryInterface
|
||||
|
||||
public function getAccountsByType(array $types, ?array $sort = []): Collection;
|
||||
|
||||
/**
|
||||
* Used in the infinite accounts list.
|
||||
*/
|
||||
public function getAccountsInOrder(array $types, array $sort, int $startRow, int $endRow): Collection;
|
||||
|
||||
public function getActiveAccountsByType(array $types): Collection;
|
||||
|
||||
/**
|
||||
@@ -56,9 +63,16 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function getMetaValue(Account $account, string $field): ?string;
|
||||
|
||||
/**
|
||||
* Reset order types of the mentioned accounts.
|
||||
*/
|
||||
public function resetAccountOrder(): void;
|
||||
|
||||
public function searchAccount(string $query, array $types, int $limit): Collection;
|
||||
|
||||
public function setUser(User $user): void;
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void;
|
||||
|
||||
public function update(Account $account, array $data): Account;
|
||||
}
|
||||
|
||||
57
app/Rules/IsValidActionExpression.php
Normal file
57
app/Rules/IsValidActionExpression.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* IsValidActionExpression.php
|
||||
* Copyright (c) 2024 Michael Thomas
|
||||
*
|
||||
* 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\Rules;
|
||||
|
||||
use FireflyIII\TransactionRules\Expressions\ActionExpression;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Translation\PotentiallyTranslatedString;
|
||||
|
||||
class IsValidActionExpression implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Check that the given action expression is syntactically valid.
|
||||
*
|
||||
* @param \Closure(string): PotentiallyTranslatedString $fail
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
if (false === config('firefly.feature_flags.expression_engine')) {
|
||||
return;
|
||||
}
|
||||
$value ??= '';
|
||||
$expr = new ActionExpression($value);
|
||||
|
||||
if (!$expr->isValid()) {
|
||||
$fail('validation.rule_action_expression')->translate(
|
||||
[
|
||||
'error' => $expr->getValidationError()->getMessage(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class Amount
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function formatAnything(TransactionCurrency $format, string $amount, bool $coloured = null): string
|
||||
public function formatAnything(TransactionCurrency $format, string $amount, ?bool $coloured = null): string
|
||||
{
|
||||
return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class Amount
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.MissingImport)
|
||||
*/
|
||||
public function formatFlat(string $symbol, int $decimalPlaces, string $amount, bool $coloured = null): string
|
||||
public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string
|
||||
{
|
||||
$locale = app('steam')->getLocale();
|
||||
$rounded = app('steam')->bcround($amount, $decimalPlaces);
|
||||
|
||||
@@ -120,4 +120,12 @@ class RemoteUserProvider implements UserProvider
|
||||
|
||||
throw new FireflyException(sprintf('C) Did not implement %s', __METHOD__));
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
throw new FireflyException(sprintf('Did not implement %s', __METHOD__));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ class WholePeriodChartGenerator
|
||||
protected function calculateStep(Carbon $start, Carbon $end): string
|
||||
{
|
||||
$step = '1D';
|
||||
$months = $start->diffInMonths($end);
|
||||
$months = $start->diffInMonths($end, true);
|
||||
if ($months > 3) {
|
||||
$step = '1W';
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class ExpandedForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function amountNoCurrency(string $name, $value = null, array $options = null): string
|
||||
public function amountNoCurrency(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
$options ??= [];
|
||||
$label = $this->label($name, $options);
|
||||
@@ -71,7 +71,7 @@ class ExpandedForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function checkbox(string $name, int $value = null, $checked = null, array $options = null): string
|
||||
public function checkbox(string $name, ?int $value = null, $checked = null, ?array $options = null): string
|
||||
{
|
||||
$options ??= [];
|
||||
$value ??= 1;
|
||||
@@ -106,7 +106,7 @@ class ExpandedForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function date(string $name, $value = null, array $options = null): string
|
||||
public function date(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
@@ -129,7 +129,7 @@ class ExpandedForm
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function file(string $name, array $options = null): string
|
||||
public function file(string $name, ?array $options = null): string
|
||||
{
|
||||
$options ??= [];
|
||||
$label = $this->label($name, $options);
|
||||
@@ -153,7 +153,7 @@ class ExpandedForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function integer(string $name, $value = null, array $options = null): string
|
||||
public function integer(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
$options ??= [];
|
||||
$label = $this->label($name, $options);
|
||||
@@ -179,7 +179,7 @@ class ExpandedForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function location(string $name, $value = null, array $options = null): string
|
||||
public function location(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
$options ??= [];
|
||||
$label = $this->label($name, $options);
|
||||
@@ -227,7 +227,7 @@ class ExpandedForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function objectGroup($value = null, array $options = null): string
|
||||
public function objectGroup($value = null, ?array $options = null): string
|
||||
{
|
||||
$name = 'object_group';
|
||||
$label = $this->label($name, $options);
|
||||
@@ -272,7 +272,7 @@ class ExpandedForm
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function password(string $name, array $options = null): string
|
||||
public function password(string $name, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
@@ -297,7 +297,7 @@ class ExpandedForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function percentage(string $name, $value = null, array $options = null): string
|
||||
public function percentage(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
@@ -323,7 +323,7 @@ class ExpandedForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function staticText(string $name, $value, array $options = null): string
|
||||
public function staticText(string $name, $value, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
@@ -346,7 +346,7 @@ class ExpandedForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function text(string $name, $value = null, array $options = null): string
|
||||
public function text(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
@@ -370,7 +370,7 @@ class ExpandedForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function textarea(string $name, $value = null, array $options = null): string
|
||||
public function textarea(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
|
||||
@@ -43,7 +43,7 @@ class AccountForm
|
||||
/**
|
||||
* Grouped dropdown list of all accounts that are valid as the destination of a withdrawal.
|
||||
*/
|
||||
public function activeDepositDestinations(string $name, mixed $value = null, array $options = null): string
|
||||
public function activeDepositDestinations(string $name, mixed $value = null, ?array $options = null): string
|
||||
{
|
||||
$types = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN, AccountType::REVENUE];
|
||||
$repository = $this->getAccountRepository();
|
||||
@@ -55,7 +55,7 @@ class AccountForm
|
||||
return $this->select($name, $grouped, $value, $options);
|
||||
}
|
||||
|
||||
private function getAccountsGrouped(array $types, AccountRepositoryInterface $repository = null): array
|
||||
private function getAccountsGrouped(array $types, ?AccountRepositoryInterface $repository = null): array
|
||||
{
|
||||
if (null === $repository) {
|
||||
$repository = $this->getAccountRepository();
|
||||
@@ -89,7 +89,7 @@ class AccountForm
|
||||
/**
|
||||
* Grouped dropdown list of all accounts that are valid as the destination of a withdrawal.
|
||||
*/
|
||||
public function activeWithdrawalDestinations(string $name, mixed $value = null, array $options = null): string
|
||||
public function activeWithdrawalDestinations(string $name, mixed $value = null, ?array $options = null): string
|
||||
{
|
||||
$types = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN, AccountType::EXPENSE];
|
||||
$repository = $this->getAccountRepository();
|
||||
@@ -107,7 +107,7 @@ class AccountForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function assetAccountCheckList(string $name, array $options = null): string
|
||||
public function assetAccountCheckList(string $name, ?array $options = null): string
|
||||
{
|
||||
$options ??= [];
|
||||
$label = $this->label($name, $options);
|
||||
@@ -138,7 +138,7 @@ class AccountForm
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function assetAccountList(string $name, $value = null, array $options = null): string
|
||||
public function assetAccountList(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
$types = [AccountType::ASSET, AccountType::DEFAULT];
|
||||
$grouped = $this->getAccountsGrouped($types);
|
||||
@@ -151,7 +151,7 @@ class AccountForm
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function longAccountList(string $name, $value = null, array $options = null): string
|
||||
public function longAccountList(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
$types = [AccountType::ASSET, AccountType::DEFAULT, AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN];
|
||||
$grouped = $this->getAccountsGrouped($types);
|
||||
|
||||
@@ -42,7 +42,7 @@ class CurrencyForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function amount(string $name, $value = null, array $options = null): string
|
||||
public function amount(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
return $this->currencyField($name, 'amount', $value, $options);
|
||||
}
|
||||
@@ -50,7 +50,7 @@ class CurrencyForm
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function currencyField(string $name, string $view, mixed $value = null, array $options = null): string
|
||||
protected function currencyField(string $name, string $view, mixed $value = null, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
@@ -106,7 +106,7 @@ class CurrencyForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function balanceAll(string $name, $value = null, array $options = null): string
|
||||
public function balanceAll(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
return $this->allCurrencyField($name, 'balance', $value, $options);
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class CurrencyForm
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function allCurrencyField(string $name, string $view, $value = null, array $options = null): string
|
||||
protected function allCurrencyField(string $name, string $view, $value = null, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
@@ -173,7 +173,7 @@ class CurrencyForm
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function currencyList(string $name, $value = null, array $options = null): string
|
||||
public function currencyList(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $currencyRepos */
|
||||
$currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||
@@ -195,7 +195,7 @@ class CurrencyForm
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function currencyListEmpty(string $name, $value = null, array $options = null): string
|
||||
public function currencyListEmpty(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $currencyRepos */
|
||||
$currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||
|
||||
@@ -36,7 +36,7 @@ trait FormSupport
|
||||
/**
|
||||
* @param mixed $selected
|
||||
*/
|
||||
public function select(string $name, array $list = null, $selected = null, array $options = null): string
|
||||
public function select(string $name, ?array $list = null, $selected = null, ?array $options = null): string
|
||||
{
|
||||
$list ??= [];
|
||||
$label = $this->label($name, $options);
|
||||
@@ -55,7 +55,7 @@ trait FormSupport
|
||||
return $html;
|
||||
}
|
||||
|
||||
protected function label(string $name, array $options = null): string
|
||||
protected function label(string $name, ?array $options = null): string
|
||||
{
|
||||
$options ??= [];
|
||||
if (array_key_exists('label', $options)) {
|
||||
@@ -69,7 +69,7 @@ trait FormSupport
|
||||
/**
|
||||
* @param mixed $label
|
||||
*/
|
||||
protected function expandOptionArray(string $name, $label, array $options = null): array
|
||||
protected function expandOptionArray(string $name, $label, ?array $options = null): array
|
||||
{
|
||||
$options ??= [];
|
||||
$name = str_replace('[]', '', $name);
|
||||
|
||||
@@ -40,7 +40,7 @@ class PiggyBankForm
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function piggyBankList(string $name, $value = null, array $options = null): string
|
||||
public function piggyBankList(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
// make repositories
|
||||
/** @var PiggyBankRepositoryInterface $repository */
|
||||
|
||||
@@ -34,7 +34,7 @@ class RuleForm
|
||||
{
|
||||
use FormSupport;
|
||||
|
||||
public function ruleGroupList(string $name, mixed $value = null, array $options = null): string
|
||||
public function ruleGroupList(string $name, mixed $value = null, ?array $options = null): string
|
||||
{
|
||||
/** @var RuleGroupRepositoryInterface $groupRepos */
|
||||
$groupRepos = app(RuleGroupRepositoryInterface::class);
|
||||
@@ -54,7 +54,7 @@ class RuleForm
|
||||
/**
|
||||
* @param null $value
|
||||
*/
|
||||
public function ruleGroupListWithEmpty(string $name, $value = null, array $options = null): string
|
||||
public function ruleGroupListWithEmpty(string $name, $value = null, ?array $options = null): string
|
||||
{
|
||||
$options ??= [];
|
||||
$options['class'] = 'form-control';
|
||||
|
||||
@@ -39,14 +39,14 @@ trait DateCalculation
|
||||
*/
|
||||
public function activeDaysLeft(Carbon $start, Carbon $end): int
|
||||
{
|
||||
$difference = $start->diffInDays($end) + 1;
|
||||
$difference = (int)($start->diffInDays($end, true) + 1);
|
||||
$today = today(config('app.timezone'))->startOfDay();
|
||||
|
||||
if ($start->lte($today) && $end->gte($today)) {
|
||||
$difference = $today->diffInDays($end);
|
||||
}
|
||||
|
||||
return 0 === $difference ? 1 : $difference;
|
||||
return (int) (0 === $difference ? 1 : $difference);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,20 +56,20 @@ trait DateCalculation
|
||||
*/
|
||||
protected function activeDaysPassed(Carbon $start, Carbon $end): int
|
||||
{
|
||||
$difference = $start->diffInDays($end) + 1;
|
||||
$difference = $start->diffInDays($end, true) + 1;
|
||||
$today = today(config('app.timezone'))->startOfDay();
|
||||
|
||||
if ($start->lte($today) && $end->gte($today)) {
|
||||
$difference = $start->diffInDays($today) + 1;
|
||||
$difference = $start->diffInDays($today, true) + 1;
|
||||
}
|
||||
|
||||
return $difference;
|
||||
return (int) $difference;
|
||||
}
|
||||
|
||||
protected function calculateStep(Carbon $start, Carbon $end): string
|
||||
{
|
||||
$step = '1D';
|
||||
$months = $start->diffInMonths($end);
|
||||
$months = $start->diffInMonths($end, true);
|
||||
if ($months > 3) {
|
||||
$step = '1W';
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class Navigation
|
||||
{
|
||||
private Calculator $calculator;
|
||||
|
||||
public function __construct(Calculator $calculator = null)
|
||||
public function __construct(?Calculator $calculator = null)
|
||||
{
|
||||
$this->calculator = $calculator instanceof Calculator ? $calculator : new Calculator();
|
||||
}
|
||||
@@ -161,7 +161,7 @@ class Navigation
|
||||
public function startOfPeriod(Carbon $theDate, string $repeatFreq): Carbon
|
||||
{
|
||||
$date = clone $theDate;
|
||||
|
||||
Log::debug(sprintf('Now in startOfPeriod("%s", "%s")', $date->toIso8601String(), $repeatFreq));
|
||||
$functionMap = [
|
||||
'1D' => 'startOfDay',
|
||||
'daily' => 'startOfDay',
|
||||
@@ -178,15 +178,32 @@ class Navigation
|
||||
'yearly' => 'startOfYear',
|
||||
'1Y' => 'startOfYear',
|
||||
];
|
||||
|
||||
$parameterMap = [
|
||||
'startOfWeek' => [Carbon::MONDAY],
|
||||
];
|
||||
|
||||
if (array_key_exists($repeatFreq, $functionMap)) {
|
||||
$function = $functionMap[$repeatFreq];
|
||||
Log::debug(sprintf('Function is ->%s()', $function));
|
||||
if (array_key_exists($function, $parameterMap)) {
|
||||
Log::debug(sprintf('Parameter map, function becomes ->%s(%s)', $function, implode(', ', $parameterMap[$function])));
|
||||
$date->{$function}($parameterMap[$function][0]);
|
||||
Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
$date->{$function}(); // @phpstan-ignore-line
|
||||
Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
|
||||
|
||||
return $date;
|
||||
}
|
||||
if ('half-year' === $repeatFreq || '6M' === $repeatFreq) {
|
||||
$skipTo = $date->month > 7 ? 6 : 0;
|
||||
$date->startOfYear()->addMonths($skipTo);
|
||||
Log::debug(sprintf('Custom call for "%s": addMonths(%d)', $repeatFreq, $skipTo));
|
||||
Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
|
||||
|
||||
return $date;
|
||||
}
|
||||
@@ -202,10 +219,14 @@ class Navigation
|
||||
default => null,
|
||||
};
|
||||
if (null !== $result) {
|
||||
Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ('custom' === $repeatFreq) {
|
||||
Log::debug(sprintf('Custom, result is "%s"', $date->toIso8601String()));
|
||||
|
||||
return $date; // the date is already at the start.
|
||||
}
|
||||
Log::error(sprintf('Cannot do startOfPeriod for $repeat_freq "%s"', $repeatFreq));
|
||||
@@ -216,6 +237,7 @@ class Navigation
|
||||
public function endOfPeriod(Carbon $end, string $repeatFreq): Carbon
|
||||
{
|
||||
$currentEnd = clone $end;
|
||||
Log::debug(sprintf('Now in endOfPeriod("%s", "%s").', $currentEnd->toIso8601String(), $repeatFreq));
|
||||
|
||||
$functionMap = [
|
||||
'1D' => 'endOfDay',
|
||||
@@ -252,7 +274,7 @@ class Navigation
|
||||
|
||||
/** @var Carbon $tEnd */
|
||||
$tEnd = session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$diffInDays = $tStart->diffInDays($tEnd);
|
||||
$diffInDays = (int) $tStart->diffInDays($tEnd, true);
|
||||
}
|
||||
Log::debug(sprintf('Diff in days is %d', $diffInDays));
|
||||
$currentEnd->addDays($diffInDays);
|
||||
@@ -296,6 +318,7 @@ class Navigation
|
||||
if (in_array($repeatFreq, $subDay, true)) {
|
||||
$currentEnd->subDay();
|
||||
}
|
||||
Log::debug(sprintf('Final result: %s', $currentEnd->toIso8601String()));
|
||||
|
||||
return $currentEnd;
|
||||
}
|
||||
@@ -304,7 +327,7 @@ class Navigation
|
||||
{
|
||||
$endOfMonth = $date->copy()->endOfMonth();
|
||||
|
||||
return $date->diffInDays($endOfMonth);
|
||||
return (int) $date->diffInDays($endOfMonth, true);
|
||||
}
|
||||
|
||||
public function diffInPeriods(string $period, int $skip, Carbon $beginning, Carbon $end): int
|
||||
@@ -317,12 +340,12 @@ class Navigation
|
||||
$end->format('Y-m-d')
|
||||
));
|
||||
$map = [
|
||||
'daily' => 'floatDiffInDays',
|
||||
'weekly' => 'floatDiffInWeeks',
|
||||
'monthly' => 'floatDiffInMonths',
|
||||
'quarterly' => 'floatDiffInMonths',
|
||||
'half-year' => 'floatDiffInMonths',
|
||||
'yearly' => 'floatDiffInYears',
|
||||
'daily' => 'diffInDays',
|
||||
'weekly' => 'diffInWeeks',
|
||||
'monthly' => 'diffInMonths',
|
||||
'quarterly' => 'diffInMonths',
|
||||
'half-year' => 'diffInMonths',
|
||||
'yearly' => 'diffInYears',
|
||||
];
|
||||
if (!array_key_exists($period, $map)) {
|
||||
Log::warning(sprintf('No diffInPeriods for period "%s"', $period));
|
||||
@@ -331,7 +354,7 @@ class Navigation
|
||||
}
|
||||
$func = $map[$period];
|
||||
// first do the diff
|
||||
$floatDiff = $beginning->{$func}($end); // @phpstan-ignore-line
|
||||
$floatDiff = $beginning->{$func}($end, true); // @phpstan-ignore-line
|
||||
|
||||
// then correct for quarterly or half-year
|
||||
if ('quarterly' === $period) {
|
||||
@@ -442,13 +465,13 @@ class Navigation
|
||||
$format = $this->preferredCarbonFormat($start, $end);
|
||||
$displayFormat = (string)trans('config.month_and_day_js', [], $locale);
|
||||
// increment by month (for year)
|
||||
if ($start->diffInMonths($end) > 1) {
|
||||
if ($start->diffInMonths($end, true) > 1) {
|
||||
$increment = 'addMonth';
|
||||
$displayFormat = (string)trans('config.month_js');
|
||||
}
|
||||
|
||||
// increment by year (for multi year)
|
||||
if ($start->diffInMonths($end) > 12) {
|
||||
// increment by year (for multi-year)
|
||||
if ($start->diffInMonths($end, true) > 12) {
|
||||
$increment = 'addYear';
|
||||
$displayFormat = (string)trans('config.year_js');
|
||||
}
|
||||
@@ -471,11 +494,11 @@ class Navigation
|
||||
public function preferredCarbonFormat(Carbon $start, Carbon $end): string
|
||||
{
|
||||
$format = 'Y-m-d';
|
||||
if ($start->diffInMonths($end) > 1) {
|
||||
if ((int)$start->diffInMonths($end, true) > 1) {
|
||||
$format = 'Y-m';
|
||||
}
|
||||
|
||||
if ($start->diffInMonths($end) > 12) {
|
||||
if ((int)$start->diffInMonths($end, true) > 12) {
|
||||
$format = 'Y';
|
||||
}
|
||||
|
||||
@@ -540,11 +563,11 @@ class Navigation
|
||||
{
|
||||
$locale = app('steam')->getLocale();
|
||||
$format = (string)trans('config.month_and_day_js', [], $locale);
|
||||
if ($start->diffInMonths($end) > 1) {
|
||||
if ($start->diffInMonths($end, true) > 1) {
|
||||
$format = (string)trans('config.month_js', [], $locale);
|
||||
}
|
||||
|
||||
if ($start->diffInMonths($end) > 12) {
|
||||
if ($start->diffInMonths($end, true) > 12) {
|
||||
$format = (string)trans('config.year_js', [], $locale);
|
||||
}
|
||||
|
||||
@@ -558,11 +581,11 @@ class Navigation
|
||||
public function preferredEndOfPeriod(Carbon $start, Carbon $end): string
|
||||
{
|
||||
$format = 'endOfDay';
|
||||
if ($start->diffInMonths($end) > 1) {
|
||||
if ((int)$start->diffInMonths($end, true) > 1) {
|
||||
$format = 'endOfMonth';
|
||||
}
|
||||
|
||||
if ($start->diffInMonths($end) > 12) {
|
||||
if ((int)$start->diffInMonths($end, true) > 12) {
|
||||
$format = 'endOfYear';
|
||||
}
|
||||
|
||||
@@ -576,11 +599,11 @@ class Navigation
|
||||
public function preferredRangeFormat(Carbon $start, Carbon $end): string
|
||||
{
|
||||
$format = '1D';
|
||||
if ($start->diffInMonths($end) > 1) {
|
||||
if ((int)$start->diffInMonths($end, true) > 1) {
|
||||
$format = '1M';
|
||||
}
|
||||
|
||||
if ($start->diffInMonths($end) > 12) {
|
||||
if ((int)$start->diffInMonths($end, true) > 12) {
|
||||
$format = '1Y';
|
||||
}
|
||||
|
||||
@@ -594,11 +617,11 @@ class Navigation
|
||||
public function preferredSqlFormat(Carbon $start, Carbon $end): string
|
||||
{
|
||||
$format = '%Y-%m-%d';
|
||||
if ($start->diffInMonths($end) > 1) {
|
||||
if ((int)$start->diffInMonths($end, true) > 1) {
|
||||
$format = '%Y-%m';
|
||||
}
|
||||
|
||||
if ($start->diffInMonths($end) > 12) {
|
||||
if ((int)$start->diffInMonths($end, true) > 12) {
|
||||
$format = '%Y';
|
||||
}
|
||||
|
||||
@@ -608,7 +631,7 @@ class Navigation
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function subtractPeriod(Carbon $theDate, string $repeatFreq, int $subtract = null): Carbon
|
||||
public function subtractPeriod(Carbon $theDate, string $repeatFreq, ?int $subtract = null): Carbon
|
||||
{
|
||||
$subtract ??= 1;
|
||||
$date = clone $theDate;
|
||||
@@ -654,7 +677,7 @@ class Navigation
|
||||
|
||||
/** @var Carbon $tEnd */
|
||||
$tEnd = session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$diffInDays = $tStart->diffInDays($tEnd);
|
||||
$diffInDays = (int) $tStart->diffInDays($tEnd, true);
|
||||
$date->subDays($diffInDays * $subtract);
|
||||
|
||||
return $date;
|
||||
|
||||
@@ -126,8 +126,8 @@ class ParseDateString
|
||||
default => $today,
|
||||
'yesterday' => $today->subDay(),
|
||||
'tomorrow' => $today->addDay(),
|
||||
'start of this week' => $today->startOfWeek(),
|
||||
'end of this week' => $today->endOfWeek(),
|
||||
'start of this week' => $today->startOfWeek(Carbon::MONDAY),
|
||||
'end of this week' => $today->endOfWeek(Carbon::SUNDAY),
|
||||
'start of this month' => $today->startOfMonth(),
|
||||
'end of this month' => $today->endOfMonth(),
|
||||
'start of this quarter' => $today->startOfQuarter(),
|
||||
|
||||
@@ -79,13 +79,14 @@ trait CalculateXOccurrencesSince
|
||||
while ($total < $count) {
|
||||
$domCorrected = min($dayOfMonth, $mutator->daysInMonth);
|
||||
$mutator->day = $domCorrected;
|
||||
app('log')->debug(sprintf('Mutator is now %s', $mutator->format('Y-m-d')));
|
||||
if (0 === $attempts % $skipMod && $mutator->gte($afterDate)) {
|
||||
app('log')->debug('Is added to the list.');
|
||||
$return[] = clone $mutator;
|
||||
++$total;
|
||||
}
|
||||
++$attempts;
|
||||
$mutator = $mutator->endOfMonth()->addDay();
|
||||
app('log')->debug(sprintf('Mutator is now %s', $mutator->format('Y-m-d')));
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
||||
52
app/Support/Request/GetSortInstructions.php
Normal file
52
app/Support/Request/GetSortInstructions.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/*
|
||||
* GetSortInstructions.php
|
||||
* Copyright (c) 2024 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\Support\Request;
|
||||
|
||||
trait GetSortInstructions
|
||||
{
|
||||
final public function getSortInstructions(string $key): array
|
||||
{
|
||||
$allowed = config(sprintf('firefly.sorting.allowed.%s', $key));
|
||||
$set = $this->get('sorting', []);
|
||||
$result = [];
|
||||
if (0 === count($set)) {
|
||||
return [];
|
||||
}
|
||||
foreach ($set as $info) {
|
||||
$column = $info['column'] ?? 'NOPE';
|
||||
$direction = $info['direction'] ?? 'NOPE';
|
||||
if ('asc' !== $direction && 'desc' !== $direction) {
|
||||
// skip invalid direction
|
||||
continue;
|
||||
}
|
||||
if (false === in_array($column, $allowed, true)) {
|
||||
// skip invalid column
|
||||
continue;
|
||||
}
|
||||
$result[$column] = $direction;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,8 @@ class OperatorQuerySearch implements SearchInterface
|
||||
private GroupCollectorInterface $collector;
|
||||
private CurrencyRepositoryInterface $currencyRepository;
|
||||
private array $excludeTags;
|
||||
private array $includeAnyTags;
|
||||
// added to fix #8632
|
||||
private array $includeTags;
|
||||
private array $invalidOperators;
|
||||
private int $limit;
|
||||
@@ -93,6 +95,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
$this->page = 1;
|
||||
$this->words = [];
|
||||
$this->excludeTags = [];
|
||||
$this->includeAnyTags = [];
|
||||
$this->includeTags = [];
|
||||
$this->prohibitedWords = [];
|
||||
$this->invalidOperators = [];
|
||||
@@ -1112,8 +1115,9 @@ class OperatorQuerySearch implements SearchInterface
|
||||
$this->collector->findNothing();
|
||||
}
|
||||
if ($tags->count() > 0) {
|
||||
// changed from includeTags to includeAnyTags for #8632
|
||||
$ids = array_values($tags->pluck('id')->toArray());
|
||||
$this->includeTags = array_unique(array_merge($this->includeTags, $ids));
|
||||
$this->includeAnyTags = array_unique(array_merge($this->includeAnyTags, $ids));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1125,8 +1129,9 @@ class OperatorQuerySearch implements SearchInterface
|
||||
$this->collector->findNothing();
|
||||
}
|
||||
if ($tags->count() > 0) {
|
||||
// changed from includeTags to includeAnyTags for #8632
|
||||
$ids = array_values($tags->pluck('id')->toArray());
|
||||
$this->includeTags = array_unique(array_merge($this->includeTags, $ids));
|
||||
$this->includeAnyTags = array_unique(array_merge($this->includeAnyTags, $ids));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -2725,6 +2730,19 @@ class OperatorQuerySearch implements SearchInterface
|
||||
}
|
||||
$this->collector->setAllTags($collection);
|
||||
}
|
||||
// if include ANY tags, include them: (see #8632)
|
||||
if (count($this->includeAnyTags) > 0) {
|
||||
app('log')->debug(sprintf('%d include ANY tag(s)', count($this->includeAnyTags)));
|
||||
$collection = new Collection();
|
||||
foreach ($this->includeAnyTags as $tagId) {
|
||||
$tag = $this->tagRepository->find($tagId);
|
||||
if (null !== $tag) {
|
||||
app('log')->debug(sprintf('Include ANY tag "%s"', $tag->tag));
|
||||
$collection->push($tag);
|
||||
}
|
||||
}
|
||||
$this->collector->setTags($collection);
|
||||
}
|
||||
}
|
||||
|
||||
public function getWords(): array
|
||||
|
||||
@@ -851,7 +851,7 @@ class Steam
|
||||
return number_format((float)$value, 0, '.', '');
|
||||
}
|
||||
|
||||
public function opposite(string $amount = null): ?string
|
||||
public function opposite(?string $amount = null): ?string
|
||||
{
|
||||
if (null === $amount) {
|
||||
return null;
|
||||
|
||||
@@ -87,7 +87,7 @@ class AmountFormat extends AbstractExtension
|
||||
{
|
||||
return new TwigFunction(
|
||||
'formatAmountByAccount',
|
||||
static function (AccountModel $account, string $amount, bool $coloured = null): string {
|
||||
static function (AccountModel $account, string $amount, ?bool $coloured = null): string {
|
||||
$coloured ??= true;
|
||||
|
||||
/** @var AccountRepositoryInterface $accountRepos */
|
||||
@@ -107,7 +107,7 @@ class AmountFormat extends AbstractExtension
|
||||
{
|
||||
return new TwigFunction(
|
||||
'formatAmountBySymbol',
|
||||
static function (string $amount, string $symbol, int $decimalPlaces = null, bool $coloured = null): string {
|
||||
static function (string $amount, string $symbol, ?int $decimalPlaces = null, ?bool $coloured = null): string {
|
||||
$decimalPlaces ??= 2;
|
||||
$coloured ??= true;
|
||||
$currency = new TransactionCurrency();
|
||||
@@ -127,7 +127,7 @@ class AmountFormat extends AbstractExtension
|
||||
{
|
||||
return new TwigFunction(
|
||||
'formatAmountByCurrency',
|
||||
static function (TransactionCurrency $currency, string $amount, bool $coloured = null): string {
|
||||
static function (TransactionCurrency $currency, string $amount, ?bool $coloured = null): string {
|
||||
$coloured ??= true;
|
||||
|
||||
return app('amount')->formatAnything($currency, $amount, $coloured);
|
||||
|
||||
@@ -54,11 +54,12 @@ class AddTag implements ActionInterface
|
||||
/** @var User $user */
|
||||
$user = User::find($journal['user_id']);
|
||||
$factory->setUser($user);
|
||||
$tag = $factory->findOrCreate($this->action->action_value);
|
||||
$tagName = $this->action->getValue($journal);
|
||||
$tag = $factory->findOrCreate($tagName);
|
||||
|
||||
if (null === $tag) {
|
||||
// could not find, could not create tag.
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.find_or_create_tag_failed', ['tag' => $this->action->action_value])));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.find_or_create_tag_failed', ['tag' => $tagName])));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -84,7 +85,7 @@ class AddTag implements ActionInterface
|
||||
app('log')->debug(
|
||||
sprintf('RuleAction AddTag fired but tag %d ("%s") was already added to journal %d.', $tag->id, $tag->tag, $journal['transaction_journal_id'])
|
||||
);
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.tag_already_added', ['tag' => $this->action->action_value])));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.tag_already_added', ['tag' => $tagName])));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -26,12 +26,16 @@ namespace FireflyIII\TransactionRules\Actions;
|
||||
use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
|
||||
/**
|
||||
* Class AppendDescription.
|
||||
* TODO Can be replaced (and migrated) to action "set description" with a prefilled expression
|
||||
*/
|
||||
class AppendDescription implements ActionInterface
|
||||
{
|
||||
use RefreshNotesTrait;
|
||||
|
||||
private RuleAction $action;
|
||||
|
||||
/**
|
||||
@@ -44,7 +48,9 @@ class AppendDescription implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$description = sprintf('%s %s', $journal['description'], $this->action->action_value);
|
||||
$this->refreshNotes($journal);
|
||||
$append = $this->action->getValue($journal);
|
||||
$description = sprintf('%s %s', $journal['description'], $append);
|
||||
\DB::table('transaction_journals')->where('id', $journal['transaction_journal_id'])->limit(1)->update(['description' => $description]);
|
||||
|
||||
// event for audit log entry
|
||||
|
||||
@@ -29,12 +29,16 @@ use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
|
||||
/**
|
||||
* Class AppendDescriptionToNotes
|
||||
* TODO Can be replaced (and migrated) to action "set notes" with a prefilled expression
|
||||
*/
|
||||
class AppendDescriptionToNotes implements ActionInterface
|
||||
{
|
||||
use RefreshNotesTrait;
|
||||
|
||||
private RuleAction $action;
|
||||
|
||||
/**
|
||||
@@ -47,6 +51,8 @@ class AppendDescriptionToNotes implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$this->refreshNotes($journal);
|
||||
|
||||
/** @var null|TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
if (null === $object) {
|
||||
|
||||
@@ -27,12 +27,16 @@ use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
|
||||
/**
|
||||
* Class AppendNotes.
|
||||
* TODO Can be replaced (and migrated) to action "set notes" with a prefilled expression
|
||||
*/
|
||||
class AppendNotes implements ActionInterface
|
||||
{
|
||||
use RefreshNotesTrait;
|
||||
|
||||
private RuleAction $action;
|
||||
|
||||
/**
|
||||
@@ -45,6 +49,7 @@ class AppendNotes implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$this->refreshNotes($journal);
|
||||
$dbNote = Note::where('noteable_id', (int)$journal['transaction_journal_id'])
|
||||
->where('noteable_type', TransactionJournal::class)
|
||||
->first(['notes.*'])
|
||||
@@ -55,15 +60,16 @@ class AppendNotes implements ActionInterface
|
||||
$dbNote->noteable_type = TransactionJournal::class;
|
||||
$dbNote->text = '';
|
||||
}
|
||||
app('log')->debug(sprintf('RuleAction AppendNotes appended "%s" to "%s".', $this->action->action_value, $dbNote->text));
|
||||
$before = $dbNote->text;
|
||||
$text = sprintf('%s%s', $dbNote->text, $this->action->action_value);
|
||||
$append = $this->action->getValue($journal);
|
||||
$text = sprintf('%s%s', $dbNote->text, $append);
|
||||
$dbNote->text = $text;
|
||||
$dbNote->save();
|
||||
|
||||
/** @var TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
|
||||
app('log')->debug(sprintf('RuleAction AppendNotes appended "%s" to "%s".', $append, $before));
|
||||
event(new TriggeredAuditLog($this->action->rule, $object, 'update_notes', $before, $text));
|
||||
|
||||
return true;
|
||||
|
||||
@@ -30,13 +30,16 @@ use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
|
||||
/**
|
||||
* Class AppendNotesToDescription
|
||||
* TODO Can be replaced (and migrated) to action "set description" with a prefilled expression
|
||||
*/
|
||||
class AppendNotesToDescription implements ActionInterface
|
||||
{
|
||||
use ConvertsDataTypes;
|
||||
use RefreshNotesTrait;
|
||||
|
||||
private RuleAction $action;
|
||||
|
||||
@@ -51,6 +54,7 @@ class AppendNotesToDescription implements ActionInterface
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
app('log')->debug('Now in AppendNotesToDescription');
|
||||
$this->refreshNotes($journal);
|
||||
|
||||
/** @var null|TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
|
||||
@@ -52,6 +52,8 @@ class ConvertToDeposit implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$actionValue = $this->action->getValue($journal);
|
||||
|
||||
// make object from array (so the data is fresh).
|
||||
/** @var null|TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
@@ -82,7 +84,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
app('log')->debug('Going to transform a withdrawal to a deposit.');
|
||||
|
||||
try {
|
||||
$res = $this->convertWithdrawalArray($object);
|
||||
$res = $this->convertWithdrawalArray($object, $actionValue);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->debug('Could not convert withdrawal to deposit.');
|
||||
app('log')->error($e->getMessage());
|
||||
@@ -99,7 +101,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
app('log')->debug('Going to transform a transfer to a deposit.');
|
||||
|
||||
try {
|
||||
$res = $this->convertTransferArray($object);
|
||||
$res = $this->convertTransferArray($object, $actionValue);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->debug('Could not convert transfer to deposit.');
|
||||
app('log')->error($e->getMessage());
|
||||
@@ -122,7 +124,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function convertWithdrawalArray(TransactionJournal $journal): bool
|
||||
private function convertWithdrawalArray(TransactionJournal $journal, string $actionValue = ''): bool
|
||||
{
|
||||
$user = $journal->user;
|
||||
|
||||
@@ -139,7 +141,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
|
||||
// get the action value, or use the original destination name in case the action value is empty:
|
||||
// this becomes a new or existing (revenue) account, which is the source of the new deposit.
|
||||
$opposingName = '' === $this->action->action_value ? $destAccount->name : $this->action->action_value;
|
||||
$opposingName = '' === $actionValue ? $destAccount->name : $actionValue;
|
||||
// we check all possible source account types if one exists:
|
||||
$validTypes = config('firefly.expected_source_types.source.Deposit');
|
||||
$opposingAccount = $repository->findByName($opposingName, $validTypes);
|
||||
@@ -147,7 +149,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
$opposingAccount = $factory->findOrCreate($opposingName, AccountType::REVENUE);
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('ConvertToDeposit. Action value is "%s", new opposing name is "%s"', $this->action->action_value, $opposingAccount->name));
|
||||
app('log')->debug(sprintf('ConvertToDeposit. Action value is "%s", new opposing name is "%s"', $actionValue, $opposingAccount->name));
|
||||
|
||||
// update the source transaction and put in the new revenue ID.
|
||||
\DB::table('transactions')
|
||||
@@ -211,7 +213,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function convertTransferArray(TransactionJournal $journal): bool
|
||||
private function convertTransferArray(TransactionJournal $journal, string $actionValue = ''): bool
|
||||
{
|
||||
$user = $journal->user;
|
||||
|
||||
@@ -227,7 +229,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
|
||||
// get the action value, or use the original source name in case the action value is empty:
|
||||
// this becomes a new or existing (revenue) account, which is the source of the new deposit.
|
||||
$opposingName = '' === $this->action->action_value ? $sourceAccount->name : $this->action->action_value;
|
||||
$opposingName = '' === $actionValue ? $sourceAccount->name : $actionValue;
|
||||
// we check all possible source account types if one exists:
|
||||
$validTypes = config('firefly.expected_source_types.source.Deposit');
|
||||
$opposingAccount = $repository->findByName($opposingName, $validTypes);
|
||||
@@ -235,7 +237,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
$opposingAccount = $factory->findOrCreate($opposingName, AccountType::REVENUE);
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('ConvertToDeposit. Action value is "%s", revenue name is "%s"', $this->action->action_value, $opposingAccount->name));
|
||||
app('log')->debug(sprintf('ConvertToDeposit. Action value is "%s", revenue name is "%s"', $actionValue, $opposingAccount->name));
|
||||
|
||||
// update source transaction(s) to be revenue account
|
||||
\DB::table('transactions')
|
||||
|
||||
@@ -55,6 +55,8 @@ class ConvertToTransfer implements ActionInterface
|
||||
*/
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$accountName = $this->action->getValue($journal);
|
||||
|
||||
// make object from array (so the data is fresh).
|
||||
/** @var null|TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
@@ -102,7 +104,7 @@ class ConvertToTransfer implements ActionInterface
|
||||
$expectedType = $this->getDestinationType($journalId);
|
||||
// Deposit? Replace source with account with same type as destination.
|
||||
}
|
||||
$opposing = $repository->findByName($this->action->action_value, [$expectedType]);
|
||||
$opposing = $repository->findByName($accountName, [$expectedType]);
|
||||
|
||||
if (null === $opposing) {
|
||||
app('log')->error(
|
||||
@@ -110,11 +112,11 @@ class ConvertToTransfer implements ActionInterface
|
||||
'Journal #%d cannot be converted because no valid %s account with name "%s" exists (rule #%d).',
|
||||
$expectedType,
|
||||
$journalId,
|
||||
$this->action->action_value,
|
||||
$accountName,
|
||||
$this->action->rule_id
|
||||
)
|
||||
);
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.no_valid_opposing', ['name' => $this->action->action_value])));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.no_valid_opposing', ['name' => $accountName])));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$actionValue = $this->action->getValue($journal);
|
||||
|
||||
// make object from array (so the data is fresh).
|
||||
/** @var null|TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
@@ -85,7 +87,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
app('log')->debug('Going to transform a deposit to a withdrawal.');
|
||||
|
||||
try {
|
||||
$res = $this->convertDepositArray($object);
|
||||
$res = $this->convertDepositArray($object, $actionValue);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->debug('Could not convert transfer to deposit.');
|
||||
app('log')->error($e->getMessage());
|
||||
@@ -101,7 +103,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
app('log')->debug('Going to transform a transfer to a withdrawal.');
|
||||
|
||||
try {
|
||||
$res = $this->convertTransferArray($object);
|
||||
$res = $this->convertTransferArray($object, $actionValue);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->debug('Could not convert transfer to deposit.');
|
||||
app('log')->error($e->getMessage());
|
||||
@@ -117,7 +119,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function convertDepositArray(TransactionJournal $journal): bool
|
||||
private function convertDepositArray(TransactionJournal $journal, string $actionValue = ''): bool
|
||||
{
|
||||
$user = $journal->user;
|
||||
|
||||
@@ -133,7 +135,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
|
||||
// get the action value, or use the original source name in case the action value is empty:
|
||||
// this becomes a new or existing (expense) account, which is the destination of the new withdrawal.
|
||||
$opposingName = '' === $this->action->action_value ? $sourceAccount->name : $this->action->action_value;
|
||||
$opposingName = '' === $actionValue ? $sourceAccount->name : $actionValue;
|
||||
// we check all possible source account types if one exists:
|
||||
$validTypes = config('firefly.expected_source_types.destination.Withdrawal');
|
||||
$opposingAccount = $repository->findByName($opposingName, $validTypes);
|
||||
@@ -141,7 +143,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
$opposingAccount = $factory->findOrCreate($opposingName, AccountType::EXPENSE);
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('ConvertToWithdrawal. Action value is "%s", expense name is "%s"', $this->action->action_value, $opposingName));
|
||||
app('log')->debug(sprintf('ConvertToWithdrawal. Action value is "%s", expense name is "%s"', $actionValue, $opposingName));
|
||||
|
||||
// update source transaction(s) to be the original destination account
|
||||
\DB::table('transactions')
|
||||
@@ -203,7 +205,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function convertTransferArray(TransactionJournal $journal): bool
|
||||
private function convertTransferArray(TransactionJournal $journal, string $actionValue = ''): bool
|
||||
{
|
||||
// find or create expense account.
|
||||
$user = $journal->user;
|
||||
@@ -219,7 +221,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
|
||||
// get the action value, or use the original source name in case the action value is empty:
|
||||
// this becomes a new or existing (expense) account, which is the destination of the new withdrawal.
|
||||
$opposingName = '' === $this->action->action_value ? $destAccount->name : $this->action->action_value;
|
||||
$opposingName = '' === $actionValue ? $destAccount->name : $actionValue;
|
||||
// we check all possible source account types if one exists:
|
||||
$validTypes = config('firefly.expected_source_types.destination.Withdrawal');
|
||||
$opposingAccount = $repository->findByName($opposingName, $validTypes);
|
||||
@@ -227,7 +229,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
$opposingAccount = $factory->findOrCreate($opposingName, AccountType::EXPENSE);
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('ConvertToWithdrawal. Action value is "%s", destination name is "%s"', $this->action->action_value, $opposingName));
|
||||
app('log')->debug(sprintf('ConvertToWithdrawal. Action value is "%s", destination name is "%s"', $actionValue, $opposingName));
|
||||
|
||||
// update destination transaction(s) to be new expense account.
|
||||
\DB::table('transactions')
|
||||
|
||||
@@ -54,7 +54,7 @@ class LinkToBill implements ActionInterface
|
||||
/** @var BillRepositoryInterface $repository */
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
$repository->setUser($user);
|
||||
$billName = (string)$this->action->action_value;
|
||||
$billName = $this->action->getValue($journal);
|
||||
$bill = $repository->findByName($billName);
|
||||
|
||||
if (null !== $bill && TransactionType::WITHDRAWAL === $journal['transaction_type_type']) {
|
||||
|
||||
@@ -32,6 +32,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
|
||||
/**
|
||||
* Class MoveDescriptionToNotes
|
||||
* TODO Can be replaced (and migrated) to action "set notes" with a prefilled expression
|
||||
*/
|
||||
class MoveDescriptionToNotes implements ActionInterface
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user