mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-08-17 03:14:34 +00:00
Compare commits
54 Commits
develop-20
...
v6.1.10
Author | SHA1 | Date | |
---|---|---|---|
|
90d58ec8fa | ||
|
e92dd7f464 | ||
|
3bdf9eeed2 | ||
|
558ac7b0da | ||
|
9d0488ffbc | ||
|
d7fa8b283e | ||
|
a0097bd613 | ||
|
ffc2156e5f | ||
|
e0a89bb5fe | ||
|
647179cd3c | ||
|
5106ccdbd7 | ||
|
7103098fe7 | ||
|
f8072f0bfc | ||
|
96ac3a95c8 | ||
|
cd713dc40f | ||
|
d9fba39d80 | ||
|
2564470197 | ||
|
9222c82af0 | ||
|
243f283bfd | ||
|
5b60aaecc0 | ||
|
20a4caec60 | ||
|
99cc096b71 | ||
|
5626d1c56d | ||
|
68c9c4ec3c | ||
|
f9d4a43e05 | ||
|
92e7f344e0 | ||
|
89ce2838d5 | ||
|
356b217692 | ||
|
950e39b753 | ||
|
8f14979717 | ||
|
aa2afd162e | ||
|
fe33352ec1 | ||
|
65c5249815 | ||
|
b1afaea1aa | ||
|
997dc3814b | ||
|
b37b5b86d4 | ||
|
b13a4e1016 | ||
|
7897ebc4d5 | ||
|
ac17b82d85 | ||
|
1b1712d998 | ||
|
a2c0d9f7d0 | ||
|
5b68b25c85 | ||
|
d3a215b575 | ||
|
5c352a0d3e | ||
|
fded058ea6 | ||
|
99f041b114 | ||
|
283b594995 | ||
|
723aa65e7a | ||
|
64d315ad51 | ||
|
d0844356cb | ||
|
ba8d65835a | ||
|
fa3343f437 | ||
|
c5b8a951d2 | ||
|
20b1fc05cb |
@@ -35,28 +35,32 @@ $finder = PhpCsFixer\Finder::create()
|
||||
|
||||
|
||||
$config = new PhpCsFixer\Config();
|
||||
return $config->setRules([
|
||||
'no_unused_imports' => true,
|
||||
'@PhpCsFixer' => true,
|
||||
return $config->setRules(
|
||||
[
|
||||
// rule sets
|
||||
'@PHP83Migration' => true,
|
||||
'@PhpCsFixer' => true,
|
||||
'@PhpCsFixer:risky' => true,
|
||||
'@PSR12' => true,
|
||||
'@PSR12:risky' => true,
|
||||
'declare_strict_types' => true,
|
||||
'strict_param' => true,
|
||||
'comment_to_phpdoc' => false, // breaks phpstan lines in combination with PHPStorm.
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'no_unused_imports' => true,
|
||||
'single_space_around_construct' => true,
|
||||
'statement_indentation' => true,
|
||||
'void_return' => true,
|
||||
|
||||
// disabled rules
|
||||
'native_function_invocation' => false, // annoying
|
||||
'php_unit_data_provider_name' => false, // bloody annoying long test names
|
||||
'static_lambda' => false, // breaks the Response macro for API's.
|
||||
'phpdoc_summary' => false, // annoying.
|
||||
'single_space_around_construct' => [
|
||||
'constructs_followed_by_a_single_space' => [
|
||||
'protected',
|
||||
],
|
||||
],
|
||||
'statement_indentation' => true,
|
||||
'comment_to_phpdoc' => false, // breaks phpstan lines in combination with PHPStorm.
|
||||
'type_declaration_spaces' => false,
|
||||
'cast_spaces' => false,
|
||||
|
||||
// complex rules
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'binary_operator_spaces' => [
|
||||
'default' => 'at_least_single_space',
|
||||
'operators' => [
|
||||
@@ -65,6 +69,5 @@ return $config->setRules([
|
||||
'??=' => 'align_single_space_minimal_by_scope',
|
||||
],
|
||||
],
|
||||
'void_return' => true,
|
||||
])
|
||||
->setFinder($finder);
|
||||
|
161
.ci/php-cs-fixer/composer.lock
generated
161
.ci/php-cs-fixer/composer.lock
generated
@@ -226,16 +226,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.49.0",
|
||||
"version": "v3.51.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "8742f7aa6f72a399688b65e4f58992c2d4681fc2"
|
||||
"reference": "127fa74f010da99053e3f5b62672615b72dd6efd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/8742f7aa6f72a399688b65e4f58992c2d4681fc2",
|
||||
"reference": "8742f7aa6f72a399688b65e4f58992c2d4681fc2",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/127fa74f010da99053e3f5b62672615b72dd6efd",
|
||||
"reference": "127fa74f010da99053e3f5b62672615b72dd6efd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -245,7 +245,7 @@
|
||||
"ext-json": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"sebastian/diff": "^4.0 || ^5.0",
|
||||
"sebastian/diff": "^4.0 || ^5.0 || ^6.0",
|
||||
"symfony/console": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/filesystem": "^5.4 || ^6.0 || ^7.0",
|
||||
@@ -266,7 +266,8 @@
|
||||
"php-cs-fixer/accessible-object": "^1.1",
|
||||
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4",
|
||||
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4",
|
||||
"phpunit/phpunit": "^9.6 || ^10.5.5",
|
||||
"phpunit/phpunit": "^9.6 || ^10.5.5 || ^11.0.2",
|
||||
"symfony/var-dumper": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/yaml": "^5.4 || ^6.0 || ^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
@@ -305,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.49.0"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.51.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -313,7 +314,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-02T00:41:40+00:00"
|
||||
"time": "2024-02-28T19:50:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -470,29 +471,29 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/diff",
|
||||
"version": "5.1.0",
|
||||
"version": "6.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/diff.git",
|
||||
"reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f"
|
||||
"reference": "ab83243ecc233de5655b76f577711de9f842e712"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
|
||||
"reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ab83243ecc233de5655b76f577711de9f842e712",
|
||||
"reference": "ab83243ecc233de5655b76f577711de9f842e712",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"phpunit/phpunit": "^11.0",
|
||||
"symfony/process": "^4.2 || ^5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "5.1-dev"
|
||||
"dev-main": "6.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -525,7 +526,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/diff/issues",
|
||||
"security": "https://github.com/sebastianbergmann/diff/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/5.1.0"
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/6.0.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -533,20 +534,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-22T10:55:06+00:00"
|
||||
"time": "2024-03-02T07:30:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.0.3",
|
||||
"version": "v7.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456"
|
||||
"reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/c5010d50f1ee4b25cfa0201d9915cf1b14071456",
|
||||
"reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/6b099f3306f7c9c2d2786ed736d0026b2903205f",
|
||||
"reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -610,7 +611,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.0.3"
|
||||
"source": "https://github.com/symfony/console/tree/v7.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -626,7 +627,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-23T15:02:46+00:00"
|
||||
"time": "2024-02-22T20:27:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -1047,16 +1048,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
||||
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1070,9 +1071,6 @@
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
@@ -1109,7 +1107,7 @@
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1125,20 +1123,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-grapheme",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
||||
"reference": "875e90aeea2777b6f135677f618529449334a612"
|
||||
"reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612",
|
||||
"reference": "875e90aeea2777b6f135677f618529449334a612",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f",
|
||||
"reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1149,9 +1147,6 @@
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
@@ -1190,7 +1185,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1206,20 +1201,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
|
||||
"reference": "bc45c394692b948b4d383a08d7753968bed9a83d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
|
||||
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d",
|
||||
"reference": "bc45c394692b948b4d383a08d7753968bed9a83d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1230,9 +1225,6 @@
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
@@ -1274,7 +1266,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1290,20 +1282,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "42292d99c55abe617799667f454222c54c60e229"
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
|
||||
"reference": "42292d99c55abe617799667f454222c54c60e229",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1317,9 +1309,6 @@
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
@@ -1357,7 +1346,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1373,20 +1362,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-28T09:04:16+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
||||
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1394,9 +1383,6 @@
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
@@ -1440,7 +1426,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1456,20 +1442,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php81.git",
|
||||
"reference": "7581cd600fa9fd681b797d00b02f068e2f13263b"
|
||||
"reference": "c565ad1e63f30e7477fc40738343c62b40bc672d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b",
|
||||
"reference": "7581cd600fa9fd681b797d00b02f068e2f13263b",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d",
|
||||
"reference": "c565ad1e63f30e7477fc40738343c62b40bc672d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1477,9 +1463,6 @@
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
@@ -1519,7 +1502,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1535,20 +1518,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.0.3",
|
||||
"version": "v7.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "937a195147e0c27b2759ade834169ed006d0bc74"
|
||||
"reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/937a195147e0c27b2759ade834169ed006d0bc74",
|
||||
"reference": "937a195147e0c27b2759ade834169ed006d0bc74",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/0e7727191c3b71ebec6d529fa0e50a01ca5679e9",
|
||||
"reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1580,7 +1563,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v7.0.3"
|
||||
"source": "https://github.com/symfony/process/tree/v7.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1596,7 +1579,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-23T15:02:46+00:00"
|
||||
"time": "2024-02-22T20:27:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
@@ -1744,16 +1727,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v7.0.3",
|
||||
"version": "v7.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "524aac4a280b90a4420d8d6a040718d0586505ac"
|
||||
"reference": "f5832521b998b0bec40bee688ad5de98d4cf111b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/524aac4a280b90a4420d8d6a040718d0586505ac",
|
||||
"reference": "524aac4a280b90a4420d8d6a040718d0586505ac",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b",
|
||||
"reference": "f5832521b998b0bec40bee688ad5de98d4cf111b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1810,7 +1793,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v7.0.3"
|
||||
"source": "https://github.com/symfony/string/tree/v7.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1826,7 +1809,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-29T15:41:16+00:00"
|
||||
"time": "2024-02-01T13:17:36+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
66
.ci/phpmd/composer.lock
generated
66
.ci/phpmd/composer.lock
generated
@@ -395,16 +395,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/config",
|
||||
"version": "v7.0.3",
|
||||
"version": "v7.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/config.git",
|
||||
"reference": "86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16"
|
||||
"reference": "44deeba7233f08f383185ffa37dace3b3bc87364"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16",
|
||||
"reference": "86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/44deeba7233f08f383185ffa37dace3b3bc87364",
|
||||
"reference": "44deeba7233f08f383185ffa37dace3b3bc87364",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -450,7 +450,7 @@
|
||||
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/config/tree/v7.0.3"
|
||||
"source": "https://github.com/symfony/config/tree/v7.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -466,20 +466,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-30T08:34:29+00:00"
|
||||
"time": "2024-02-26T07:52:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/dependency-injection",
|
||||
"version": "v7.0.3",
|
||||
"version": "v7.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dependency-injection.git",
|
||||
"reference": "e915c6684b8e3ae90a4441f6823ebbb40edf0b92"
|
||||
"reference": "47f37af245df8457ea63409fc242b3cc825ce5eb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e915c6684b8e3ae90a4441f6823ebbb40edf0b92",
|
||||
"reference": "e915c6684b8e3ae90a4441f6823ebbb40edf0b92",
|
||||
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/47f37af245df8457ea63409fc242b3cc825ce5eb",
|
||||
"reference": "47f37af245df8457ea63409fc242b3cc825ce5eb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -530,7 +530,7 @@
|
||||
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/dependency-injection/tree/v7.0.3"
|
||||
"source": "https://github.com/symfony/dependency-injection/tree/v7.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -546,7 +546,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-30T08:34:29+00:00"
|
||||
"time": "2024-02-22T20:27:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -680,16 +680,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
||||
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -703,9 +703,6 @@
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
@@ -742,7 +739,7 @@
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -758,20 +755,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "42292d99c55abe617799667f454222c54c60e229"
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
|
||||
"reference": "42292d99c55abe617799667f454222c54c60e229",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -785,9 +782,6 @@
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
@@ -825,7 +819,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -841,7 +835,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-28T09:04:16+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
@@ -927,16 +921,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-exporter",
|
||||
"version": "v7.0.3",
|
||||
"version": "v7.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-exporter.git",
|
||||
"reference": "1fb79308cb5fc2b44bff6e8af10a5af6812e05b8"
|
||||
"reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/1fb79308cb5fc2b44bff6e8af10a5af6812e05b8",
|
||||
"reference": "1fb79308cb5fc2b44bff6e8af10a5af6812e05b8",
|
||||
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41",
|
||||
"reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -981,7 +975,7 @@
|
||||
"serialize"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-exporter/tree/v7.0.3"
|
||||
"source": "https://github.com/symfony/var-exporter/tree/v7.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -997,7 +991,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-23T15:02:46+00:00"
|
||||
"time": "2024-02-26T10:35:24+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
|
14
.env.example
14
.env.example
@@ -1,6 +1,6 @@
|
||||
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
|
||||
# Never set it to "testing".
|
||||
APP_ENV=local
|
||||
APP_ENV=production
|
||||
|
||||
# Set to true if you want to see debug information in error screens.
|
||||
APP_DEBUG=false
|
||||
@@ -332,15 +332,7 @@ DEMO_PASSWORD=
|
||||
FIREFLY_III_LAYOUT=v1
|
||||
|
||||
#
|
||||
# If you have trouble configuring your Firefly III installation, DON'T BOTHER setting this variable.
|
||||
# It won't work. It doesn't do ANYTHING. Don't believe the lies you read online. I'm not joking.
|
||||
# This configuration value WILL NOT HELP.
|
||||
#
|
||||
# Notable exception to this rule is Synology, which, according to some users, will use APP_URL to rewrite stuff.
|
||||
#
|
||||
# This variable is ONLY used in some of the emails Firefly III sends around. Nowhere else.
|
||||
# So when configuring anything WEB related this variable doesn't do anything. Nothing
|
||||
#
|
||||
# If you're stuck I understand you get desperate but look SOMEWHERE ELSE.
|
||||
# Please make sure this URL matches the external URL of your Firefly III installation.
|
||||
# It is used to validate specific requests and to generate URLs in emails.
|
||||
#
|
||||
APP_URL=http://localhost
|
||||
|
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
||||
CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
|
||||
- name: Cleanup translations
|
||||
id: cleanup-transactions
|
||||
uses: JC5/firefly-iii-dev@v32
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
with:
|
||||
action: 'ff3:crowdin-warning'
|
||||
output: ''
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
GH_TOKEN: ''
|
||||
- name: Cleanup changelog
|
||||
id: cleanup-changelog
|
||||
uses: JC5/firefly-iii-dev@v32
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
with:
|
||||
action: 'ff3:changelog'
|
||||
output: ''
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.CHANGELOG_TOKEN }}
|
||||
- name: Extract changelog
|
||||
id: extract-changelog
|
||||
uses: JC5/firefly-iii-dev@v32
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
with:
|
||||
action: 'ff3:extract-changelog'
|
||||
output: 'output'
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
GH_TOKEN: ""
|
||||
- name: Replace version
|
||||
id: replace-version
|
||||
uses: JC5/firefly-iii-dev@v32
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
with:
|
||||
action: 'ff3:version'
|
||||
output: ''
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
- name: Generate JSON v1
|
||||
id: json-v1
|
||||
uses: JC5/firefly-iii-dev@v32
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
with:
|
||||
action: 'ff3:json-translations v1'
|
||||
output: ''
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
GH_TOKEN: ''
|
||||
- name: Generate JSON v2
|
||||
id: json-v2
|
||||
uses: JC5/firefly-iii-dev@v32
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
with:
|
||||
action: 'ff3:json-translations v2'
|
||||
output: ''
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
GH_TOKEN: ''
|
||||
- name: Code cleanup
|
||||
id: code-cleanup
|
||||
uses: JC5/firefly-iii-dev@v32
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
with:
|
||||
action: 'ff3:code'
|
||||
output: ''
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
npm run build
|
||||
- name: Build old JS
|
||||
id: old-js
|
||||
uses: JC5/firefly-iii-dev@v32
|
||||
uses: JC5/firefly-iii-dev@v34
|
||||
with:
|
||||
action: 'ff3:old-js'
|
||||
output: ''
|
||||
|
@@ -76,38 +76,6 @@ abstract class Controller extends BaseController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to help build URL's.
|
||||
*/
|
||||
final protected function buildParams(): string
|
||||
{
|
||||
$return = '?';
|
||||
$params = [];
|
||||
foreach ($this->parameters as $key => $value) {
|
||||
if ('page' === $key) {
|
||||
continue;
|
||||
}
|
||||
if ($value instanceof Carbon) {
|
||||
$params[$key] = $value->format('Y-m-d');
|
||||
|
||||
continue;
|
||||
}
|
||||
$params[$key] = $value;
|
||||
}
|
||||
|
||||
return $return.http_build_query($params);
|
||||
}
|
||||
|
||||
final protected function getManager(): Manager
|
||||
{
|
||||
// create some objects:
|
||||
$manager = new Manager();
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v1';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
return $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to grab all parameters from the URL.
|
||||
*/
|
||||
@@ -216,4 +184,36 @@ abstract class Controller extends BaseController
|
||||
|
||||
return $bag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to help build URL's.
|
||||
*/
|
||||
final protected function buildParams(): string
|
||||
{
|
||||
$return = '?';
|
||||
$params = [];
|
||||
foreach ($this->parameters as $key => $value) {
|
||||
if ('page' === $key) {
|
||||
continue;
|
||||
}
|
||||
if ($value instanceof Carbon) {
|
||||
$params[$key] = $value->format('Y-m-d');
|
||||
|
||||
continue;
|
||||
}
|
||||
$params[$key] = $value;
|
||||
}
|
||||
|
||||
return $return.http_build_query($params);
|
||||
}
|
||||
|
||||
final protected function getManager(): Manager
|
||||
{
|
||||
// create some objects:
|
||||
$manager = new Manager();
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v1';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
return $manager;
|
||||
}
|
||||
}
|
||||
|
@@ -68,7 +68,7 @@ class DestroyController extends Controller
|
||||
$allExceptAssets = [AccountType::BENEFICIARY, AccountType::CASH, AccountType::CREDITCARD, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::IMPORT, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, AccountType::RECONCILIATION, AccountType::REVENUE];
|
||||
$all = [AccountType::ASSET, AccountType::BENEFICIARY, AccountType::CASH, AccountType::CREDITCARD, AccountType::DEBT, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::IMPORT, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::RECONCILIATION];
|
||||
$liabilities = [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD];
|
||||
$transactions = [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::RECONCILIATION, TransactionType::OPENING_BALANCE];
|
||||
$transactions = [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::RECONCILIATION];
|
||||
|
||||
match ($objects) {
|
||||
'budgets' => $this->destroyBudgets(),
|
||||
|
@@ -68,6 +68,32 @@ class ExportController extends Controller
|
||||
return $this->returnExport('accounts');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function returnExport(string $key): LaravelResponse
|
||||
{
|
||||
$date = date('Y-m-d-H-i-s');
|
||||
$fileName = sprintf('%s-export-%s.csv', $date, $key);
|
||||
$data = $this->exporter->export();
|
||||
|
||||
/** @var LaravelResponse $response */
|
||||
$response = response($data[$key]);
|
||||
$response
|
||||
->header('Content-Description', 'File Transfer')
|
||||
->header('Content-Type', 'application/octet-stream')
|
||||
->header('Content-Disposition', 'attachment; filename='.$fileName)
|
||||
->header('Content-Transfer-Encoding', 'binary')
|
||||
->header('Connection', 'Keep-Alive')
|
||||
->header('Expires', '0')
|
||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||
->header('Pragma', 'public')
|
||||
->header('Content-Length', (string)strlen($data[$key]))
|
||||
;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/data/exportBills
|
||||
@@ -189,30 +215,4 @@ class ExportController extends Controller
|
||||
|
||||
return $this->returnExport('transactions');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function returnExport(string $key): LaravelResponse
|
||||
{
|
||||
$date = date('Y-m-d-H-i-s');
|
||||
$fileName = sprintf('%s-export-%s.csv', $date, $key);
|
||||
$data = $this->exporter->export();
|
||||
|
||||
/** @var LaravelResponse $response */
|
||||
$response = response($data[$key]);
|
||||
$response
|
||||
->header('Content-Description', 'File Transfer')
|
||||
->header('Content-Type', 'application/octet-stream')
|
||||
->header('Content-Disposition', 'attachment; filename='.$fileName)
|
||||
->header('Content-Transfer-Encoding', 'binary')
|
||||
->header('Connection', 'Keep-Alive')
|
||||
->header('Expires', '0')
|
||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||
->header('Pragma', 'public')
|
||||
->header('Content-Length', (string) strlen($data[$key]))
|
||||
;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
@@ -118,23 +118,6 @@ class BasicController extends Controller
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date is outside session range.
|
||||
*/
|
||||
protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
|
||||
{
|
||||
$result = false;
|
||||
if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) {
|
||||
$result = true;
|
||||
}
|
||||
// start and end in the past? use $end
|
||||
if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) {
|
||||
$result = true;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getBalanceInformation(Carbon $start, Carbon $end): array
|
||||
{
|
||||
// prep some arrays:
|
||||
@@ -380,4 +363,21 @@ class BasicController extends Controller
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date is outside session range.
|
||||
*/
|
||||
protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
|
||||
{
|
||||
$result = false;
|
||||
if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) {
|
||||
$result = true;
|
||||
}
|
||||
// start and end in the past? use $end
|
||||
if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) {
|
||||
$result = true;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@@ -88,6 +88,35 @@ class ConfigurationController extends Controller
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all config values.
|
||||
*/
|
||||
private function getDynamicConfiguration(): array
|
||||
{
|
||||
$isDemoSite = app('fireflyconfig')->get('is_demo_site');
|
||||
$updateCheck = app('fireflyconfig')->get('permission_update_check');
|
||||
$lastCheck = app('fireflyconfig')->get('last_update_check');
|
||||
$singleUser = app('fireflyconfig')->get('single_user_mode');
|
||||
|
||||
return [
|
||||
'is_demo_site' => $isDemoSite?->data,
|
||||
'permission_update_check' => null === $updateCheck ? null : (int)$updateCheck->data,
|
||||
'last_update_check' => null === $lastCheck ? null : (int)$lastCheck->data,
|
||||
'single_user_mode' => $singleUser?->data,
|
||||
];
|
||||
}
|
||||
|
||||
private function getStaticConfiguration(): array
|
||||
{
|
||||
$list = EitherConfigKey::$static;
|
||||
$return = [];
|
||||
foreach ($list as $key) {
|
||||
$return[$key] = config($key);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/configuration/getSingleConfiguration
|
||||
@@ -145,33 +174,4 @@ class ConfigurationController extends Controller
|
||||
|
||||
return response()->json(['data' => $data])->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all config values.
|
||||
*/
|
||||
private function getDynamicConfiguration(): array
|
||||
{
|
||||
$isDemoSite = app('fireflyconfig')->get('is_demo_site');
|
||||
$updateCheck = app('fireflyconfig')->get('permission_update_check');
|
||||
$lastCheck = app('fireflyconfig')->get('last_update_check');
|
||||
$singleUser = app('fireflyconfig')->get('single_user_mode');
|
||||
|
||||
return [
|
||||
'is_demo_site' => $isDemoSite?->data,
|
||||
'permission_update_check' => null === $updateCheck ? null : (int)$updateCheck->data,
|
||||
'last_update_check' => null === $lastCheck ? null : (int)$lastCheck->data,
|
||||
'single_user_mode' => $singleUser?->data,
|
||||
];
|
||||
}
|
||||
|
||||
private function getStaticConfiguration(): array
|
||||
{
|
||||
$list = EitherConfigKey::$static;
|
||||
$return = [];
|
||||
foreach ($list as $key) {
|
||||
$return[$key] = config($key);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -78,6 +78,25 @@ class GenericRequest extends FormRequest
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function parseAccounts(): void
|
||||
{
|
||||
if (0 !== $this->accounts->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('accounts');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $accountId) {
|
||||
$accountId = (int)$accountId;
|
||||
$account = $repository->find($accountId);
|
||||
if (null !== $account) {
|
||||
$this->accounts->push($account);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getBills(): Collection
|
||||
{
|
||||
$this->parseBills();
|
||||
@@ -85,6 +104,25 @@ class GenericRequest extends FormRequest
|
||||
return $this->bills;
|
||||
}
|
||||
|
||||
private function parseBills(): void
|
||||
{
|
||||
if (0 !== $this->bills->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('bills');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $billId) {
|
||||
$billId = (int)$billId;
|
||||
$bill = $repository->find($billId);
|
||||
if (null !== $bill) {
|
||||
$this->bills->push($bill);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getBudgets(): Collection
|
||||
{
|
||||
$this->parseBudgets();
|
||||
@@ -92,6 +130,25 @@ class GenericRequest extends FormRequest
|
||||
return $this->budgets;
|
||||
}
|
||||
|
||||
private function parseBudgets(): void
|
||||
{
|
||||
if (0 !== $this->budgets->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('budgets');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $budgetId) {
|
||||
$budgetId = (int)$budgetId;
|
||||
$budget = $repository->find($budgetId);
|
||||
if (null !== $budget) {
|
||||
$this->budgets->push($budget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getCategories(): Collection
|
||||
{
|
||||
$this->parseCategories();
|
||||
@@ -99,6 +156,25 @@ class GenericRequest extends FormRequest
|
||||
return $this->categories;
|
||||
}
|
||||
|
||||
private function parseCategories(): void
|
||||
{
|
||||
if (0 !== $this->categories->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(CategoryRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('categories');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $categoryId) {
|
||||
$categoryId = (int)$categoryId;
|
||||
$category = $repository->find($categoryId);
|
||||
if (null !== $category) {
|
||||
$this->categories->push($category);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getEnd(): Carbon
|
||||
{
|
||||
$date = $this->getCarbonDate('end');
|
||||
@@ -154,100 +230,6 @@ class GenericRequest extends FormRequest
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
// this is cheating, but it works to initialize the collections.
|
||||
$this->accounts = new Collection();
|
||||
$this->budgets = new Collection();
|
||||
$this->categories = new Collection();
|
||||
$this->bills = new Collection();
|
||||
$this->tags = new Collection();
|
||||
|
||||
return [
|
||||
'start' => 'required|date',
|
||||
'end' => 'required|date|after_or_equal:start',
|
||||
];
|
||||
}
|
||||
|
||||
private function parseAccounts(): void
|
||||
{
|
||||
if (0 !== $this->accounts->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('accounts');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $accountId) {
|
||||
$accountId = (int)$accountId;
|
||||
$account = $repository->find($accountId);
|
||||
if (null !== $account) {
|
||||
$this->accounts->push($account);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseBills(): void
|
||||
{
|
||||
if (0 !== $this->bills->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('bills');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $billId) {
|
||||
$billId = (int)$billId;
|
||||
$bill = $repository->find($billId);
|
||||
if (null !== $bill) {
|
||||
$this->bills->push($bill);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseBudgets(): void
|
||||
{
|
||||
if (0 !== $this->budgets->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('budgets');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $budgetId) {
|
||||
$budgetId = (int)$budgetId;
|
||||
$budget = $repository->find($budgetId);
|
||||
if (null !== $budget) {
|
||||
$this->budgets->push($budget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseCategories(): void
|
||||
{
|
||||
if (0 !== $this->categories->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(CategoryRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('categories');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $categoryId) {
|
||||
$categoryId = (int)$categoryId;
|
||||
$category = $repository->find($categoryId);
|
||||
if (null !== $category) {
|
||||
$this->categories->push($category);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseTags(): void
|
||||
{
|
||||
if (0 !== $this->tags->count()) {
|
||||
@@ -266,4 +248,22 @@ class GenericRequest extends FormRequest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
// this is cheating, but it works to initialize the collections.
|
||||
$this->accounts = new Collection();
|
||||
$this->budgets = new Collection();
|
||||
$this->categories = new Collection();
|
||||
$this->bills = new Collection();
|
||||
$this->tags = new Collection();
|
||||
|
||||
return [
|
||||
'start' => 'required|date',
|
||||
'end' => 'required|date|after_or_equal:start',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -73,6 +73,65 @@ class StoreRequest extends FormRequest
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transaction data as it is found in the submitted data. It's a complex method according to code
|
||||
* standards but it just has a lot of ??-statements because of the fields that may or may not exist.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// transaction data:
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
if (null === $transactions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$return[] = $this->getSingleTransactionData($transaction);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repetition data as it is found in the submitted data.
|
||||
*/
|
||||
private function getRepetitionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// repetition data:
|
||||
/** @var null|array $repetitions */
|
||||
$repetitions = $this->get('repetitions');
|
||||
if (null === $repetitions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $repetition */
|
||||
foreach ($repetitions as $repetition) {
|
||||
$current = [];
|
||||
if (array_key_exists('type', $repetition)) {
|
||||
$current['type'] = $repetition['type'];
|
||||
}
|
||||
if (array_key_exists('moment', $repetition)) {
|
||||
$current['moment'] = $repetition['moment'];
|
||||
}
|
||||
if (array_key_exists('skip', $repetition)) {
|
||||
$current['skip'] = (int)$repetition['skip'];
|
||||
}
|
||||
if (array_key_exists('weekend', $repetition)) {
|
||||
$current['weekend'] = (int)$repetition['weekend'];
|
||||
}
|
||||
|
||||
$return[] = $current;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -136,63 +195,4 @@ class StoreRequest extends FormRequest
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transaction data as it is found in the submitted data. It's a complex method according to code
|
||||
* standards but it just has a lot of ??-statements because of the fields that may or may not exist.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// transaction data:
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
if (null === $transactions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$return[] = $this->getSingleTransactionData($transaction);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repetition data as it is found in the submitted data.
|
||||
*/
|
||||
private function getRepetitionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// repetition data:
|
||||
/** @var null|array $repetitions */
|
||||
$repetitions = $this->get('repetitions');
|
||||
if (null === $repetitions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $repetition */
|
||||
foreach ($repetitions as $repetition) {
|
||||
$current = [];
|
||||
if (array_key_exists('type', $repetition)) {
|
||||
$current['type'] = $repetition['type'];
|
||||
}
|
||||
if (array_key_exists('moment', $repetition)) {
|
||||
$current['moment'] = $repetition['moment'];
|
||||
}
|
||||
if (array_key_exists('skip', $repetition)) {
|
||||
$current['skip'] = (int)$repetition['skip'];
|
||||
}
|
||||
if (array_key_exists('weekend', $repetition)) {
|
||||
$current['weekend'] = (int)$repetition['weekend'];
|
||||
}
|
||||
|
||||
$return[] = $current;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -78,6 +78,70 @@ class UpdateRequest extends FormRequest
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repetition data as it is found in the submitted data.
|
||||
*/
|
||||
private function getRepetitionData(): ?array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// repetition data:
|
||||
/** @var null|array $repetitions */
|
||||
$repetitions = $this->get('repetitions');
|
||||
if (null === $repetitions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var array $repetition */
|
||||
foreach ($repetitions as $repetition) {
|
||||
$current = [];
|
||||
if (array_key_exists('type', $repetition)) {
|
||||
$current['type'] = $repetition['type'];
|
||||
}
|
||||
|
||||
if (array_key_exists('moment', $repetition)) {
|
||||
$current['moment'] = (string)$repetition['moment'];
|
||||
}
|
||||
|
||||
if (array_key_exists('skip', $repetition)) {
|
||||
$current['skip'] = (int)$repetition['skip'];
|
||||
}
|
||||
|
||||
if (array_key_exists('weekend', $repetition)) {
|
||||
$current['weekend'] = (int)$repetition['weekend'];
|
||||
}
|
||||
$return[] = $current;
|
||||
}
|
||||
if (0 === count($return)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transaction data as it is found in the submitted data. It's a complex method according to code
|
||||
* standards but it just has a lot of ??-statements because of the fields that may or may not exist.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// transaction data:
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
if (null === $transactions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$return[] = $this->getSingleTransactionData($transaction);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -146,68 +210,4 @@ class UpdateRequest extends FormRequest
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repetition data as it is found in the submitted data.
|
||||
*/
|
||||
private function getRepetitionData(): ?array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// repetition data:
|
||||
/** @var null|array $repetitions */
|
||||
$repetitions = $this->get('repetitions');
|
||||
if (null === $repetitions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var array $repetition */
|
||||
foreach ($repetitions as $repetition) {
|
||||
$current = [];
|
||||
if (array_key_exists('type', $repetition)) {
|
||||
$current['type'] = $repetition['type'];
|
||||
}
|
||||
|
||||
if (array_key_exists('moment', $repetition)) {
|
||||
$current['moment'] = (string) $repetition['moment'];
|
||||
}
|
||||
|
||||
if (array_key_exists('skip', $repetition)) {
|
||||
$current['skip'] = (int) $repetition['skip'];
|
||||
}
|
||||
|
||||
if (array_key_exists('weekend', $repetition)) {
|
||||
$current['weekend'] = (int) $repetition['weekend'];
|
||||
}
|
||||
$return[] = $current;
|
||||
}
|
||||
if (0 === count($return)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transaction data as it is found in the submitted data. It's a complex method according to code
|
||||
* standards but it just has a lot of ??-statements because of the fields that may or may not exist.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// transaction data:
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
if (null === $transactions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$return[] = $this->getSingleTransactionData($transaction);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -64,6 +64,42 @@ class StoreRequest extends FormRequest
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getRuleTriggers(): array
|
||||
{
|
||||
$triggers = $this->get('triggers');
|
||||
$return = [];
|
||||
if (is_array($triggers)) {
|
||||
foreach ($triggers as $trigger) {
|
||||
$return[] = [
|
||||
'type' => $trigger['type'],
|
||||
'value' => $trigger['value'],
|
||||
'active' => $this->convertBoolean((string)($trigger['active'] ?? 'true')),
|
||||
'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleActions(): array
|
||||
{
|
||||
$actions = $this->get('actions');
|
||||
$return = [];
|
||||
if (is_array($actions)) {
|
||||
foreach ($actions as $action) {
|
||||
$return[] = [
|
||||
'type' => $action['type'],
|
||||
'value' => $action['value'],
|
||||
'active' => $this->convertBoolean((string)($action['active'] ?? 'true')),
|
||||
'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -197,40 +233,4 @@ class StoreRequest extends FormRequest
|
||||
$validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getRuleTriggers(): array
|
||||
{
|
||||
$triggers = $this->get('triggers');
|
||||
$return = [];
|
||||
if (is_array($triggers)) {
|
||||
foreach ($triggers as $trigger) {
|
||||
$return[] = [
|
||||
'type' => $trigger['type'],
|
||||
'value' => $trigger['value'],
|
||||
'active' => $this->convertBoolean((string)($trigger['active'] ?? 'true')),
|
||||
'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleActions(): array
|
||||
{
|
||||
$actions = $this->get('actions');
|
||||
$return = [];
|
||||
if (is_array($actions)) {
|
||||
foreach ($actions as $action) {
|
||||
$return[] = [
|
||||
'type' => $action['type'],
|
||||
'value' => $action['value'],
|
||||
'active' => $this->convertBoolean((string)($action['active'] ?? 'true')),
|
||||
'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -47,16 +47,6 @@ class TestRequest extends FormRequest
|
||||
];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date',
|
||||
'end' => 'date|after_or_equal:start',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
|
||||
private function getPage(): int
|
||||
{
|
||||
return 0 === (int)$this->query('page') ? 1 : (int)$this->query('page');
|
||||
@@ -81,4 +71,14 @@ class TestRequest extends FormRequest
|
||||
{
|
||||
return $this->get('accounts');
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date',
|
||||
'end' => 'date|after_or_equal:start',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -46,16 +46,6 @@ class TriggerRequest extends FormRequest
|
||||
];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date',
|
||||
'end' => 'date|after_or_equal:start',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
{
|
||||
$value = $this->query($field);
|
||||
@@ -75,4 +65,14 @@ class TriggerRequest extends FormRequest
|
||||
{
|
||||
return $this->get('accounts') ?? [];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date',
|
||||
'end' => 'date|after_or_equal:start',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -70,6 +70,50 @@ class UpdateRequest extends FormRequest
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleTriggers(): ?array
|
||||
{
|
||||
if (!$this->has('triggers')) {
|
||||
return null;
|
||||
}
|
||||
$triggers = $this->get('triggers');
|
||||
$return = [];
|
||||
if (is_array($triggers)) {
|
||||
foreach ($triggers as $trigger) {
|
||||
$active = array_key_exists('active', $trigger) ? $trigger['active'] : true;
|
||||
$stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false;
|
||||
$return[] = [
|
||||
'type' => $trigger['type'],
|
||||
'value' => $trigger['value'],
|
||||
'active' => $active,
|
||||
'stop_processing' => $stopProcessing,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleActions(): ?array
|
||||
{
|
||||
if (!$this->has('actions')) {
|
||||
return null;
|
||||
}
|
||||
$actions = $this->get('actions');
|
||||
$return = [];
|
||||
if (is_array($actions)) {
|
||||
foreach ($actions as $action) {
|
||||
$return[] = [
|
||||
'type' => $action['type'],
|
||||
'value' => $action['value'],
|
||||
'active' => $this->convertBoolean((string)($action['active'] ?? 'false')),
|
||||
'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -204,48 +248,4 @@ class UpdateRequest extends FormRequest
|
||||
$validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getRuleTriggers(): ?array
|
||||
{
|
||||
if (!$this->has('triggers')) {
|
||||
return null;
|
||||
}
|
||||
$triggers = $this->get('triggers');
|
||||
$return = [];
|
||||
if (is_array($triggers)) {
|
||||
foreach ($triggers as $trigger) {
|
||||
$active = array_key_exists('active', $trigger) ? $trigger['active'] : true;
|
||||
$stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false;
|
||||
$return[] = [
|
||||
'type' => $trigger['type'],
|
||||
'value' => $trigger['value'],
|
||||
'active' => $active,
|
||||
'stop_processing' => $stopProcessing,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleActions(): ?array
|
||||
{
|
||||
if (!$this->has('actions')) {
|
||||
return null;
|
||||
}
|
||||
$actions = $this->get('actions');
|
||||
$return = [];
|
||||
if (is_array($actions)) {
|
||||
foreach ($actions as $action) {
|
||||
$return[] = [
|
||||
'type' => $action['type'],
|
||||
'value' => $action['value'],
|
||||
'active' => $this->convertBoolean((string)($action['active'] ?? 'false')),
|
||||
'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -46,16 +46,6 @@ class TestRequest extends FormRequest
|
||||
];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date',
|
||||
'end' => 'date|after_or_equal:start',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
{
|
||||
$value = $this->query($field);
|
||||
@@ -75,4 +65,14 @@ class TestRequest extends FormRequest
|
||||
{
|
||||
return $this->get('accounts');
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date',
|
||||
'end' => 'date|after_or_equal:start',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -46,14 +46,6 @@ class TriggerRequest extends FormRequest
|
||||
];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date',
|
||||
'end' => 'date|after_or_equal:start',
|
||||
];
|
||||
}
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
{
|
||||
$value = $this->query($field);
|
||||
@@ -77,4 +69,12 @@ class TriggerRequest extends FormRequest
|
||||
|
||||
return $this->get('accounts');
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date',
|
||||
'end' => 'date|after_or_equal:start',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -69,6 +69,101 @@ class StoreRequest extends FormRequest
|
||||
// TODO include location and ability to process it.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/**
|
||||
* @var array $transaction
|
||||
*/
|
||||
foreach ($this->get('transactions') as $transaction) {
|
||||
$object = new NullArrayObject($transaction);
|
||||
$return[] = [
|
||||
'type' => $this->clearString($object['type']),
|
||||
'date' => $this->dateFromValue($object['date']),
|
||||
'order' => $this->integerFromValue((string)$object['order']),
|
||||
|
||||
'currency_id' => $this->integerFromValue((string)$object['currency_id']),
|
||||
'currency_code' => $this->clearString((string)$object['currency_code']),
|
||||
|
||||
// foreign currency info:
|
||||
'foreign_currency_id' => $this->integerFromValue((string)$object['foreign_currency_id']),
|
||||
'foreign_currency_code' => $this->clearString((string)$object['foreign_currency_code']),
|
||||
|
||||
// amount and foreign amount. Cannot be 0.
|
||||
'amount' => $this->clearString((string)$object['amount']),
|
||||
'foreign_amount' => $this->clearString((string)$object['foreign_amount']),
|
||||
|
||||
// description.
|
||||
'description' => $this->clearString($object['description']),
|
||||
|
||||
// source of transaction. If everything is null, assume cash account.
|
||||
'source_id' => $this->integerFromValue((string)$object['source_id']),
|
||||
'source_name' => $this->clearString((string)$object['source_name']),
|
||||
'source_iban' => $this->clearString((string)$object['source_iban']),
|
||||
'source_number' => $this->clearString((string)$object['source_number']),
|
||||
'source_bic' => $this->clearString((string)$object['source_bic']),
|
||||
|
||||
// destination of transaction. If everything is null, assume cash account.
|
||||
'destination_id' => $this->integerFromValue((string)$object['destination_id']),
|
||||
'destination_name' => $this->clearString((string)$object['destination_name']),
|
||||
'destination_iban' => $this->clearString((string)$object['destination_iban']),
|
||||
'destination_number' => $this->clearString((string)$object['destination_number']),
|
||||
'destination_bic' => $this->clearString((string)$object['destination_bic']),
|
||||
|
||||
// budget info
|
||||
'budget_id' => $this->integerFromValue((string)$object['budget_id']),
|
||||
'budget_name' => $this->clearString((string)$object['budget_name']),
|
||||
|
||||
// category info
|
||||
'category_id' => $this->integerFromValue((string)$object['category_id']),
|
||||
'category_name' => $this->clearString((string)$object['category_name']),
|
||||
|
||||
// journal bill reference. Optional. Will only work for withdrawals
|
||||
'bill_id' => $this->integerFromValue((string)$object['bill_id']),
|
||||
'bill_name' => $this->clearString((string)$object['bill_name']),
|
||||
|
||||
// piggy bank reference. Optional. Will only work for transfers
|
||||
'piggy_bank_id' => $this->integerFromValue((string)$object['piggy_bank_id']),
|
||||
'piggy_bank_name' => $this->clearString((string)$object['piggy_bank_name']),
|
||||
|
||||
// some other interesting properties
|
||||
'reconciled' => $this->convertBoolean((string)$object['reconciled']),
|
||||
'notes' => $this->clearStringKeepNewlines((string)$object['notes']),
|
||||
'tags' => $this->arrayFromValue($object['tags']),
|
||||
|
||||
// all custom fields:
|
||||
'internal_reference' => $this->clearString((string)$object['internal_reference']),
|
||||
'external_id' => $this->clearString((string)$object['external_id']),
|
||||
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
|
||||
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
|
||||
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id']),
|
||||
'external_url' => $this->clearString((string)$object['external_url']),
|
||||
|
||||
'sepa_cc' => $this->clearString((string)$object['sepa_cc']),
|
||||
'sepa_ct_op' => $this->clearString((string)$object['sepa_ct_op']),
|
||||
'sepa_ct_id' => $this->clearString((string)$object['sepa_ct_id']),
|
||||
'sepa_db' => $this->clearString((string)$object['sepa_db']),
|
||||
'sepa_country' => $this->clearString((string)$object['sepa_country']),
|
||||
'sepa_ep' => $this->clearString((string)$object['sepa_ep']),
|
||||
'sepa_ci' => $this->clearString((string)$object['sepa_ci']),
|
||||
'sepa_batch_id' => $this->clearString((string)$object['sepa_batch_id']),
|
||||
// custom date fields. Must be Carbon objects. Presence is optional.
|
||||
'interest_date' => $this->dateFromValue($object['interest_date']),
|
||||
'book_date' => $this->dateFromValue($object['book_date']),
|
||||
'process_date' => $this->dateFromValue($object['process_date']),
|
||||
'due_date' => $this->dateFromValue($object['due_date']),
|
||||
'payment_date' => $this->dateFromValue($object['payment_date']),
|
||||
'invoice_date' => $this->dateFromValue($object['invoice_date']),
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -196,99 +291,4 @@ class StoreRequest extends FormRequest
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/**
|
||||
* @var array $transaction
|
||||
*/
|
||||
foreach ($this->get('transactions') as $transaction) {
|
||||
$object = new NullArrayObject($transaction);
|
||||
$return[] = [
|
||||
'type' => $this->clearString($object['type']),
|
||||
'date' => $this->dateFromValue($object['date']),
|
||||
'order' => $this->integerFromValue((string)$object['order']),
|
||||
|
||||
'currency_id' => $this->integerFromValue((string)$object['currency_id']),
|
||||
'currency_code' => $this->clearString((string)$object['currency_code']),
|
||||
|
||||
// foreign currency info:
|
||||
'foreign_currency_id' => $this->integerFromValue((string)$object['foreign_currency_id']),
|
||||
'foreign_currency_code' => $this->clearString((string)$object['foreign_currency_code']),
|
||||
|
||||
// amount and foreign amount. Cannot be 0.
|
||||
'amount' => $this->clearString((string)$object['amount']),
|
||||
'foreign_amount' => $this->clearString((string)$object['foreign_amount']),
|
||||
|
||||
// description.
|
||||
'description' => $this->clearString($object['description']),
|
||||
|
||||
// source of transaction. If everything is null, assume cash account.
|
||||
'source_id' => $this->integerFromValue((string)$object['source_id']),
|
||||
'source_name' => $this->clearString((string)$object['source_name']),
|
||||
'source_iban' => $this->clearString((string)$object['source_iban']),
|
||||
'source_number' => $this->clearString((string)$object['source_number']),
|
||||
'source_bic' => $this->clearString((string)$object['source_bic']),
|
||||
|
||||
// destination of transaction. If everything is null, assume cash account.
|
||||
'destination_id' => $this->integerFromValue((string)$object['destination_id']),
|
||||
'destination_name' => $this->clearString((string)$object['destination_name']),
|
||||
'destination_iban' => $this->clearString((string)$object['destination_iban']),
|
||||
'destination_number' => $this->clearString((string)$object['destination_number']),
|
||||
'destination_bic' => $this->clearString((string)$object['destination_bic']),
|
||||
|
||||
// budget info
|
||||
'budget_id' => $this->integerFromValue((string)$object['budget_id']),
|
||||
'budget_name' => $this->clearString((string)$object['budget_name']),
|
||||
|
||||
// category info
|
||||
'category_id' => $this->integerFromValue((string)$object['category_id']),
|
||||
'category_name' => $this->clearString((string)$object['category_name']),
|
||||
|
||||
// journal bill reference. Optional. Will only work for withdrawals
|
||||
'bill_id' => $this->integerFromValue((string)$object['bill_id']),
|
||||
'bill_name' => $this->clearString((string)$object['bill_name']),
|
||||
|
||||
// piggy bank reference. Optional. Will only work for transfers
|
||||
'piggy_bank_id' => $this->integerFromValue((string)$object['piggy_bank_id']),
|
||||
'piggy_bank_name' => $this->clearString((string)$object['piggy_bank_name']),
|
||||
|
||||
// some other interesting properties
|
||||
'reconciled' => $this->convertBoolean((string)$object['reconciled']),
|
||||
'notes' => $this->clearStringKeepNewlines((string)$object['notes']),
|
||||
'tags' => $this->arrayFromValue($object['tags']),
|
||||
|
||||
// all custom fields:
|
||||
'internal_reference' => $this->clearString((string)$object['internal_reference']),
|
||||
'external_id' => $this->clearString((string)$object['external_id']),
|
||||
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
|
||||
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
|
||||
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id']),
|
||||
'external_url' => $this->clearString((string)$object['external_url']),
|
||||
|
||||
'sepa_cc' => $this->clearString((string)$object['sepa_cc']),
|
||||
'sepa_ct_op' => $this->clearString((string)$object['sepa_ct_op']),
|
||||
'sepa_ct_id' => $this->clearString((string)$object['sepa_ct_id']),
|
||||
'sepa_db' => $this->clearString((string)$object['sepa_db']),
|
||||
'sepa_country' => $this->clearString((string)$object['sepa_country']),
|
||||
'sepa_ep' => $this->clearString((string)$object['sepa_ep']),
|
||||
'sepa_ci' => $this->clearString((string)$object['sepa_ci']),
|
||||
'sepa_batch_id' => $this->clearString((string)$object['sepa_batch_id']),
|
||||
// custom date fields. Must be Carbon objects. Presence is optional.
|
||||
'interest_date' => $this->dateFromValue($object['interest_date']),
|
||||
'book_date' => $this->dateFromValue($object['book_date']),
|
||||
'process_date' => $this->dateFromValue($object['process_date']),
|
||||
'due_date' => $this->dateFromValue($object['due_date']),
|
||||
'payment_date' => $this->dateFromValue($object['payment_date']),
|
||||
'invoice_date' => $this->dateFromValue($object['invoice_date']),
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -90,127 +90,6 @@ class UpdateRequest extends FormRequest
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
$validProtocols = config('firefly.valid_url_protocols');
|
||||
|
||||
return [
|
||||
// basic fields for group:
|
||||
'group_title' => 'min:1|max:1000|nullable',
|
||||
'apply_rules' => [new IsBoolean()],
|
||||
|
||||
// transaction rules (in array for splits):
|
||||
'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation',
|
||||
'transactions.*.date' => [new IsDateOrTime()],
|
||||
'transactions.*.order' => 'numeric|min:0',
|
||||
|
||||
// group id:
|
||||
'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()],
|
||||
|
||||
// currency info
|
||||
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
|
||||
'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
|
||||
'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
|
||||
|
||||
// amount
|
||||
'transactions.*.amount' => [new IsValidPositiveAmount()],
|
||||
'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()],
|
||||
|
||||
// description
|
||||
'transactions.*.description' => 'nullable|min:1|max:1000',
|
||||
|
||||
// source of transaction
|
||||
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.source_name' => 'min:1|max:255|nullable',
|
||||
|
||||
// destination of transaction
|
||||
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.destination_name' => 'min:1|max:255|nullable',
|
||||
|
||||
// budget, category, bill and piggy
|
||||
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser(), 'nullable'],
|
||||
'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()],
|
||||
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'],
|
||||
'transactions.*.category_name' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()],
|
||||
'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()],
|
||||
|
||||
// other interesting fields
|
||||
'transactions.*.reconciled' => [new IsBoolean()],
|
||||
'transactions.*.notes' => 'min:1|max:32768|nullable',
|
||||
'transactions.*.tags' => 'min:0|max:255|nullable',
|
||||
'transactions.*.tags.*' => 'min:0|max:255',
|
||||
|
||||
// meta info fields
|
||||
'transactions.*.internal_reference' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.recurrence_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols),
|
||||
|
||||
// SEPA fields:
|
||||
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
|
||||
|
||||
// dates
|
||||
'transactions.*.interest_date' => 'date|nullable',
|
||||
'transactions.*.book_date' => 'date|nullable',
|
||||
'transactions.*.process_date' => 'date|nullable',
|
||||
'transactions.*.due_date' => 'date|nullable',
|
||||
'transactions.*.payment_date' => 'date|nullable',
|
||||
'transactions.*.invoice_date' => 'date|nullable',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
app('log')->debug('Now in withValidator');
|
||||
|
||||
/** @var TransactionGroup $transactionGroup */
|
||||
$transactionGroup = $this->route()->parameter('transactionGroup');
|
||||
$validator->after(
|
||||
function (Validator $validator) use ($transactionGroup): void {
|
||||
// if more than one, verify that there are journal ID's present.
|
||||
$this->validateJournalIds($validator, $transactionGroup);
|
||||
|
||||
// all transaction types must be equal:
|
||||
$this->validateTransactionTypesForUpdate($validator);
|
||||
|
||||
// user wants to update a reconciled transaction.
|
||||
// source, destination, amount + foreign_amount cannot be changed
|
||||
// and must be omitted from the request.
|
||||
$this->preventUpdateReconciled($validator, $transactionGroup);
|
||||
|
||||
// validate source/destination is equal, depending on the transaction journal type.
|
||||
$this->validateEqualAccountsForUpdate($validator, $transactionGroup);
|
||||
|
||||
// see method:
|
||||
// $this->preventNoAccountInfo($validator, );
|
||||
|
||||
// validate that the currency fits the source and/or destination account.
|
||||
// validate all account info
|
||||
$this->validateAccountInformationUpdate($validator, $transactionGroup);
|
||||
}
|
||||
);
|
||||
if($validator->fails()) {
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*
|
||||
@@ -362,4 +241,125 @@ class UpdateRequest extends FormRequest
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
$validProtocols = config('firefly.valid_url_protocols');
|
||||
|
||||
return [
|
||||
// basic fields for group:
|
||||
'group_title' => 'min:1|max:1000|nullable',
|
||||
'apply_rules' => [new IsBoolean()],
|
||||
|
||||
// transaction rules (in array for splits):
|
||||
'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation',
|
||||
'transactions.*.date' => [new IsDateOrTime()],
|
||||
'transactions.*.order' => 'numeric|min:0',
|
||||
|
||||
// group id:
|
||||
'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()],
|
||||
|
||||
// currency info
|
||||
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
|
||||
'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
|
||||
'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
|
||||
|
||||
// amount
|
||||
'transactions.*.amount' => [new IsValidPositiveAmount()],
|
||||
'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()],
|
||||
|
||||
// description
|
||||
'transactions.*.description' => 'nullable|min:1|max:1000',
|
||||
|
||||
// source of transaction
|
||||
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.source_name' => 'min:1|max:255|nullable',
|
||||
|
||||
// destination of transaction
|
||||
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.destination_name' => 'min:1|max:255|nullable',
|
||||
|
||||
// budget, category, bill and piggy
|
||||
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser(), 'nullable'],
|
||||
'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()],
|
||||
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'],
|
||||
'transactions.*.category_name' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()],
|
||||
'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()],
|
||||
|
||||
// other interesting fields
|
||||
'transactions.*.reconciled' => [new IsBoolean()],
|
||||
'transactions.*.notes' => 'min:1|max:32768|nullable',
|
||||
'transactions.*.tags' => 'min:0|max:255|nullable',
|
||||
'transactions.*.tags.*' => 'min:0|max:255',
|
||||
|
||||
// meta info fields
|
||||
'transactions.*.internal_reference' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.recurrence_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols),
|
||||
|
||||
// SEPA fields:
|
||||
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
|
||||
|
||||
// dates
|
||||
'transactions.*.interest_date' => 'date|nullable',
|
||||
'transactions.*.book_date' => 'date|nullable',
|
||||
'transactions.*.process_date' => 'date|nullable',
|
||||
'transactions.*.due_date' => 'date|nullable',
|
||||
'transactions.*.payment_date' => 'date|nullable',
|
||||
'transactions.*.invoice_date' => 'date|nullable',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
app('log')->debug('Now in withValidator');
|
||||
|
||||
/** @var TransactionGroup $transactionGroup */
|
||||
$transactionGroup = $this->route()->parameter('transactionGroup');
|
||||
$validator->after(
|
||||
function (Validator $validator) use ($transactionGroup): void {
|
||||
// if more than one, verify that there are journal ID's present.
|
||||
$this->validateJournalIds($validator, $transactionGroup);
|
||||
|
||||
// all transaction types must be equal:
|
||||
$this->validateTransactionTypesForUpdate($validator);
|
||||
|
||||
// user wants to update a reconciled transaction.
|
||||
// source, destination, amount + foreign_amount cannot be changed
|
||||
// and must be omitted from the request.
|
||||
$this->preventUpdateReconciled($validator, $transactionGroup);
|
||||
|
||||
// validate source/destination is equal, depending on the transaction journal type.
|
||||
$this->validateEqualAccountsForUpdate($validator, $transactionGroup);
|
||||
|
||||
// see method:
|
||||
// $this->preventNoAccountInfo($validator, );
|
||||
|
||||
// validate that the currency fits the source and/or destination account.
|
||||
// validate all account info
|
||||
$this->validateAccountInformationUpdate($validator, $transactionGroup);
|
||||
}
|
||||
);
|
||||
if ($validator->fails()) {
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -67,43 +67,6 @@ class Controller extends BaseController
|
||||
);
|
||||
}
|
||||
|
||||
final protected function jsonApiList(string $key, LengthAwarePaginator $paginator, AbstractTransformer $transformer): array
|
||||
{
|
||||
$manager = new Manager();
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v2';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$objects = $paginator->getCollection();
|
||||
|
||||
// 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);
|
||||
|
||||
$resource = new FractalCollection($objects, $transformer, $key);
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return $manager->createData($resource)->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON API object and returns it.
|
||||
*
|
||||
* @param array<int, mixed>|Model $object
|
||||
*/
|
||||
final protected function jsonApiObject(string $key, array|Model $object, AbstractTransformer $transformer): array
|
||||
{
|
||||
// create some objects:
|
||||
$manager = new Manager();
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v2';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$transformer->collectMetaData(new Collection([$object]));
|
||||
|
||||
$resource = new Item($object, $transformer, $key);
|
||||
|
||||
return $manager->createData($resource)->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate from V1 controller
|
||||
* Method to grab all parameters from the URL.
|
||||
@@ -184,4 +147,41 @@ class Controller extends BaseController
|
||||
|
||||
return $bag;
|
||||
}
|
||||
|
||||
final protected function jsonApiList(string $key, LengthAwarePaginator $paginator, AbstractTransformer $transformer): array
|
||||
{
|
||||
$manager = new Manager();
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v2';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$objects = $paginator->getCollection();
|
||||
|
||||
// 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);
|
||||
|
||||
$resource = new FractalCollection($objects, $transformer, $key);
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return $manager->createData($resource)->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON API object and returns it.
|
||||
*
|
||||
* @param array<int, mixed>|Model $object
|
||||
*/
|
||||
final protected function jsonApiObject(string $key, array|Model $object, AbstractTransformer $transformer): array
|
||||
{
|
||||
// create some objects:
|
||||
$manager = new Manager();
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v2';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$transformer->collectMetaData(new Collection([$object]));
|
||||
|
||||
$resource = new Item($object, $transformer, $key);
|
||||
|
||||
return $manager->createData($resource)->toArray();
|
||||
}
|
||||
}
|
||||
|
@@ -51,20 +51,6 @@ class ShowController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a budget.
|
||||
*/
|
||||
public function show(Budget $budget): JsonResponse
|
||||
{
|
||||
$transformer = new BudgetTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()
|
||||
->api($this->jsonApiObject('budgets', $budget, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* 2023-10-29 removed the cerSum reference, not sure where this is used atm
|
||||
* so removed from api.php. Also applies to "spent" method.
|
||||
@@ -80,6 +66,20 @@ class ShowController extends Controller
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a budget.
|
||||
*/
|
||||
public function show(Budget $budget): JsonResponse
|
||||
{
|
||||
$transformer = new BudgetTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()
|
||||
->api($this->jsonApiObject('budgets', $budget, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* TODO add URL
|
||||
|
@@ -114,23 +114,6 @@ class BasicController extends Controller
|
||||
return response()->json($total);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date is outside session range.
|
||||
*/
|
||||
protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
|
||||
{
|
||||
$result = false;
|
||||
if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) {
|
||||
$result = true;
|
||||
}
|
||||
// start and end in the past? use $end
|
||||
if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) {
|
||||
$result = true;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -411,4 +394,21 @@ class BasicController extends Controller
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date is outside session range.
|
||||
*/
|
||||
protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
|
||||
{
|
||||
$result = false;
|
||||
if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) {
|
||||
$result = true;
|
||||
}
|
||||
// start and end in the past? use $end
|
||||
if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) {
|
||||
$result = true;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V2\Controllers\Transaction\List;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Transaction\InfiniteListRequest;
|
||||
use FireflyIII\Api\V2\Request\Model\Transaction\ListRequest;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Transformers\V2\TransactionGroupTransformer;
|
||||
@@ -59,9 +60,6 @@ class TransactionController extends Controller
|
||||
$collector->setEnd($end);
|
||||
}
|
||||
|
||||
// $collector->dumpQuery();
|
||||
// exit;
|
||||
|
||||
$paginator = $collector->getPaginatedGroups();
|
||||
$params = $request->buildParams($pageSize);
|
||||
$paginator->setPath(
|
||||
@@ -77,4 +75,45 @@ 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)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
132
app/Api/V2/Request/Model/Transaction/InfiniteListRequest.php
Normal file
132
app/Api/V2/Request/Model/Transaction/InfiniteListRequest.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* ListRequest.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V2\Request\Model\Transaction;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Class InfiniteListRequest
|
||||
* Used specifically to list transactions.
|
||||
*/
|
||||
class InfiniteListRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use TransactionFilter;
|
||||
|
||||
public function buildParams(): string
|
||||
{
|
||||
$array = [
|
||||
'start_row' => $this->getStartRow(),
|
||||
'end_row' => $this->getEndRow(),
|
||||
];
|
||||
|
||||
$start = $this->getStartDate();
|
||||
$end = $this->getEndDate();
|
||||
if (null !== $start && null !== $end) {
|
||||
$array['start'] = $start->format('Y-m-d');
|
||||
$array['end'] = $end->format('Y-m-d');
|
||||
}
|
||||
|
||||
return http_build_query($array);
|
||||
}
|
||||
|
||||
public function getStartRow(): int
|
||||
{
|
||||
$startRow = $this->convertInteger('start_row');
|
||||
|
||||
return $startRow < 0 || $startRow > 4294967296 ? 0 : $startRow;
|
||||
}
|
||||
|
||||
public function getEndRow(): int
|
||||
{
|
||||
$endRow = $this->convertInteger('end_row');
|
||||
|
||||
return $endRow <= 0 || $endRow > 4294967296 ? 100 : $endRow;
|
||||
}
|
||||
|
||||
public function getStartDate(): ?Carbon
|
||||
{
|
||||
return $this->getCarbonDate('start');
|
||||
}
|
||||
|
||||
public function getEndDate(): ?Carbon
|
||||
{
|
||||
return $this->getCarbonDate('end');
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
$page = $this->convertInteger('page');
|
||||
|
||||
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');
|
||||
|
||||
return $this->mapTransactionTypes($type);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date',
|
||||
'end' => 'date|after:start',
|
||||
'start_row' => 'integer|min:0|max:4294967296',
|
||||
'end_row' => 'integer|min:0|max:4294967296|gt:start_row',
|
||||
];
|
||||
}
|
||||
}
|
@@ -78,6 +78,103 @@ class StoreRequest extends FormRequest
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/**
|
||||
* @var array $transaction
|
||||
*/
|
||||
foreach ($this->get('transactions') as $transaction) {
|
||||
$object = new NullArrayObject($transaction);
|
||||
$result = [
|
||||
'type' => $this->clearString($object['type']),
|
||||
'date' => $this->dateFromValue($object['date']),
|
||||
'order' => $this->integerFromValue((string)$object['order']),
|
||||
|
||||
'currency_id' => $this->integerFromValue((string)$object['currency_id']),
|
||||
'currency_code' => $this->clearString((string)$object['currency_code']),
|
||||
|
||||
// foreign currency info:
|
||||
'foreign_currency_id' => $this->integerFromValue((string)$object['foreign_currency_id']),
|
||||
'foreign_currency_code' => $this->clearString((string)$object['foreign_currency_code']),
|
||||
|
||||
// amount and foreign amount. Cannot be 0.
|
||||
'amount' => $this->clearString((string)$object['amount']),
|
||||
'foreign_amount' => $this->clearString((string)$object['foreign_amount']),
|
||||
|
||||
// description.
|
||||
'description' => $this->clearString($object['description']),
|
||||
|
||||
// source of transaction. If everything is null, assume cash account.
|
||||
'source_id' => $this->integerFromValue((string)$object['source_id']),
|
||||
'source_name' => $this->clearString((string)$object['source_name']),
|
||||
'source_iban' => $this->clearString((string)$object['source_iban']),
|
||||
'source_number' => $this->clearString((string)$object['source_number']),
|
||||
'source_bic' => $this->clearString((string)$object['source_bic']),
|
||||
|
||||
// destination of transaction. If everything is null, assume cash account.
|
||||
'destination_id' => $this->integerFromValue((string)$object['destination_id']),
|
||||
'destination_name' => $this->clearString((string)$object['destination_name']),
|
||||
'destination_iban' => $this->clearString((string)$object['destination_iban']),
|
||||
'destination_number' => $this->clearString((string)$object['destination_number']),
|
||||
'destination_bic' => $this->clearString((string)$object['destination_bic']),
|
||||
|
||||
// budget info
|
||||
'budget_id' => $this->integerFromValue((string)$object['budget_id']),
|
||||
'budget_name' => $this->clearString((string)$object['budget_name']),
|
||||
|
||||
// category info
|
||||
'category_id' => $this->integerFromValue((string)$object['category_id']),
|
||||
'category_name' => $this->clearString((string)$object['category_name']),
|
||||
|
||||
// journal bill reference. Optional. Will only work for withdrawals
|
||||
'bill_id' => $this->integerFromValue((string)$object['bill_id']),
|
||||
'bill_name' => $this->clearString((string)$object['bill_name']),
|
||||
|
||||
// piggy bank reference. Optional. Will only work for transfers
|
||||
'piggy_bank_id' => $this->integerFromValue((string)$object['piggy_bank_id']),
|
||||
'piggy_bank_name' => $this->clearString((string)$object['piggy_bank_name']),
|
||||
|
||||
// some other interesting properties
|
||||
'reconciled' => $this->convertBoolean((string)$object['reconciled']),
|
||||
'notes' => $this->clearStringKeepNewlines((string)$object['notes']),
|
||||
'tags' => $this->arrayFromValue($object['tags']),
|
||||
|
||||
// all custom fields:
|
||||
'internal_reference' => $this->clearString((string)$object['internal_reference']),
|
||||
'external_id' => $this->clearString((string)$object['external_id']),
|
||||
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
|
||||
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
|
||||
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id']),
|
||||
'external_url' => $this->clearString((string)$object['external_url']),
|
||||
|
||||
'sepa_cc' => $this->clearString((string)$object['sepa_cc']),
|
||||
'sepa_ct_op' => $this->clearString((string)$object['sepa_ct_op']),
|
||||
'sepa_ct_id' => $this->clearString((string)$object['sepa_ct_id']),
|
||||
'sepa_db' => $this->clearString((string)$object['sepa_db']),
|
||||
'sepa_country' => $this->clearString((string)$object['sepa_country']),
|
||||
'sepa_ep' => $this->clearString((string)$object['sepa_ep']),
|
||||
'sepa_ci' => $this->clearString((string)$object['sepa_ci']),
|
||||
'sepa_batch_id' => $this->clearString((string)$object['sepa_batch_id']),
|
||||
// custom date fields. Must be Carbon objects. Presence is optional.
|
||||
'interest_date' => $this->dateFromValue($object['interest_date']),
|
||||
'book_date' => $this->dateFromValue($object['book_date']),
|
||||
'process_date' => $this->dateFromValue($object['process_date']),
|
||||
'due_date' => $this->dateFromValue($object['due_date']),
|
||||
'payment_date' => $this->dateFromValue($object['payment_date']),
|
||||
'invoice_date' => $this->dateFromValue($object['invoice_date']),
|
||||
];
|
||||
$result = $this->addFromromTransactionStore($transaction, $result);
|
||||
$return[] = $result;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -216,101 +313,4 @@ class StoreRequest extends FormRequest
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/**
|
||||
* @var array $transaction
|
||||
*/
|
||||
foreach ($this->get('transactions') as $transaction) {
|
||||
$object = new NullArrayObject($transaction);
|
||||
$result = [
|
||||
'type' => $this->clearString($object['type']),
|
||||
'date' => $this->dateFromValue($object['date']),
|
||||
'order' => $this->integerFromValue((string)$object['order']),
|
||||
|
||||
'currency_id' => $this->integerFromValue((string)$object['currency_id']),
|
||||
'currency_code' => $this->clearString((string)$object['currency_code']),
|
||||
|
||||
// foreign currency info:
|
||||
'foreign_currency_id' => $this->integerFromValue((string)$object['foreign_currency_id']),
|
||||
'foreign_currency_code' => $this->clearString((string)$object['foreign_currency_code']),
|
||||
|
||||
// amount and foreign amount. Cannot be 0.
|
||||
'amount' => $this->clearString((string)$object['amount']),
|
||||
'foreign_amount' => $this->clearString((string)$object['foreign_amount']),
|
||||
|
||||
// description.
|
||||
'description' => $this->clearString($object['description']),
|
||||
|
||||
// source of transaction. If everything is null, assume cash account.
|
||||
'source_id' => $this->integerFromValue((string)$object['source_id']),
|
||||
'source_name' => $this->clearString((string)$object['source_name']),
|
||||
'source_iban' => $this->clearString((string)$object['source_iban']),
|
||||
'source_number' => $this->clearString((string)$object['source_number']),
|
||||
'source_bic' => $this->clearString((string)$object['source_bic']),
|
||||
|
||||
// destination of transaction. If everything is null, assume cash account.
|
||||
'destination_id' => $this->integerFromValue((string)$object['destination_id']),
|
||||
'destination_name' => $this->clearString((string)$object['destination_name']),
|
||||
'destination_iban' => $this->clearString((string)$object['destination_iban']),
|
||||
'destination_number' => $this->clearString((string)$object['destination_number']),
|
||||
'destination_bic' => $this->clearString((string)$object['destination_bic']),
|
||||
|
||||
// budget info
|
||||
'budget_id' => $this->integerFromValue((string)$object['budget_id']),
|
||||
'budget_name' => $this->clearString((string)$object['budget_name']),
|
||||
|
||||
// category info
|
||||
'category_id' => $this->integerFromValue((string)$object['category_id']),
|
||||
'category_name' => $this->clearString((string)$object['category_name']),
|
||||
|
||||
// journal bill reference. Optional. Will only work for withdrawals
|
||||
'bill_id' => $this->integerFromValue((string)$object['bill_id']),
|
||||
'bill_name' => $this->clearString((string)$object['bill_name']),
|
||||
|
||||
// piggy bank reference. Optional. Will only work for transfers
|
||||
'piggy_bank_id' => $this->integerFromValue((string)$object['piggy_bank_id']),
|
||||
'piggy_bank_name' => $this->clearString((string)$object['piggy_bank_name']),
|
||||
|
||||
// some other interesting properties
|
||||
'reconciled' => $this->convertBoolean((string)$object['reconciled']),
|
||||
'notes' => $this->clearStringKeepNewlines((string)$object['notes']),
|
||||
'tags' => $this->arrayFromValue($object['tags']),
|
||||
|
||||
// all custom fields:
|
||||
'internal_reference' => $this->clearString((string)$object['internal_reference']),
|
||||
'external_id' => $this->clearString((string)$object['external_id']),
|
||||
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
|
||||
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
|
||||
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id']),
|
||||
'external_url' => $this->clearString((string)$object['external_url']),
|
||||
|
||||
'sepa_cc' => $this->clearString((string)$object['sepa_cc']),
|
||||
'sepa_ct_op' => $this->clearString((string)$object['sepa_ct_op']),
|
||||
'sepa_ct_id' => $this->clearString((string)$object['sepa_ct_id']),
|
||||
'sepa_db' => $this->clearString((string)$object['sepa_db']),
|
||||
'sepa_country' => $this->clearString((string)$object['sepa_country']),
|
||||
'sepa_ep' => $this->clearString((string)$object['sepa_ep']),
|
||||
'sepa_ci' => $this->clearString((string)$object['sepa_ci']),
|
||||
'sepa_batch_id' => $this->clearString((string)$object['sepa_batch_id']),
|
||||
// custom date fields. Must be Carbon objects. Presence is optional.
|
||||
'interest_date' => $this->dateFromValue($object['interest_date']),
|
||||
'book_date' => $this->dateFromValue($object['book_date']),
|
||||
'process_date' => $this->dateFromValue($object['process_date']),
|
||||
'due_date' => $this->dateFromValue($object['due_date']),
|
||||
'payment_date' => $this->dateFromValue($object['payment_date']),
|
||||
'invoice_date' => $this->dateFromValue($object['invoice_date']),
|
||||
];
|
||||
$result = $this->addFromromTransactionStore($transaction, $result);
|
||||
$return[] = $result;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -91,127 +91,6 @@ class UpdateRequest extends Request
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
$validProtocols = config('firefly.valid_url_protocols');
|
||||
|
||||
return [
|
||||
// basic fields for group:
|
||||
'group_title' => 'min:1|max:1000|nullable',
|
||||
'apply_rules' => [new IsBoolean()],
|
||||
|
||||
// transaction rules (in array for splits):
|
||||
'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation',
|
||||
'transactions.*.date' => [new IsDateOrTime()],
|
||||
'transactions.*.order' => 'numeric|min:0',
|
||||
|
||||
// group id:
|
||||
'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()],
|
||||
|
||||
// currency info
|
||||
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
|
||||
'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
|
||||
'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
|
||||
|
||||
// amount
|
||||
'transactions.*.amount' => ['nullable', new IsValidPositiveAmount()],
|
||||
'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()],
|
||||
|
||||
// description
|
||||
'transactions.*.description' => 'nullable|min:1|max:1000',
|
||||
|
||||
// source of transaction
|
||||
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.source_name' => 'min:1|max:255|nullable',
|
||||
|
||||
// destination of transaction
|
||||
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.destination_name' => 'min:1|max:255|nullable',
|
||||
|
||||
// budget, category, bill and piggy
|
||||
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser(), 'nullable'],
|
||||
'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()],
|
||||
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'],
|
||||
'transactions.*.category_name' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()],
|
||||
'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()],
|
||||
|
||||
// other interesting fields
|
||||
'transactions.*.reconciled' => [new IsBoolean()],
|
||||
'transactions.*.notes' => 'min:1|max:32768|nullable',
|
||||
'transactions.*.tags' => 'min:0|max:255|nullable',
|
||||
'transactions.*.tags.*' => 'min:0|max:255',
|
||||
|
||||
// meta info fields
|
||||
'transactions.*.internal_reference' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.recurrence_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols),
|
||||
|
||||
// SEPA fields:
|
||||
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
|
||||
|
||||
// dates
|
||||
'transactions.*.interest_date' => 'date|nullable',
|
||||
'transactions.*.book_date' => 'date|nullable',
|
||||
'transactions.*.process_date' => 'date|nullable',
|
||||
'transactions.*.due_date' => 'date|nullable',
|
||||
'transactions.*.payment_date' => 'date|nullable',
|
||||
'transactions.*.invoice_date' => 'date|nullable',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
app('log')->debug('Now in withValidator');
|
||||
|
||||
/** @var TransactionGroup $transactionGroup */
|
||||
$transactionGroup = $this->route()->parameter('userGroupTransaction');
|
||||
$validator->after(
|
||||
function (Validator $validator) use ($transactionGroup): void {
|
||||
// if more than one, verify that there are journal ID's present.
|
||||
$this->validateJournalIds($validator, $transactionGroup);
|
||||
|
||||
// all transaction types must be equal:
|
||||
$this->validateTransactionTypesForUpdate($validator);
|
||||
|
||||
// user wants to update a reconciled transaction.
|
||||
// source, destination, amount + foreign_amount cannot be changed
|
||||
// and must be omitted from the request.
|
||||
$this->preventUpdateReconciled($validator, $transactionGroup);
|
||||
|
||||
// validate source/destination is equal, depending on the transaction journal type.
|
||||
$this->validateEqualAccountsForUpdate($validator, $transactionGroup);
|
||||
|
||||
// see method:
|
||||
// $this->preventNoAccountInfo($validator, );
|
||||
|
||||
// validate that the currency fits the source and/or destination account.
|
||||
// validate all account info
|
||||
$this->validateAccountInformationUpdate($validator, $transactionGroup);
|
||||
}
|
||||
);
|
||||
if($validator->fails()) {
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*
|
||||
@@ -363,4 +242,125 @@ class UpdateRequest extends Request
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
$validProtocols = config('firefly.valid_url_protocols');
|
||||
|
||||
return [
|
||||
// basic fields for group:
|
||||
'group_title' => 'min:1|max:1000|nullable',
|
||||
'apply_rules' => [new IsBoolean()],
|
||||
|
||||
// transaction rules (in array for splits):
|
||||
'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation',
|
||||
'transactions.*.date' => [new IsDateOrTime()],
|
||||
'transactions.*.order' => 'numeric|min:0',
|
||||
|
||||
// group id:
|
||||
'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()],
|
||||
|
||||
// currency info
|
||||
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
|
||||
'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
|
||||
'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
|
||||
|
||||
// amount
|
||||
'transactions.*.amount' => ['nullable', new IsValidPositiveAmount()],
|
||||
'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()],
|
||||
|
||||
// description
|
||||
'transactions.*.description' => 'nullable|min:1|max:1000',
|
||||
|
||||
// source of transaction
|
||||
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.source_name' => 'min:1|max:255|nullable',
|
||||
|
||||
// destination of transaction
|
||||
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.destination_name' => 'min:1|max:255|nullable',
|
||||
|
||||
// budget, category, bill and piggy
|
||||
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser(), 'nullable'],
|
||||
'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()],
|
||||
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'],
|
||||
'transactions.*.category_name' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()],
|
||||
'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()],
|
||||
|
||||
// other interesting fields
|
||||
'transactions.*.reconciled' => [new IsBoolean()],
|
||||
'transactions.*.notes' => 'min:1|max:32768|nullable',
|
||||
'transactions.*.tags' => 'min:0|max:255|nullable',
|
||||
'transactions.*.tags.*' => 'min:0|max:255',
|
||||
|
||||
// meta info fields
|
||||
'transactions.*.internal_reference' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.recurrence_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols),
|
||||
|
||||
// SEPA fields:
|
||||
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
|
||||
|
||||
// dates
|
||||
'transactions.*.interest_date' => 'date|nullable',
|
||||
'transactions.*.book_date' => 'date|nullable',
|
||||
'transactions.*.process_date' => 'date|nullable',
|
||||
'transactions.*.due_date' => 'date|nullable',
|
||||
'transactions.*.payment_date' => 'date|nullable',
|
||||
'transactions.*.invoice_date' => 'date|nullable',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
app('log')->debug('Now in withValidator');
|
||||
|
||||
/** @var TransactionGroup $transactionGroup */
|
||||
$transactionGroup = $this->route()->parameter('userGroupTransaction');
|
||||
$validator->after(
|
||||
function (Validator $validator) use ($transactionGroup): void {
|
||||
// if more than one, verify that there are journal ID's present.
|
||||
$this->validateJournalIds($validator, $transactionGroup);
|
||||
|
||||
// all transaction types must be equal:
|
||||
$this->validateTransactionTypesForUpdate($validator);
|
||||
|
||||
// user wants to update a reconciled transaction.
|
||||
// source, destination, amount + foreign_amount cannot be changed
|
||||
// and must be omitted from the request.
|
||||
$this->preventUpdateReconciled($validator, $transactionGroup);
|
||||
|
||||
// validate source/destination is equal, depending on the transaction journal type.
|
||||
$this->validateEqualAccountsForUpdate($validator, $transactionGroup);
|
||||
|
||||
// see method:
|
||||
// $this->preventNoAccountInfo($validator, );
|
||||
|
||||
// validate that the currency fits the source and/or destination account.
|
||||
// validate all account info
|
||||
$this->validateAccountInformationUpdate($validator, $transactionGroup);
|
||||
}
|
||||
);
|
||||
if ($validator->fails()) {
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -248,29 +248,14 @@ class FixAccountTypes extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function isLiability(string $destinationType): bool
|
||||
{
|
||||
return AccountType::LOAN === $destinationType || AccountType::DEBT === $destinationType || AccountType::MORTGAGE === $destinationType;
|
||||
}
|
||||
|
||||
private function shouldBeTransfer(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return TransactionType::TRANSFER === $transactionType && AccountType::ASSET === $sourceType && $this->isLiability($destinationType);
|
||||
}
|
||||
|
||||
private function shouldBeDeposit(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
private function isLiability(string $destinationType): bool
|
||||
{
|
||||
return TransactionType::TRANSFER === $transactionType && $this->isLiability($sourceType) && AccountType::ASSET === $destinationType;
|
||||
}
|
||||
|
||||
private function shouldGoToExpenseAccount(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return TransactionType::WITHDRAWAL === $transactionType && AccountType::ASSET === $sourceType && AccountType::REVENUE === $destinationType;
|
||||
}
|
||||
|
||||
private function shouldComeFromRevenueAccount(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return TransactionType::DEPOSIT === $transactionType && AccountType::EXPENSE === $sourceType && AccountType::ASSET === $destinationType;
|
||||
return AccountType::LOAN === $destinationType || AccountType::DEBT === $destinationType || AccountType::MORTGAGE === $destinationType;
|
||||
}
|
||||
|
||||
private function makeTransfer(TransactionJournal $journal): void
|
||||
@@ -286,6 +271,11 @@ class FixAccountTypes extends Command
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function shouldBeDeposit(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return TransactionType::TRANSFER === $transactionType && $this->isLiability($sourceType) && AccountType::ASSET === $destinationType;
|
||||
}
|
||||
|
||||
private function makeDeposit(TransactionJournal $journal): void
|
||||
{
|
||||
// from a liability to an asset should be a deposit.
|
||||
@@ -299,6 +289,11 @@ class FixAccountTypes extends Command
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function shouldGoToExpenseAccount(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return TransactionType::WITHDRAWAL === $transactionType && AccountType::ASSET === $sourceType && AccountType::REVENUE === $destinationType;
|
||||
}
|
||||
|
||||
private function makeExpenseDestination(TransactionJournal $journal, Transaction $destination): void
|
||||
{
|
||||
// withdrawals with a revenue account as destination instead of an expense account.
|
||||
@@ -320,6 +315,11 @@ class FixAccountTypes extends Command
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function shouldComeFromRevenueAccount(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return TransactionType::DEPOSIT === $transactionType && AccountType::EXPENSE === $sourceType && AccountType::ASSET === $destinationType;
|
||||
}
|
||||
|
||||
private function makeRevenueSource(TransactionJournal $journal, Transaction $source): void
|
||||
{
|
||||
// deposits with an expense account as source instead of a revenue account.
|
||||
@@ -355,11 +355,6 @@ class FixAccountTypes extends Command
|
||||
return in_array($accountType, $validTypes, true);
|
||||
}
|
||||
|
||||
private function canCreateDestination(array $validDestinations): bool
|
||||
{
|
||||
return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, true);
|
||||
}
|
||||
|
||||
private function giveNewRevenue(TransactionJournal $journal, Transaction $source): void
|
||||
{
|
||||
app('log')->debug(sprintf('An account of type "%s" could be a valid source.', AccountTypeEnum::REVENUE->value));
|
||||
@@ -373,6 +368,11 @@ class FixAccountTypes extends Command
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function canCreateDestination(array $validDestinations): bool
|
||||
{
|
||||
return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, true);
|
||||
}
|
||||
|
||||
private function giveNewExpense(TransactionJournal $journal, Transaction $destination): void
|
||||
{
|
||||
app('log')->debug(sprintf('An account of type "%s" could be a valid destination.', AccountTypeEnum::EXPENSE->value));
|
||||
|
@@ -57,6 +57,19 @@ class CreateGroupMemberships extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function createGroupMemberships(): void
|
||||
{
|
||||
$users = User::get();
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
self::createGroupMembership($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO move to helper.
|
||||
*
|
||||
@@ -93,17 +106,4 @@ class CreateGroupMemberships extends Command
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function createGroupMemberships(): void
|
||||
{
|
||||
$users = User::get();
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
self::createGroupMembership($user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -72,6 +72,16 @@ class UpgradeFireflyInstructions extends Command
|
||||
}
|
||||
}
|
||||
|
||||
// validate some settings.
|
||||
if ('' === $text && 'local' === (string)config('app.env')) {
|
||||
$text = 'Please set APP_ENV=production for a safer environment.';
|
||||
}
|
||||
|
||||
$prefix = 'v';
|
||||
if (str_starts_with($version, 'develop')) {
|
||||
$prefix = '';
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->showLogo();
|
||||
$this->newLine();
|
||||
@@ -79,7 +89,7 @@ class UpgradeFireflyInstructions extends Command
|
||||
|
||||
$this->boxed('');
|
||||
if ('' === $text) {
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s', $version));
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, %s%s', $prefix, $version));
|
||||
$this->boxedInfo('There are no extra upgrade instructions.');
|
||||
$this->boxed('Firefly III should be ready for use.');
|
||||
$this->boxed('');
|
||||
@@ -88,7 +98,7 @@ class UpgradeFireflyInstructions extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s!', $version));
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, %s%s!', $prefix, $version));
|
||||
$this->boxedInfo($text);
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
@@ -181,13 +191,24 @@ class UpgradeFireflyInstructions extends Command
|
||||
$text = (string)$config[$compare];
|
||||
}
|
||||
}
|
||||
|
||||
// validate some settings.
|
||||
if ('' === $text && 'local' === (string)config('app.env')) {
|
||||
$text = 'Please set APP_ENV=production for a safer environment.';
|
||||
}
|
||||
|
||||
$prefix = 'v';
|
||||
if (str_starts_with($version, 'develop')) {
|
||||
$prefix = '';
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->showLogo();
|
||||
$this->newLine();
|
||||
$this->showLine();
|
||||
$this->boxed('');
|
||||
if ('' === $text) {
|
||||
$this->boxed(sprintf('Thank you for installing Firefly III, v%s!', $version));
|
||||
$this->boxed(sprintf('Thank you for installing Firefly III, %s%s!', $prefix, $version));
|
||||
$this->boxedInfo('There are no extra installation instructions.');
|
||||
$this->boxed('Firefly III should be ready for use.');
|
||||
$this->boxed('');
|
||||
@@ -196,7 +217,7 @@ class UpgradeFireflyInstructions extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$this->boxed(sprintf('Thank you for installing Firefly III, v%s!', $version));
|
||||
$this->boxed(sprintf('Thank you for installing Firefly III, %s%s!', $prefix, $version));
|
||||
$this->boxedInfo($text);
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
|
@@ -193,22 +193,6 @@ class MigrateToGroups extends Command
|
||||
);
|
||||
}
|
||||
|
||||
private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction
|
||||
{
|
||||
$set = $journal->transactions->filter(
|
||||
static function (Transaction $subject) use ($transaction) {
|
||||
$amount = (float) $transaction->amount * -1 === (float) $subject->amount; // intentional float
|
||||
$identifier = $transaction->identifier === $subject->identifier;
|
||||
app('log')->debug(sprintf('Amount the same? %s', var_export($amount, true)));
|
||||
app('log')->debug(sprintf('ID the same? %s', var_export($identifier, true)));
|
||||
|
||||
return $amount && $identifier;
|
||||
}
|
||||
);
|
||||
|
||||
return $set->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
@@ -299,6 +283,22 @@ class MigrateToGroups extends Command
|
||||
];
|
||||
}
|
||||
|
||||
private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction
|
||||
{
|
||||
$set = $journal->transactions->filter(
|
||||
static function (Transaction $subject) use ($transaction) {
|
||||
$amount = (float)$transaction->amount * -1 === (float)$subject->amount; // intentional float
|
||||
$identifier = $transaction->identifier === $subject->identifier;
|
||||
app('log')->debug(sprintf('Amount the same? %s', var_export($amount, true)));
|
||||
app('log')->debug(sprintf('ID the same? %s', var_export($identifier, true)));
|
||||
|
||||
return $amount && $identifier;
|
||||
}
|
||||
);
|
||||
|
||||
return $set->first();
|
||||
}
|
||||
|
||||
private function getTransactionBudget(Transaction $left, Transaction $right): ?int
|
||||
{
|
||||
app('log')->debug('Now in getTransactionBudget()');
|
||||
|
@@ -34,8 +34,7 @@ class RequestedVersionCheckStatus extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/** @var User The user */
|
||||
public $user;
|
||||
public User $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance. This event is triggered when Firefly III wants to know
|
||||
|
@@ -227,6 +227,16 @@ class Handler extends ExceptionHandler
|
||||
parent::report($e);
|
||||
}
|
||||
|
||||
private function shouldntReportLocal(\Throwable $e): bool
|
||||
{
|
||||
return null !== Arr::first(
|
||||
$this->dontReport,
|
||||
static function ($type) use ($e) {
|
||||
return $e instanceof $type;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a validation exception into a response.
|
||||
*
|
||||
@@ -244,16 +254,6 @@ class Handler extends ExceptionHandler
|
||||
;
|
||||
}
|
||||
|
||||
private function shouldntReportLocal(\Throwable $e): bool
|
||||
{
|
||||
return null !== Arr::first(
|
||||
$this->dontReport,
|
||||
static function ($type) use ($e) {
|
||||
return $e instanceof $type;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only return the redirectTo property from the exception if it is a valid URL. Return NULL otherwise.
|
||||
*/
|
||||
|
@@ -121,21 +121,6 @@ class AccountFactory
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function find(string $accountName, string $accountType): ?Account
|
||||
{
|
||||
app('log')->debug(sprintf('Now in AccountFactory::find("%s", "%s")', $accountName, $accountType));
|
||||
$type = AccountType::whereType($accountType)->first();
|
||||
|
||||
// @var Account|null
|
||||
return $this->user->accounts()->where('account_type_id', $type->id)->where('name', $accountName)->first();
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->accountRepository->setUser($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -169,6 +154,15 @@ class AccountFactory
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function find(string $accountName, string $accountType): ?Account
|
||||
{
|
||||
app('log')->debug(sprintf('Now in AccountFactory::find("%s", "%s")', $accountName, $accountType));
|
||||
$type = AccountType::whereType($accountType)->first();
|
||||
|
||||
// @var Account|null
|
||||
return $this->user->accounts()->where('account_type_id', $type->id)->where('name', $accountName)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -365,4 +359,10 @@ class AccountFactory
|
||||
$updateService->setUser($account->user);
|
||||
$updateService->update($account, ['order' => $order]);
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->accountRepository->setUser($user);
|
||||
}
|
||||
}
|
||||
|
@@ -72,64 +72,6 @@ class TransactionFactory
|
||||
return $this->create(app('steam')->negative($amount), $foreignAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create transaction with positive amount (for destination accounts).
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function createPositive(string $amount, ?string $foreignAmount): Transaction
|
||||
{
|
||||
if ('' === $foreignAmount) {
|
||||
$foreignAmount = null;
|
||||
}
|
||||
if (null !== $foreignAmount) {
|
||||
$foreignAmount = app('steam')->positive($foreignAmount);
|
||||
}
|
||||
|
||||
return $this->create(app('steam')->positive($amount), $foreignAmount);
|
||||
}
|
||||
|
||||
public function setAccount(Account $account): void
|
||||
{
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
public function setAccountInformation(array $accountInformation): void
|
||||
{
|
||||
$this->accountInformation = $accountInformation;
|
||||
}
|
||||
|
||||
public function setCurrency(TransactionCurrency $currency): void
|
||||
{
|
||||
$this->currency = $currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|TransactionCurrency $foreignCurrency |null
|
||||
*/
|
||||
public function setForeignCurrency(?TransactionCurrency $foreignCurrency): void
|
||||
{
|
||||
$this->foreignCurrency = $foreignCurrency;
|
||||
}
|
||||
|
||||
public function setJournal(TransactionJournal $journal): void
|
||||
{
|
||||
$this->journal = $journal;
|
||||
}
|
||||
|
||||
public function setReconciled(bool $reconciled): void
|
||||
{
|
||||
$this->reconciled = $reconciled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
// empty function.
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -224,4 +166,62 @@ class TransactionFactory
|
||||
$service = app(AccountUpdateService::class);
|
||||
$service->update($this->account, ['iban' => $this->accountInformation['iban']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create transaction with positive amount (for destination accounts).
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function createPositive(string $amount, ?string $foreignAmount): Transaction
|
||||
{
|
||||
if ('' === $foreignAmount) {
|
||||
$foreignAmount = null;
|
||||
}
|
||||
if (null !== $foreignAmount) {
|
||||
$foreignAmount = app('steam')->positive($foreignAmount);
|
||||
}
|
||||
|
||||
return $this->create(app('steam')->positive($amount), $foreignAmount);
|
||||
}
|
||||
|
||||
public function setAccount(Account $account): void
|
||||
{
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
public function setAccountInformation(array $accountInformation): void
|
||||
{
|
||||
$this->accountInformation = $accountInformation;
|
||||
}
|
||||
|
||||
public function setCurrency(TransactionCurrency $currency): void
|
||||
{
|
||||
$this->currency = $currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|TransactionCurrency $foreignCurrency |null
|
||||
*/
|
||||
public function setForeignCurrency(?TransactionCurrency $foreignCurrency): void
|
||||
{
|
||||
$this->foreignCurrency = $foreignCurrency;
|
||||
}
|
||||
|
||||
public function setJournal(TransactionJournal $journal): void
|
||||
{
|
||||
$this->journal = $journal;
|
||||
}
|
||||
|
||||
public function setReconciled(bool $reconciled): void
|
||||
{
|
||||
$this->reconciled = $reconciled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
// empty function.
|
||||
}
|
||||
}
|
||||
|
@@ -141,49 +141,6 @@ class TransactionJournalFactory
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user.
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->currencyRepository->setUser($this->user);
|
||||
$this->tagFactory->setUser($user);
|
||||
$this->billRepository->setUser($this->user);
|
||||
$this->budgetRepository->setUser($this->user);
|
||||
$this->categoryRepository->setUser($this->user);
|
||||
$this->piggyRepository->setUser($this->user);
|
||||
$this->accountRepository->setUser($this->user);
|
||||
}
|
||||
|
||||
public function setErrorOnHash(bool $errorOnHash): void
|
||||
{
|
||||
$this->errorOnHash = $errorOnHash;
|
||||
if (true === $errorOnHash) {
|
||||
app('log')->info('Will trigger duplication alert for this journal.');
|
||||
}
|
||||
}
|
||||
|
||||
protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void
|
||||
{
|
||||
$set = [
|
||||
'journal' => $journal,
|
||||
'name' => $field,
|
||||
'data' => (string) ($data[$field] ?? ''),
|
||||
];
|
||||
if ($data[$field] instanceof Carbon) {
|
||||
$data[$field]->setTimezone(config('app.timezone'));
|
||||
app('log')->debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName()));
|
||||
$set['data'] = $data[$field]->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data']));
|
||||
|
||||
/** @var TransactionJournalMetaFactory $factory */
|
||||
$factory = app(TransactionJournalMetaFactory::class);
|
||||
$factory->updateOrCreate($set);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO typeOverrule: the account validator may have another opinion on the transaction type. not sure what to do
|
||||
* with this.
|
||||
@@ -326,18 +283,6 @@ class TransactionJournalFactory
|
||||
return $journal;
|
||||
}
|
||||
|
||||
private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void
|
||||
{
|
||||
if (true === $data['store_location']) {
|
||||
$location = new Location();
|
||||
$location->longitude = $data['longitude'];
|
||||
$location->latitude = $data['latitude'];
|
||||
$location->zoom_level = $data['zoom_level'];
|
||||
$location->locatable()->associate($journal);
|
||||
$location->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function hashArray(NullArrayObject $row): string
|
||||
{
|
||||
$dataRow = $row->getArrayCopy();
|
||||
@@ -428,6 +373,21 @@ class TransactionJournalFactory
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user.
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->currencyRepository->setUser($this->user);
|
||||
$this->tagFactory->setUser($user);
|
||||
$this->billRepository->setUser($this->user);
|
||||
$this->budgetRepository->setUser($this->user);
|
||||
$this->categoryRepository->setUser($this->user);
|
||||
$this->piggyRepository->setUser($this->user);
|
||||
$this->accountRepository->setUser($this->user);
|
||||
}
|
||||
|
||||
private function reconciliationSanityCheck(?Account $sourceAccount, ?Account $destinationAccount): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
@@ -567,4 +527,44 @@ class TransactionJournalFactory
|
||||
$this->storeMeta($journal, $transaction, $field);
|
||||
}
|
||||
}
|
||||
|
||||
protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void
|
||||
{
|
||||
$set = [
|
||||
'journal' => $journal,
|
||||
'name' => $field,
|
||||
'data' => (string)($data[$field] ?? ''),
|
||||
];
|
||||
if ($data[$field] instanceof Carbon) {
|
||||
$data[$field]->setTimezone(config('app.timezone'));
|
||||
app('log')->debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName()));
|
||||
$set['data'] = $data[$field]->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data']));
|
||||
|
||||
/** @var TransactionJournalMetaFactory $factory */
|
||||
$factory = app(TransactionJournalMetaFactory::class);
|
||||
$factory->updateOrCreate($set);
|
||||
}
|
||||
|
||||
private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void
|
||||
{
|
||||
if (true === $data['store_location']) {
|
||||
$location = new Location();
|
||||
$location->longitude = $data['longitude'];
|
||||
$location->latitude = $data['latitude'];
|
||||
$location->zoom_level = $data['zoom_level'];
|
||||
$location->locatable()->associate($journal);
|
||||
$location->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function setErrorOnHash(bool $errorOnHash): void
|
||||
{
|
||||
$this->errorOnHash = $errorOnHash;
|
||||
if (true === $errorOnHash) {
|
||||
app('log')->info('Will trigger duplication alert for this journal.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -67,6 +67,14 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the preferred period.
|
||||
*/
|
||||
protected function preferredPeriod(): string
|
||||
{
|
||||
return 'day';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set accounts.
|
||||
*/
|
||||
@@ -130,12 +138,4 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the preferred period.
|
||||
*/
|
||||
protected function preferredPeriod(): string
|
||||
{
|
||||
return 'day';
|
||||
}
|
||||
}
|
||||
|
@@ -125,26 +125,6 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the involved budgets.
|
||||
*/
|
||||
public function setBudgets(Collection $budgets): ReportGeneratorInterface
|
||||
{
|
||||
$this->budgets = $budgets;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the involved accounts.
|
||||
*/
|
||||
public function setAccounts(Collection $accounts): ReportGeneratorInterface
|
||||
{
|
||||
$this->accounts = $accounts;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expenses.
|
||||
*/
|
||||
@@ -170,4 +150,24 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
|
||||
return $journals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the involved budgets.
|
||||
*/
|
||||
public function setBudgets(Collection $budgets): ReportGeneratorInterface
|
||||
{
|
||||
$this->budgets = $budgets;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the involved accounts.
|
||||
*/
|
||||
public function setAccounts(Collection $accounts): ReportGeneratorInterface
|
||||
{
|
||||
$this->accounts = $accounts;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -124,26 +124,6 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the categories involved in this report.
|
||||
*/
|
||||
public function setCategories(Collection $categories): ReportGeneratorInterface
|
||||
{
|
||||
$this->categories = $categories;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the involved accounts.
|
||||
*/
|
||||
public function setAccounts(Collection $accounts): ReportGeneratorInterface
|
||||
{
|
||||
$this->accounts = $accounts;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expenses for this report.
|
||||
*/
|
||||
@@ -168,6 +148,26 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
return $transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the categories involved in this report.
|
||||
*/
|
||||
public function setCategories(Collection $categories): ReportGeneratorInterface
|
||||
{
|
||||
$this->categories = $categories;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the involved accounts.
|
||||
*/
|
||||
public function setAccounts(Collection $accounts): ReportGeneratorInterface
|
||||
{
|
||||
$this->accounts = $accounts;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the income for this report.
|
||||
*/
|
||||
|
@@ -49,20 +49,6 @@ class BudgetLimitHandler
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
|
||||
public function deleted(Deleted $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('BudgetLimitHandler::deleted(#%s)', $event->budgetLimit->id));
|
||||
$budgetLimit = $event->budgetLimit;
|
||||
$budgetLimit->id = 0;
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
|
||||
public function updated(Updated $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id));
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
|
||||
private function updateAvailableBudget(BudgetLimit $budgetLimit): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in updateAvailableBudget(#%d)', $budgetLimit->id));
|
||||
@@ -154,6 +140,7 @@ class BudgetLimitHandler
|
||||
]
|
||||
);
|
||||
$availableBudget->save();
|
||||
app('log')->debug(sprintf('ID of new AB is #%d', $availableBudget->id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,4 +234,18 @@ class BudgetLimitHandler
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
public function deleted(Deleted $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('BudgetLimitHandler::deleted(#%s)', $event->budgetLimit->id));
|
||||
$budgetLimit = $event->budgetLimit;
|
||||
$budgetLimit->id = 0;
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
|
||||
public function updated(Updated $event): void
|
||||
{
|
||||
app('log')->debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id));
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
}
|
||||
|
@@ -27,6 +27,8 @@ namespace FireflyIII\Handlers\Events\Model;
|
||||
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
|
||||
use FireflyIII\Events\Model\Rule\RuleActionFailedOnObject;
|
||||
use FireflyIII\Notifications\User\RuleActionFailed;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
/**
|
||||
@@ -56,7 +58,11 @@ class RuleHandler
|
||||
$ruleLink = route('rules.edit', [$rule->id]);
|
||||
$params = [$mainMessage, $groupTitle, $groupLink, $ruleTitle, $ruleLink];
|
||||
|
||||
try {
|
||||
Notification::send($user, new RuleActionFailed($params));
|
||||
} catch (ClientException $e) {
|
||||
Log::error(sprintf('[a] Error sending notification that the rule action failed: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public function ruleActionFailedOnObject(RuleActionFailedOnObject $event): void
|
||||
@@ -81,6 +87,10 @@ class RuleHandler
|
||||
$ruleLink = route('rules.edit', [$rule->id]);
|
||||
$params = [$mainMessage, $groupTitle, $groupLink, $ruleTitle, $ruleLink];
|
||||
|
||||
try {
|
||||
Notification::send($user, new RuleActionFailed($params));
|
||||
} catch (ClientException $e) {
|
||||
Log::error(sprintf('[b] Error sending notification that the rule action failed: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -81,6 +81,27 @@ trait AttachmentCollection
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join table to get attachment information.
|
||||
*/
|
||||
private function joinAttachmentTables(): void
|
||||
{
|
||||
if (false === $this->hasJoinedAttTables) {
|
||||
// join some extra tables:
|
||||
$this->hasJoinedAttTables = true;
|
||||
$this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id')
|
||||
->where(
|
||||
static function (EloquentBuilder $q1): void { // @phpstan-ignore-line
|
||||
$q1->where('attachments.attachable_type', TransactionJournal::class);
|
||||
$q1->where('attachments.uploaded', true);
|
||||
$q1->whereNull('attachments.deleted_at');
|
||||
$q1->orWhereNull('attachments.attachable_type');
|
||||
}
|
||||
)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
public function withAttachmentInformation(): GroupCollectorInterface
|
||||
{
|
||||
$this->fields[] = 'attachments.id as attachment_id';
|
||||
@@ -511,25 +532,4 @@ trait AttachmentCollection
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join table to get attachment information.
|
||||
*/
|
||||
private function joinAttachmentTables(): void
|
||||
{
|
||||
if (false === $this->hasJoinedAttTables) {
|
||||
// join some extra tables:
|
||||
$this->hasJoinedAttTables = true;
|
||||
$this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id')
|
||||
->where(
|
||||
static function (EloquentBuilder $q1): void { // @phpstan-ignore-line
|
||||
$q1->where('attachments.attachable_type', TransactionJournal::class);
|
||||
$q1->where('attachments.uploaded', true);
|
||||
$q1->whereNull('attachments.deleted_at');
|
||||
$q1->orWhereNull('attachments.attachable_type');
|
||||
}
|
||||
)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -33,7 +33,10 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
*/
|
||||
trait CollectorProperties
|
||||
{
|
||||
/** @var array<int, string> */
|
||||
public array $sorting;
|
||||
public const string TEST = 'Test';
|
||||
private ?int $endRow;
|
||||
private bool $expandGroupSearch;
|
||||
private array $fields;
|
||||
private bool $hasAccountInfo;
|
||||
@@ -49,6 +52,7 @@ trait CollectorProperties
|
||||
private ?int $page;
|
||||
private array $postFilters;
|
||||
private HasMany $query;
|
||||
private ?int $startRow;
|
||||
private array $stringFields;
|
||||
/*
|
||||
* This array is used to collect ALL tags the user may search for (using 'setTags').
|
||||
|
@@ -171,6 +171,19 @@ trait MetaCollection
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join table to get tag information.
|
||||
*/
|
||||
protected function joinMetaDataTables(): void
|
||||
{
|
||||
if (false === $this->hasJoinedMetaTables) {
|
||||
$this->hasJoinedMetaTables = true;
|
||||
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
|
||||
$this->fields[] = 'journal_meta.name as meta_name';
|
||||
$this->fields[] = 'journal_meta.data as meta_data';
|
||||
}
|
||||
}
|
||||
|
||||
public function excludeExternalUrl(string $url): GroupCollectorInterface
|
||||
{
|
||||
$this->joinMetaDataTables();
|
||||
@@ -369,6 +382,19 @@ trait MetaCollection
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join table to get tag information.
|
||||
*/
|
||||
protected function joinTagTables(): void
|
||||
{
|
||||
if (false === $this->hasJoinedTagTables) {
|
||||
// join some extra tables:
|
||||
$this->hasJoinedTagTables = true;
|
||||
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
|
||||
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
|
||||
}
|
||||
}
|
||||
|
||||
public function internalReferenceContains(string $internalReference): GroupCollectorInterface
|
||||
{
|
||||
$internalReference = (string)json_encode($internalReference);
|
||||
@@ -539,6 +565,63 @@ trait MetaCollection
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to a SPECIFIC set of tags.
|
||||
*/
|
||||
public function setAllTags(Collection $tags): GroupCollectorInterface
|
||||
{
|
||||
Log::debug(sprintf('Now in setAllTags(%d tag(s))', $tags->count()));
|
||||
$this->withTagInformation();
|
||||
$this->query->whereNotNull('tag_transaction_journal.tag_id');
|
||||
|
||||
// this method adds a "postFilter" to the collector.
|
||||
$list = $tags->pluck('tag')->toArray();
|
||||
$list = array_map('strtolower', $list);
|
||||
$filter = static function (array $object) use ($list): bool|array {
|
||||
$includedJournals = [];
|
||||
$return = $object;
|
||||
unset($return['transactions']);
|
||||
$return['transactions'] = [];
|
||||
Log::debug(sprintf('Now in setAllTags(%s) filter', implode(', ', $list)));
|
||||
$expectedTagCount = count($list);
|
||||
$foundTagCount = 0;
|
||||
foreach ($object['transactions'] as $transaction) {
|
||||
$transactionTagCount = count($transaction['tags']);
|
||||
app('log')->debug(sprintf('Transaction #%d has %d tag(s)', $transaction['transaction_journal_id'], $transactionTagCount));
|
||||
if ($transactionTagCount < $expectedTagCount) {
|
||||
app('log')->debug(sprintf('Transaction has %d tag(s), we expect %d tag(s), return false.', $transactionTagCount, $expectedTagCount));
|
||||
|
||||
return false;
|
||||
}
|
||||
foreach ($transaction['tags'] as $tag) {
|
||||
Log::debug(sprintf('"%s" versus', strtolower($tag['name'])), $list);
|
||||
if (in_array(strtolower($tag['name']), $list, true)) {
|
||||
app('log')->debug(sprintf('Transaction has tag "%s" so count++.', $tag['name']));
|
||||
++$foundTagCount;
|
||||
$journalId = $transaction['transaction_journal_id'];
|
||||
// #8377 prevent adding a transaction twice when multiple tag searches find this transaction
|
||||
if (!in_array($journalId, $includedJournals, true)) {
|
||||
$includedJournals[] = $journalId;
|
||||
$return['transactions'][] = $transaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Found %d tags, need at least %d.', $foundTagCount, $expectedTagCount));
|
||||
|
||||
// found at least the expected tags.
|
||||
$result = $foundTagCount >= $expectedTagCount;
|
||||
if (true === $result) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
$this->postFilters[] = $filter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the search to a specific bill.
|
||||
*/
|
||||
@@ -669,63 +752,6 @@ trait MetaCollection
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to a SPECIFIC set of tags.
|
||||
*/
|
||||
public function setAllTags(Collection $tags): GroupCollectorInterface
|
||||
{
|
||||
Log::debug(sprintf('Now in setAllTags(%d tag(s))', $tags->count()));
|
||||
$this->withTagInformation();
|
||||
$this->query->whereNotNull('tag_transaction_journal.tag_id');
|
||||
|
||||
// this method adds a "postFilter" to the collector.
|
||||
$list = $tags->pluck('tag')->toArray();
|
||||
$list = array_map('strtolower', $list);
|
||||
$filter = static function (array $object) use ($list): bool|array {
|
||||
$includedJournals = [];
|
||||
$return = $object;
|
||||
unset($return['transactions']);
|
||||
$return['transactions'] = [];
|
||||
Log::debug(sprintf('Now in setAllTags(%s) filter', implode(', ', $list)));
|
||||
$expectedTagCount = count($list);
|
||||
$foundTagCount = 0;
|
||||
foreach ($object['transactions'] as $transaction) {
|
||||
$transactionTagCount = count($transaction['tags']);
|
||||
app('log')->debug(sprintf('Transaction #%d has %d tag(s)', $transaction['transaction_journal_id'], $transactionTagCount));
|
||||
if ($transactionTagCount < $expectedTagCount) {
|
||||
app('log')->debug(sprintf('Transaction has %d tag(s), we expect %d tag(s), return false.', $transactionTagCount, $expectedTagCount));
|
||||
|
||||
return false;
|
||||
}
|
||||
foreach ($transaction['tags'] as $tag) {
|
||||
Log::debug(sprintf('"%s" versus', strtolower($tag['name'])), $list);
|
||||
if (in_array(strtolower($tag['name']), $list, true)) {
|
||||
app('log')->debug(sprintf('Transaction has tag "%s" so count++.', $tag['name']));
|
||||
++$foundTagCount;
|
||||
$journalId = $transaction['transaction_journal_id'];
|
||||
// #8377 prevent adding a transaction twice when multiple tag searches find this transaction
|
||||
if (!in_array($journalId, $includedJournals, true)) {
|
||||
$includedJournals[] = $journalId;
|
||||
$return['transactions'][] = $transaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Found %d tags, need at least %d.', $foundTagCount, $expectedTagCount));
|
||||
|
||||
// found at least the expected tags.
|
||||
$result = $foundTagCount >= $expectedTagCount;
|
||||
if (true === $result) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
$this->postFilters[] = $filter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to any of the tags in the list.
|
||||
*/
|
||||
@@ -938,30 +964,4 @@ trait MetaCollection
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join table to get tag information.
|
||||
*/
|
||||
protected function joinMetaDataTables(): void
|
||||
{
|
||||
if (false === $this->hasJoinedMetaTables) {
|
||||
$this->hasJoinedMetaTables = true;
|
||||
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
|
||||
$this->fields[] = 'journal_meta.name as meta_name';
|
||||
$this->fields[] = 'journal_meta.data as meta_data';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Join table to get tag information.
|
||||
*/
|
||||
protected function joinTagTables(): void
|
||||
{
|
||||
if (false === $this->hasJoinedTagTables) {
|
||||
// join some extra tables:
|
||||
$this->hasJoinedTagTables = true;
|
||||
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
|
||||
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -61,12 +61,15 @@ class GroupCollector implements GroupCollectorInterface
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->sorting = [];
|
||||
$this->postFilters = [];
|
||||
$this->tags = [];
|
||||
$this->user = null;
|
||||
$this->userGroup = null;
|
||||
$this->limit = null;
|
||||
$this->page = null;
|
||||
$this->startRow = null;
|
||||
$this->endRow = null;
|
||||
|
||||
$this->hasAccountInfo = false;
|
||||
$this->hasCatInformation = false;
|
||||
@@ -464,6 +467,9 @@ class GroupCollector implements GroupCollectorInterface
|
||||
// filter the array using all available post filters:
|
||||
$collection = $this->postFilterCollection($collection);
|
||||
|
||||
// sort the collection, if sort instructions are present.
|
||||
$collection = $this->sortCollection($collection);
|
||||
|
||||
// count it and continue:
|
||||
$this->total = $collection->count();
|
||||
|
||||
@@ -473,211 +479,14 @@ class GroupCollector implements GroupCollectorInterface
|
||||
|
||||
return $collection->slice($offset, $this->limit);
|
||||
}
|
||||
// OR filter the array according to the start and end row variable
|
||||
if (null !== $this->startRow && null !== $this->endRow) {
|
||||
return $collection->slice($this->startRow, $this->endRow);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as getGroups but everything is in a paginator.
|
||||
*/
|
||||
public function getPaginatedGroups(): LengthAwarePaginator
|
||||
{
|
||||
$set = $this->getGroups();
|
||||
if (0 === $this->limit) {
|
||||
$this->setLimit(50);
|
||||
}
|
||||
|
||||
return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the number of returned entries.
|
||||
*/
|
||||
public function setLimit(int $limit): GroupCollectorInterface
|
||||
{
|
||||
$this->limit = $limit;
|
||||
// app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isNotReconciled(): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where('source.reconciled', 0)->where('destination.reconciled', 0);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isReconciled(): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where('source.reconciled', 1)->where('destination.reconciled', 1);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to a specific currency, either foreign or normal one.
|
||||
*/
|
||||
public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where(
|
||||
static function (EloquentBuilder $q) use ($currency): void { // @phpstan-ignore-line
|
||||
$q->where('source.transaction_currency_id', $currency->id);
|
||||
$q->orWhere('source.foreign_currency_id', $currency->id);
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setExpandGroupSearch(bool $expandGroupSearch): GroupCollectorInterface
|
||||
{
|
||||
$this->expandGroupSearch = $expandGroupSearch;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where('source.foreign_currency_id', $currency->id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the result to a set of specific transaction groups.
|
||||
*/
|
||||
public function setIds(array $groupIds): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereIn('transaction_groups.id', $groupIds);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the result to a set of specific journals.
|
||||
*/
|
||||
public function setJournalIds(array $journalIds): GroupCollectorInterface
|
||||
{
|
||||
if (0 !== count($journalIds)) {
|
||||
// make all integers.
|
||||
$integerIDs = array_map('intval', $journalIds);
|
||||
Log::debug(sprintf('GroupCollector: setJournalIds: %s', implode(', ', $integerIDs)));
|
||||
|
||||
$this->query->whereIn('transaction_journals.id', $integerIDs);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the page to get.
|
||||
*/
|
||||
public function setPage(int $page): GroupCollectorInterface
|
||||
{
|
||||
$page = 0 === $page ? 1 : $page;
|
||||
$this->page = $page;
|
||||
// app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for words in descriptions.
|
||||
*/
|
||||
public function setSearchWords(array $array): GroupCollectorInterface
|
||||
{
|
||||
if (0 === count($array)) {
|
||||
return $this;
|
||||
}
|
||||
$this->query->where(
|
||||
static function (EloquentBuilder $q) use ($array): void { // @phpstan-ignore-line
|
||||
$q->where(
|
||||
static function (EloquentBuilder $q1) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s%%', $word);
|
||||
$q1->where('transaction_journals.description', 'LIKE', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
$q->orWhere(
|
||||
static function (EloquentBuilder $q2) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s%%', $word);
|
||||
$q2->where('transaction_groups.title', 'LIKE', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the search to one specific transaction group.
|
||||
*/
|
||||
public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where('transaction_groups.id', $transactionGroup->id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the included transaction types.
|
||||
*/
|
||||
public function setTypes(array $types): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereIn('transaction_types.type', $types);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user object and start the query.
|
||||
*/
|
||||
public function setUser(User $user): GroupCollectorInterface
|
||||
{
|
||||
if (null === $this->user) {
|
||||
$this->user = $user;
|
||||
$this->startQuery();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user object and start the query.
|
||||
*/
|
||||
public function setUserGroup(UserGroup $userGroup): GroupCollectorInterface
|
||||
{
|
||||
if (null === $this->userGroup) {
|
||||
$this->userGroup = $userGroup;
|
||||
$this->startQueryForGroup();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically include all stuff required to make API calls work.
|
||||
*/
|
||||
public function withAPIInformation(): GroupCollectorInterface
|
||||
{
|
||||
// include source + destination account name and type.
|
||||
$this->withAccountInformation()
|
||||
// include category ID + name (if any)
|
||||
->withCategoryInformation()
|
||||
// include budget ID + name (if any)
|
||||
->withBudgetInformation()
|
||||
// include bill ID + name (if any)
|
||||
->withBillInformation()
|
||||
;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getCollectedGroupIds(): array
|
||||
{
|
||||
return $this->query->get(['transaction_journals.transaction_group_id'])->pluck('transaction_group_id')->toArray();
|
||||
@@ -931,6 +740,11 @@ class GroupCollector implements GroupCollectorInterface
|
||||
private function postFilterCollection(Collection $collection): Collection
|
||||
{
|
||||
$currentCollection = $collection;
|
||||
$countFilters = count($this->postFilters);
|
||||
$countCollection = count($currentCollection);
|
||||
if (0 === $countFilters && 0 === $countCollection) {
|
||||
return $currentCollection;
|
||||
}
|
||||
app('log')->debug(sprintf('GroupCollector: postFilterCollection has %d filter(s) and %d transaction(s).', count($this->postFilters), count($currentCollection)));
|
||||
|
||||
/**
|
||||
@@ -968,6 +782,195 @@ class GroupCollector implements GroupCollectorInterface
|
||||
return $currentCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as getGroups but everything is in a paginator.
|
||||
*/
|
||||
public function getPaginatedGroups(): LengthAwarePaginator
|
||||
{
|
||||
$set = $this->getGroups();
|
||||
if (0 === $this->limit) {
|
||||
$this->setLimit(50);
|
||||
}
|
||||
if (null !== $this->startRow && null !== $this->endRow) {
|
||||
$total = $this->endRow - $this->startRow;
|
||||
|
||||
return new LengthAwarePaginator($set, $this->total, $total, 1);
|
||||
}
|
||||
|
||||
return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the number of returned entries.
|
||||
*/
|
||||
public function setLimit(int $limit): GroupCollectorInterface
|
||||
{
|
||||
$this->limit = $limit;
|
||||
// app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isNotReconciled(): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where('source.reconciled', 0)->where('destination.reconciled', 0);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isReconciled(): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where('source.reconciled', 1)->where('destination.reconciled', 1);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to a specific currency, either foreign or normal one.
|
||||
*/
|
||||
public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where(
|
||||
static function (EloquentBuilder $q) use ($currency): void { // @phpstan-ignore-line
|
||||
$q->where('source.transaction_currency_id', $currency->id);
|
||||
$q->orWhere('source.foreign_currency_id', $currency->id);
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEndRow(int $endRow): self
|
||||
{
|
||||
$this->endRow = $endRow;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setExpandGroupSearch(bool $expandGroupSearch): GroupCollectorInterface
|
||||
{
|
||||
$this->expandGroupSearch = $expandGroupSearch;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where('source.foreign_currency_id', $currency->id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the result to a set of specific transaction groups.
|
||||
*/
|
||||
public function setIds(array $groupIds): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereIn('transaction_groups.id', $groupIds);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the result to a set of specific journals.
|
||||
*/
|
||||
public function setJournalIds(array $journalIds): GroupCollectorInterface
|
||||
{
|
||||
if (0 !== count($journalIds)) {
|
||||
// make all integers.
|
||||
$integerIDs = array_map('intval', $journalIds);
|
||||
Log::debug(sprintf('GroupCollector: setJournalIds: %s', implode(', ', $integerIDs)));
|
||||
|
||||
$this->query->whereIn('transaction_journals.id', $integerIDs);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the page to get.
|
||||
*/
|
||||
public function setPage(int $page): GroupCollectorInterface
|
||||
{
|
||||
$page = 0 === $page ? 1 : $page;
|
||||
$this->page = $page;
|
||||
// app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for words in descriptions.
|
||||
*/
|
||||
public function setSearchWords(array $array): GroupCollectorInterface
|
||||
{
|
||||
if (0 === count($array)) {
|
||||
return $this;
|
||||
}
|
||||
$this->query->where(
|
||||
static function (EloquentBuilder $q) use ($array): void { // @phpstan-ignore-line
|
||||
$q->where(
|
||||
static function (EloquentBuilder $q1) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s%%', $word);
|
||||
$q1->where('transaction_journals.description', 'LIKE', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
$q->orWhere(
|
||||
static function (EloquentBuilder $q2) use ($array): void {
|
||||
foreach ($array as $word) {
|
||||
$keyword = sprintf('%%%s%%', $word);
|
||||
$q2->where('transaction_groups.title', 'LIKE', $keyword);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStartRow(int $startRow): self
|
||||
{
|
||||
$this->startRow = $startRow;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the search to one specific transaction group.
|
||||
*/
|
||||
public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface
|
||||
{
|
||||
$this->query->where('transaction_groups.id', $transactionGroup->id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the included transaction types.
|
||||
*/
|
||||
public function setTypes(array $types): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereIn('transaction_types.type', $types);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user object and start the query.
|
||||
*/
|
||||
public function setUser(User $user): GroupCollectorInterface
|
||||
{
|
||||
if (null === $this->user) {
|
||||
$this->user = $user;
|
||||
$this->startQuery();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the query.
|
||||
*/
|
||||
@@ -1014,6 +1017,19 @@ class GroupCollector implements GroupCollectorInterface
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user object and start the query.
|
||||
*/
|
||||
public function setUserGroup(UserGroup $userGroup): GroupCollectorInterface
|
||||
{
|
||||
if (null === $this->userGroup) {
|
||||
$this->userGroup = $userGroup;
|
||||
$this->startQueryForGroup();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the query.
|
||||
*/
|
||||
@@ -1057,4 +1073,59 @@ class GroupCollector implements GroupCollectorInterface
|
||||
->orderBy('source.amount', 'DESC')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically include all stuff required to make API calls work.
|
||||
*/
|
||||
public function withAPIInformation(): GroupCollectorInterface
|
||||
{
|
||||
// include source + destination account name and type.
|
||||
$this->withAccountInformation()
|
||||
// include category ID + name (if any)
|
||||
->withCategoryInformation()
|
||||
// include budget ID + name (if any)
|
||||
->withBudgetInformation()
|
||||
// include bill ID + name (if any)
|
||||
->withBillInformation()
|
||||
;
|
||||
|
||||
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,6 +285,13 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -401,6 +408,11 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function setAfter(Carbon $date): self;
|
||||
|
||||
/**
|
||||
* Limit results to a SPECIFIC set of tags.
|
||||
*/
|
||||
public function setAllTags(Collection $tags): self;
|
||||
|
||||
/**
|
||||
* Collect transactions before a specific date.
|
||||
*/
|
||||
@@ -461,6 +473,11 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function setEnd(Carbon $end): self;
|
||||
|
||||
/**
|
||||
* Set the page to get.
|
||||
*/
|
||||
public function setEndRow(int $endRow): self;
|
||||
|
||||
public function setExpandGroupSearch(bool $expandGroupSearch): self;
|
||||
|
||||
/**
|
||||
@@ -553,6 +570,11 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function setStart(Carbon $start): self;
|
||||
|
||||
/**
|
||||
* Set the page to get.
|
||||
*/
|
||||
public function setStartRow(int $startRow): self;
|
||||
|
||||
/**
|
||||
* Limit results to a specific tag.
|
||||
*/
|
||||
@@ -563,11 +585,6 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function setTags(Collection $tags): self;
|
||||
|
||||
/**
|
||||
* Limit results to a SPECIFIC set of tags.
|
||||
*/
|
||||
public function setAllTags(Collection $tags): self;
|
||||
|
||||
/**
|
||||
* Limit the search to one specific transaction group.
|
||||
*/
|
||||
|
@@ -51,7 +51,7 @@ class NetWorth implements NetWorthInterface
|
||||
|
||||
private CurrencyRepositoryInterface $currencyRepos;
|
||||
private User $user;
|
||||
private null|UserGroup $userGroup;
|
||||
private ?UserGroup $userGroup;
|
||||
|
||||
/**
|
||||
* This method collects the user's net worth in ALL the user's currencies
|
||||
@@ -144,6 +144,15 @@ class NetWorth implements NetWorthInterface
|
||||
return $netWorth;
|
||||
}
|
||||
|
||||
private function getRepository(): AccountRepositoryInterface|AdminAccountRepositoryInterface
|
||||
{
|
||||
if (null === $this->userGroup) {
|
||||
return $this->accountRepository;
|
||||
}
|
||||
|
||||
return $this->adminAccountRepository;
|
||||
}
|
||||
|
||||
public function setUser(null|Authenticatable|User $user): void
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
@@ -202,15 +211,6 @@ class NetWorth implements NetWorthInterface
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRepository(): AccountRepositoryInterface|AdminAccountRepositoryInterface
|
||||
{
|
||||
if (null === $this->userGroup) {
|
||||
return $this->accountRepository;
|
||||
}
|
||||
|
||||
return $this->adminAccountRepository;
|
||||
}
|
||||
|
||||
private function getAccounts(): Collection
|
||||
{
|
||||
$accounts = $this->getRepository()->getAccountsByType(
|
||||
|
@@ -68,6 +68,9 @@ class ForgotPasswordController extends Controller
|
||||
return view('error', compact('message'));
|
||||
}
|
||||
|
||||
// validate host header.
|
||||
$this->validateHost();
|
||||
|
||||
$this->validateEmail($request);
|
||||
|
||||
// verify if the user is not a demo user. If so, we give him back an error.
|
||||
@@ -92,6 +95,21 @@ class ForgotPasswordController extends Controller
|
||||
return back()->with('status', trans($response));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function validateHost(): void
|
||||
{
|
||||
$configuredHost = parse_url((string)config('app.url'), PHP_URL_HOST);
|
||||
if (false === $configuredHost || null === $configuredHost) {
|
||||
throw new FireflyException('Please set a valid and correct Firefly III URL in the APP_URL environment variable.');
|
||||
}
|
||||
$host = request()->host();
|
||||
if ($configuredHost !== $host) {
|
||||
throw new FireflyException('The Host-header does not match the host in the APP_URL environment variable. Please make sure these match. See also: https://bit.ly/FF3-host-header');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show form for email recovery.
|
||||
*
|
||||
|
@@ -132,6 +132,25 @@ class LoginController extends Controller
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the failed login response instance.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected function sendFailedLoginResponse(Request $request): void
|
||||
{
|
||||
$exception = ValidationException::withMessages(
|
||||
[
|
||||
$this->username() => [trans('auth.failed')],
|
||||
]
|
||||
);
|
||||
$exception->redirectTo = route('login');
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the user out of the application.
|
||||
*
|
||||
@@ -210,23 +229,4 @@ class LoginController extends Controller
|
||||
|
||||
return view('auth.login', compact('allowRegistration', 'email', 'remember', 'allowReset', 'title', 'usernameField'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the failed login response instance.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected function sendFailedLoginResponse(Request $request): void
|
||||
{
|
||||
$exception = ValidationException::withMessages(
|
||||
[
|
||||
$this->username() => [trans('auth.failed')],
|
||||
]
|
||||
);
|
||||
$exception->redirectTo = route('login');
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
@@ -109,6 +109,31 @@ class RegisterController extends Controller
|
||||
return redirect($this->redirectPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function allowedToRegister(): bool
|
||||
{
|
||||
// is allowed to register?
|
||||
$allowRegistration = true;
|
||||
|
||||
try {
|
||||
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
|
||||
$singleUserMode = true;
|
||||
}
|
||||
$userCount = User::count();
|
||||
$guard = config('auth.defaults.guard');
|
||||
if (true === $singleUserMode && $userCount > 0 && 'web' === $guard) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
if ('web' !== $guard) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
return $allowRegistration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the application registration form if the invitation code is valid.
|
||||
*
|
||||
@@ -164,29 +189,4 @@ class RegisterController extends Controller
|
||||
|
||||
return view('auth.register', compact('isDemoSite', 'email', 'pageTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function allowedToRegister(): bool
|
||||
{
|
||||
// is allowed to register?
|
||||
$allowRegistration = true;
|
||||
|
||||
try {
|
||||
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
|
||||
$singleUserMode = true;
|
||||
}
|
||||
$userCount = User::count();
|
||||
$guard = config('auth.defaults.guard');
|
||||
if (true === $singleUserMode && $userCount > 0 && 'web' === $guard) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
if ('web' !== $guard) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
return $allowRegistration;
|
||||
}
|
||||
}
|
||||
|
@@ -80,6 +80,7 @@ class ResetPasswordController extends Controller
|
||||
|
||||
return view('error', compact('message'));
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'token' => 'required',
|
||||
'email' => 'required|email',
|
||||
@@ -90,7 +91,7 @@ class ResetPasswordController extends Controller
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
// database. Otherwise, we will parse the error and return the response.
|
||||
$response = $this->broker()->reset(
|
||||
$this->credentials($request),
|
||||
function ($user, $password): void {
|
||||
|
@@ -31,7 +31,6 @@ use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use PragmaRX\Google2FALaravel\Support\Authenticator;
|
||||
use Preferences;
|
||||
|
||||
/**
|
||||
* Class TwoFactorController.
|
||||
|
@@ -134,24 +134,6 @@ class IndexController extends Controller
|
||||
return view('bills.index', compact('bills', 'sums', 'total', 'totals', 'today'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the order of a bill.
|
||||
*/
|
||||
public function setOrder(Request $request, Bill $bill): JsonResponse
|
||||
{
|
||||
$objectGroupTitle = (string)$request->get('objectGroupTitle');
|
||||
$newOrder = (int)$request->get('order');
|
||||
$this->repository->setOrder($bill, $newOrder);
|
||||
if ('' !== $objectGroupTitle) {
|
||||
$this->repository->setObjectGroup($bill, $objectGroupTitle);
|
||||
}
|
||||
if ('' === $objectGroupTitle) {
|
||||
$this->repository->removeObjectGroup($bill);
|
||||
}
|
||||
|
||||
return response()->json(['data' => 'OK']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -268,4 +250,22 @@ class IndexController extends Controller
|
||||
|
||||
return $totals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the order of a bill.
|
||||
*/
|
||||
public function setOrder(Request $request, Bill $bill): JsonResponse
|
||||
{
|
||||
$objectGroupTitle = (string)$request->get('objectGroupTitle');
|
||||
$newOrder = (int)$request->get('order');
|
||||
$this->repository->setOrder($bill, $newOrder);
|
||||
if ('' !== $objectGroupTitle) {
|
||||
$this->repository->setObjectGroup($bill, $objectGroupTitle);
|
||||
}
|
||||
if ('' === $objectGroupTitle) {
|
||||
$this->repository->removeObjectGroup($bill);
|
||||
}
|
||||
|
||||
return response()->json(['data' => 'OK']);
|
||||
}
|
||||
}
|
||||
|
@@ -116,7 +116,6 @@ class IndexController extends Controller
|
||||
|
||||
// get all available budgets:
|
||||
$availableBudgets = $this->getAllAvailableBudgets($start, $end);
|
||||
|
||||
// get all active budgets:
|
||||
$budgets = $this->getAllBudgets($start, $end, $currencies, $defaultCurrency);
|
||||
$sums = $this->getSums($budgets);
|
||||
@@ -159,24 +158,6 @@ class IndexController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
$this->abRepository->cleanup();
|
||||
$budgetIds = $request->get('budgetIds');
|
||||
|
||||
foreach ($budgetIds as $index => $budgetId) {
|
||||
$budgetId = (int)$budgetId;
|
||||
$budget = $repository->find($budgetId);
|
||||
if (null !== $budget) {
|
||||
app('log')->debug(sprintf('Set budget #%d ("%s") to position %d', $budget->id, $budget->name, $index + 1));
|
||||
$repository->setBudgetOrder($budget, $index + 1);
|
||||
}
|
||||
}
|
||||
app('preferences')->mark();
|
||||
|
||||
return response()->json(['OK']);
|
||||
}
|
||||
|
||||
private function getAllAvailableBudgets(Carbon $start, Carbon $end): array
|
||||
{
|
||||
// get all available budgets.
|
||||
@@ -316,4 +297,22 @@ class IndexController extends Controller
|
||||
|
||||
return $sums;
|
||||
}
|
||||
|
||||
public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
$this->abRepository->cleanup();
|
||||
$budgetIds = $request->get('budgetIds');
|
||||
|
||||
foreach ($budgetIds as $index => $budgetId) {
|
||||
$budgetId = (int)$budgetId;
|
||||
$budget = $repository->find($budgetId);
|
||||
if (null !== $budget) {
|
||||
app('log')->debug(sprintf('Set budget #%d ("%s") to position %d', $budget->id, $budget->name, $index + 1));
|
||||
$repository->setBudgetOrder($budget, $index + 1);
|
||||
}
|
||||
}
|
||||
app('preferences')->mark();
|
||||
|
||||
return response()->json(['OK']);
|
||||
}
|
||||
}
|
||||
|
@@ -409,6 +409,58 @@ class AccountController extends Controller
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code));
|
||||
$locale = app('steam')->getLocale();
|
||||
$step = $this->calculateStep($start, $end);
|
||||
$result = [
|
||||
'label' => sprintf('%s (%s)', $account->name, $currency->symbol),
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_code' => $currency->code,
|
||||
];
|
||||
$entries = [];
|
||||
$current = clone $start;
|
||||
app('log')->debug(sprintf('Step is %s', $step));
|
||||
|
||||
// fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
|
||||
// have to make sure this chart is always based on the balance at the END of the period.
|
||||
// This period depends on the size of the chart
|
||||
$current = app('navigation')->endOfX($current, $step, null);
|
||||
app('log')->debug(sprintf('$current date is %s', $current->format('Y-m-d')));
|
||||
if ('1D' === $step) {
|
||||
// per day the entire period, balance for every day.
|
||||
$format = (string)trans('config.month_and_day_js', [], $locale);
|
||||
$range = app('steam')->balanceInRange($account, $start, $end, $currency);
|
||||
$previous = array_values($range)[0];
|
||||
while ($end >= $current) {
|
||||
$theDate = $current->format('Y-m-d');
|
||||
$balance = $range[$theDate] ?? $previous;
|
||||
$label = $current->isoFormat($format);
|
||||
$entries[$label] = (float)$balance;
|
||||
$previous = $balance;
|
||||
$current->addDay();
|
||||
}
|
||||
}
|
||||
if ('1W' === $step || '1M' === $step || '1Y' === $step) {
|
||||
while ($end >= $current) {
|
||||
app('log')->debug(sprintf('Current is: %s', $current->format('Y-m-d')));
|
||||
$balance = (float)app('steam')->balance($account, $current, $currency);
|
||||
$label = app('navigation')->periodShow($current, $step);
|
||||
$entries[$label] = $balance;
|
||||
$current = app('navigation')->addPeriod($current, $step, 0);
|
||||
// here too, to fix #8041, the data is corrected to the end of the period.
|
||||
$current = app('navigation')->endOfX($current, $step, null);
|
||||
}
|
||||
}
|
||||
$result['entries'] = $entries;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the balances for a given set of dates and accounts.
|
||||
*
|
||||
@@ -512,56 +564,4 @@ class AccountController extends Controller
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code));
|
||||
$locale = app('steam')->getLocale();
|
||||
$step = $this->calculateStep($start, $end);
|
||||
$result = [
|
||||
'label' => sprintf('%s (%s)', $account->name, $currency->symbol),
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_code' => $currency->code,
|
||||
];
|
||||
$entries = [];
|
||||
$current = clone $start;
|
||||
app('log')->debug(sprintf('Step is %s', $step));
|
||||
|
||||
// fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
|
||||
// have to make sure this chart is always based on the balance at the END of the period.
|
||||
// This period depends on the size of the chart
|
||||
$current = app('navigation')->endOfX($current, $step, null);
|
||||
app('log')->debug(sprintf('$current date is %s', $current->format('Y-m-d')));
|
||||
if ('1D' === $step) {
|
||||
// per day the entire period, balance for every day.
|
||||
$format = (string)trans('config.month_and_day_js', [], $locale);
|
||||
$range = app('steam')->balanceInRange($account, $start, $end, $currency);
|
||||
$previous = array_values($range)[0];
|
||||
while ($end >= $current) {
|
||||
$theDate = $current->format('Y-m-d');
|
||||
$balance = $range[$theDate] ?? $previous;
|
||||
$label = $current->isoFormat($format);
|
||||
$entries[$label] = (float)$balance;
|
||||
$previous = $balance;
|
||||
$current->addDay();
|
||||
}
|
||||
}
|
||||
if ('1W' === $step || '1M' === $step || '1Y' === $step) {
|
||||
while ($end >= $current) {
|
||||
app('log')->debug(sprintf('Current is: %s', $current->format('Y-m-d')));
|
||||
$balance = (float)app('steam')->balance($account, $current, $currency);
|
||||
$label = app('navigation')->periodShow($current, $step);
|
||||
$entries[$label] = $balance;
|
||||
$current = app('navigation')->addPeriod($current, $step, 0);
|
||||
// here too, to fix #8041, the data is corrected to the end of the period.
|
||||
$current = app('navigation')->endOfX($current, $step, null);
|
||||
}
|
||||
}
|
||||
$result['entries'] = $entries;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@@ -195,6 +195,23 @@ class BudgetReportController extends Controller
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
private function makeEntries(Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
|
||||
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
|
||||
$currentStart = clone $start;
|
||||
while ($currentStart <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
|
||||
$key = $currentStart->isoFormat($format);
|
||||
$return[$key] = '0';
|
||||
$currentStart = clone $currentEnd;
|
||||
$currentStart->addDay()->startOfDay();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chart that groups expenses by the account.
|
||||
*/
|
||||
@@ -224,21 +241,4 @@ class BudgetReportController extends Controller
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
private function makeEntries(Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
|
||||
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
|
||||
$currentStart = clone $start;
|
||||
while ($currentStart <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
|
||||
$key = $currentStart->isoFormat($format);
|
||||
$return[$key] = '0';
|
||||
$currentStart = clone $currentEnd;
|
||||
$currentStart->addDay()->startOfDay();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -94,6 +94,11 @@ class CategoryController extends Controller
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
private function getDate(): Carbon
|
||||
{
|
||||
return today(config('app.timezone'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the category chart on the front page.
|
||||
* TODO test method for category refactor.
|
||||
@@ -141,66 +146,6 @@ class CategoryController extends Controller
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chart for period for transactions without a category.
|
||||
* TODO test me.
|
||||
*/
|
||||
public function reportPeriodNoCategory(Collection $accounts, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.category.period.no-cat');
|
||||
$cache->addProperty($accounts->pluck('id')->toArray());
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
$data = $this->reportPeriodChart($accounts, $start, $end, null);
|
||||
|
||||
$cache->store($data);
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chart for a specific period.
|
||||
* TODO test me, for category refactor.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function specificPeriod(Category $category, Carbon $date): JsonResponse
|
||||
{
|
||||
$range = app('navigation')->getViewRange(false);
|
||||
$start = app('navigation')->startOfPeriod($date, $range);
|
||||
$end = session()->get('end');
|
||||
if ($end < $start) {
|
||||
[$end, $start] = [$start, $end];
|
||||
}
|
||||
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($category->id);
|
||||
$cache->addProperty('chart.category.period-chart');
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
|
||||
/** @var WholePeriodChartGenerator $chartGenerator */
|
||||
$chartGenerator = app(WholePeriodChartGenerator::class);
|
||||
$chartData = $chartGenerator->generate($category, $start, $end);
|
||||
$data = $this->generator->multiSet($chartData);
|
||||
|
||||
$cache->store($data);
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
private function getDate(): Carbon
|
||||
{
|
||||
return today(config('app.timezone'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate report chart for either with or without category.
|
||||
*/
|
||||
@@ -279,4 +224,59 @@ class CategoryController extends Controller
|
||||
|
||||
return $this->generator->multiSet($chartData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chart for period for transactions without a category.
|
||||
* TODO test me.
|
||||
*/
|
||||
public function reportPeriodNoCategory(Collection $accounts, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.category.period.no-cat');
|
||||
$cache->addProperty($accounts->pluck('id')->toArray());
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
$data = $this->reportPeriodChart($accounts, $start, $end, null);
|
||||
|
||||
$cache->store($data);
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chart for a specific period.
|
||||
* TODO test me, for category refactor.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function specificPeriod(Category $category, Carbon $date): JsonResponse
|
||||
{
|
||||
$range = app('navigation')->getViewRange(false);
|
||||
$start = app('navigation')->startOfPeriod($date, $range);
|
||||
$end = session()->get('end');
|
||||
if ($end < $start) {
|
||||
[$end, $start] = [$start, $end];
|
||||
}
|
||||
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($category->id);
|
||||
$cache->addProperty('chart.category.period-chart');
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
}
|
||||
|
||||
/** @var WholePeriodChartGenerator $chartGenerator */
|
||||
$chartGenerator = app(WholePeriodChartGenerator::class);
|
||||
$chartData = $chartGenerator->generate($category, $start, $end);
|
||||
$data = $this->generator->multiSet($chartData);
|
||||
|
||||
$cache->store($data);
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
}
|
||||
|
@@ -269,6 +269,26 @@ class CategoryReportController extends Controller
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate function
|
||||
*/
|
||||
private function makeEntries(Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
|
||||
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
|
||||
$currentStart = clone $start;
|
||||
while ($currentStart <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
|
||||
$key = $currentStart->isoFormat($format);
|
||||
$return[$key] = '0';
|
||||
$currentStart = clone $currentEnd;
|
||||
$currentStart->addDay()->startOfDay();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function sourceExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
$result = [];
|
||||
@@ -324,24 +344,4 @@ class CategoryReportController extends Controller
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate function
|
||||
*/
|
||||
private function makeEntries(Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
|
||||
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
|
||||
$currentStart = clone $start;
|
||||
while ($currentStart <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
|
||||
$key = $currentStart->isoFormat($format);
|
||||
$return[$key] = '0';
|
||||
$currentStart = clone $currentEnd;
|
||||
$currentStart->addDay()->startOfDay();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -212,6 +212,44 @@ class DoubleReportController extends Controller
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate function
|
||||
*/
|
||||
private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
if ($account->name === $name && $account->id !== $id) {
|
||||
return $account->name;
|
||||
}
|
||||
if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) {
|
||||
return $account->iban;
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate function
|
||||
*/
|
||||
private function makeEntries(Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
|
||||
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
|
||||
$currentStart = clone $start;
|
||||
while ($currentStart <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
|
||||
$key = $currentStart->isoFormat($format);
|
||||
$return[$key] = '0';
|
||||
$currentStart = clone $currentEnd;
|
||||
$currentStart->addDay()->startOfDay();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function tagExpense(Collection $accounts, Collection $others, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
$result = [];
|
||||
@@ -317,42 +355,4 @@ class DoubleReportController extends Controller
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate function
|
||||
*/
|
||||
private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
if ($account->name === $name && $account->id !== $id) {
|
||||
return $account->name;
|
||||
}
|
||||
if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) {
|
||||
return $account->iban;
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate function
|
||||
*/
|
||||
private function makeEntries(Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
|
||||
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
|
||||
$currentStart = clone $start;
|
||||
while ($currentStart <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
|
||||
$key = $currentStart->isoFormat($format);
|
||||
$return[$key] = '0';
|
||||
$currentStart = clone $currentEnd;
|
||||
$currentStart->addDay()->startOfDay();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -274,6 +274,26 @@ class TagReportController extends Controller
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate function
|
||||
*/
|
||||
private function makeEntries(Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
|
||||
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
|
||||
$currentStart = clone $start;
|
||||
while ($currentStart <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
|
||||
$key = $currentStart->isoFormat($format);
|
||||
$return[$key] = '0';
|
||||
$currentStart = clone $currentEnd;
|
||||
$currentStart->addDay()->startOfDay();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function sourceExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
$result = [];
|
||||
@@ -381,24 +401,4 @@ class TagReportController extends Controller
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate function
|
||||
*/
|
||||
private function makeEntries(Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
|
||||
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
|
||||
$currentStart = clone $start;
|
||||
while ($currentStart <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
|
||||
$key = $currentStart->isoFormat($format);
|
||||
$return[$key] = '0';
|
||||
$currentStart = clone $currentEnd;
|
||||
$currentStart->addDay()->startOfDay();
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,6 @@ 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;
|
||||
@@ -141,21 +140,6 @@ class DebugController extends Controller
|
||||
return view('debug', compact('table', 'now', 'logContent'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Flash all types of messages.
|
||||
*
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function testFlash(Request $request)
|
||||
{
|
||||
$request->session()->flash('success', 'This is a success message.');
|
||||
$request->session()->flash('info', 'This is an info message.');
|
||||
$request->session()->flash('warning', 'This is a warning.');
|
||||
$request->session()->flash('error', 'This is an error!');
|
||||
|
||||
return redirect(route('home'));
|
||||
}
|
||||
|
||||
private function generateTable(): string
|
||||
{
|
||||
// system information:
|
||||
@@ -178,7 +162,9 @@ class DebugController extends Controller
|
||||
'db_version' => app('fireflyconfig')->get('db_version', 1)->data,
|
||||
'php_version' => PHP_VERSION,
|
||||
'php_os' => PHP_OS,
|
||||
'uname' => php_uname('m'),
|
||||
'interface' => \PHP_SAPI,
|
||||
'bits' => \PHP_INT_SIZE * 8,
|
||||
'bcscale' => bcscale(),
|
||||
'display_errors' => ini_get('display_errors'),
|
||||
'error_reporting' => $this->errorReporting((int)ini_get('error_reporting')),
|
||||
@@ -201,6 +187,7 @@ class DebugController extends Controller
|
||||
try {
|
||||
if (file_exists('/var/www/counter-main.txt')) {
|
||||
$return['build'] = trim((string)file_get_contents('/var/www/counter-main.txt'));
|
||||
app('log')->debug(sprintf('build is now "%s"', $return['build']));
|
||||
}
|
||||
} catch (\Exception $e) { // @phpstan-ignore-line
|
||||
app('log')->debug('Could not check build counter, but thats ok.');
|
||||
@@ -335,4 +322,19 @@ class DebugController extends Controller
|
||||
|
||||
return implode(' ', $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flash all types of messages.
|
||||
*
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function testFlash(Request $request)
|
||||
{
|
||||
$request->session()->flash('success', 'This is a success message.');
|
||||
$request->session()->flash('info', 'This is an info message.');
|
||||
$request->session()->flash('warning', 'This is a warning.');
|
||||
$request->session()->flash('error', 'This is an error!');
|
||||
|
||||
return redirect(route('home'));
|
||||
}
|
||||
}
|
||||
|
@@ -78,7 +78,7 @@ class RecurrenceController extends Controller
|
||||
$weekend = (int)$request->get('weekend');
|
||||
$repetitionMoment = '';
|
||||
$skip = (int)$request->get('skip');
|
||||
$skip = $skip < 1 || $skip > 31 ? 1 : $skip;
|
||||
$skip = $skip < 0 || $skip > 31 ? 0 : $skip;
|
||||
$weekend = $weekend < 1 || $weekend > 4 ? 1 : $weekend;
|
||||
|
||||
if (false === $start || false === $end || false === $firstDate || false === $endDate) {
|
||||
|
@@ -141,24 +141,6 @@ class IndexController extends Controller
|
||||
return view('piggy-banks.index', compact('piggyBanks', 'accounts'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the order of a piggy bank.
|
||||
*/
|
||||
public function setOrder(Request $request, PiggyBank $piggyBank): JsonResponse
|
||||
{
|
||||
$objectGroupTitle = (string)$request->get('objectGroupTitle');
|
||||
$newOrder = (int)$request->get('order');
|
||||
$this->piggyRepos->setOrder($piggyBank, $newOrder);
|
||||
if ('' !== $objectGroupTitle) {
|
||||
$this->piggyRepos->setObjectGroup($piggyBank, $objectGroupTitle);
|
||||
}
|
||||
if ('' === $objectGroupTitle) {
|
||||
$this->piggyRepos->removeObjectGroup($piggyBank);
|
||||
}
|
||||
|
||||
return response()->json(['data' => 'OK']);
|
||||
}
|
||||
|
||||
private function makeSums(array $piggyBanks): array
|
||||
{
|
||||
$sums = [];
|
||||
@@ -193,4 +175,22 @@ class IndexController extends Controller
|
||||
|
||||
return $piggyBanks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the order of a piggy bank.
|
||||
*/
|
||||
public function setOrder(Request $request, PiggyBank $piggyBank): JsonResponse
|
||||
{
|
||||
$objectGroupTitle = (string)$request->get('objectGroupTitle');
|
||||
$newOrder = (int)$request->get('order');
|
||||
$this->piggyRepos->setOrder($piggyBank, $newOrder);
|
||||
if ('' !== $objectGroupTitle) {
|
||||
$this->piggyRepos->setObjectGroup($piggyBank, $objectGroupTitle);
|
||||
}
|
||||
if ('' === $objectGroupTitle) {
|
||||
$this->piggyRepos->removeObjectGroup($piggyBank);
|
||||
}
|
||||
|
||||
return response()->json(['data' => 'OK']);
|
||||
}
|
||||
}
|
||||
|
@@ -497,6 +497,47 @@ class ProfileController extends Controller
|
||||
return redirect(route('profile.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate code.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function addToMFAHistory(string $mfaCode): void
|
||||
{
|
||||
/** @var array $mfaHistory */
|
||||
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
|
||||
$entry = [
|
||||
'time' => time(),
|
||||
'code' => $mfaCode,
|
||||
];
|
||||
$mfaHistory[] = $entry;
|
||||
|
||||
app('preferences')->set('mfa_history', $mfaHistory);
|
||||
$this->filterMFAHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove old entries from the preferences array.
|
||||
*/
|
||||
private function filterMFAHistory(): void
|
||||
{
|
||||
/** @var array $mfaHistory */
|
||||
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
|
||||
$newHistory = [];
|
||||
$now = time();
|
||||
foreach ($mfaHistory as $entry) {
|
||||
$time = $entry['time'];
|
||||
$code = $entry['code'];
|
||||
if ($now - $time <= 300) {
|
||||
$newHistory[] = [
|
||||
'time' => $time,
|
||||
'code' => $code,
|
||||
];
|
||||
}
|
||||
}
|
||||
app('preferences')->set('mfa_history', $newHistory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit delete account.
|
||||
*
|
||||
@@ -631,45 +672,4 @@ class ProfileController extends Controller
|
||||
|
||||
return redirect(route('login'));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate code.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function addToMFAHistory(string $mfaCode): void
|
||||
{
|
||||
/** @var array $mfaHistory */
|
||||
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
|
||||
$entry = [
|
||||
'time' => time(),
|
||||
'code' => $mfaCode,
|
||||
];
|
||||
$mfaHistory[] = $entry;
|
||||
|
||||
app('preferences')->set('mfa_history', $mfaHistory);
|
||||
$this->filterMFAHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove old entries from the preferences array.
|
||||
*/
|
||||
private function filterMFAHistory(): void
|
||||
{
|
||||
/** @var array $mfaHistory */
|
||||
$mfaHistory = app('preferences')->get('mfa_history', [])->data;
|
||||
$newHistory = [];
|
||||
$now = time();
|
||||
foreach ($mfaHistory as $entry) {
|
||||
$time = $entry['time'];
|
||||
$code = $entry['code'];
|
||||
if ($now - $time <= 300) {
|
||||
$newHistory[] = [
|
||||
'time' => $time,
|
||||
'code' => $code,
|
||||
];
|
||||
}
|
||||
}
|
||||
app('preferences')->set('mfa_history', $newHistory);
|
||||
}
|
||||
}
|
||||
|
@@ -278,6 +278,24 @@ class DoubleController extends Controller
|
||||
return view('reports.double.partials.accounts', compact('sums', 'report'));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO this method is duplicated.
|
||||
*/
|
||||
private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
if ($account->name === $name && $account->id !== $id) {
|
||||
return $account->name;
|
||||
}
|
||||
if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) {
|
||||
return $account->iban;
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory|View
|
||||
*/
|
||||
@@ -468,22 +486,4 @@ class DoubleController extends Controller
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO this method is duplicated.
|
||||
*/
|
||||
private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
if ($account->name === $name && $account->id !== $id) {
|
||||
return $account->name;
|
||||
}
|
||||
if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) {
|
||||
return $account->iban;
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
@@ -159,7 +159,7 @@ class CreateController extends Controller
|
||||
$oldActions = $this->getActionsForBill($bill);
|
||||
|
||||
// restore actions and triggers from old input:
|
||||
if (null !== $request->old()) {
|
||||
if (null !== $request->old() && is_array($request->old()) && count($request->old()) > 0) {
|
||||
$oldTriggers = $this->getPreviousTriggers($request);
|
||||
$oldActions = $this->getPreviousActions($request);
|
||||
}
|
||||
|
@@ -132,29 +132,6 @@ class EditController extends Controller
|
||||
return view('rules.rule.edit', compact('rule', 'subTitle', 'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the rule.
|
||||
*
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function update(RuleFormRequest $request, Rule $rule)
|
||||
{
|
||||
$data = $request->getRuleData();
|
||||
|
||||
$this->ruleRepos->update($rule, $data);
|
||||
|
||||
session()->flash('success', (string)trans('firefly.updated_rule', ['title' => $rule->title]));
|
||||
app('preferences')->mark();
|
||||
$redirect = redirect($this->getPreviousUrl('rules.edit.url'));
|
||||
if (1 === (int)$request->get('return_to_edit')) {
|
||||
session()->put('rules.edit.fromUpdate', true);
|
||||
|
||||
$redirect = redirect(route('rules.edit', [$rule->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -196,4 +173,27 @@ class EditController extends Controller
|
||||
|
||||
return $renderedEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the rule.
|
||||
*
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function update(RuleFormRequest $request, Rule $rule)
|
||||
{
|
||||
$data = $request->getRuleData();
|
||||
|
||||
$this->ruleRepos->update($rule, $data);
|
||||
|
||||
session()->flash('success', (string)trans('firefly.updated_rule', ['title' => $rule->title]));
|
||||
app('preferences')->mark();
|
||||
$redirect = redirect($this->getPreviousUrl('rules.edit.url'));
|
||||
if (1 === (int)$request->get('return_to_edit')) {
|
||||
session()->put('rules.edit.fromUpdate', true);
|
||||
|
||||
$redirect = redirect(route('rules.edit', [$rule->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
}
|
||||
|
@@ -129,26 +129,6 @@ class InstallController extends Controller
|
||||
return response()->json($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create specific RSA keys.
|
||||
*/
|
||||
public function keys(): void
|
||||
{
|
||||
$key = RSA::createKey(4096);
|
||||
|
||||
[$publicKey, $privateKey] = [
|
||||
Passport::keyPath('oauth-public.key'),
|
||||
Passport::keyPath('oauth-private.key'),
|
||||
];
|
||||
|
||||
if (file_exists($publicKey) || file_exists($privateKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
file_put_contents($publicKey, (string)$key->getPublicKey());
|
||||
file_put_contents($privateKey, $key->toString('PKCS1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -173,4 +153,24 @@ class InstallController extends Controller
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create specific RSA keys.
|
||||
*/
|
||||
public function keys(): void
|
||||
{
|
||||
$key = RSA::createKey(4096);
|
||||
|
||||
[$publicKey, $privateKey] = [
|
||||
Passport::keyPath('oauth-public.key'),
|
||||
Passport::keyPath('oauth-private.key'),
|
||||
];
|
||||
|
||||
if (file_exists($publicKey) || file_exists($privateKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
file_put_contents($publicKey, (string)$key->getPublicKey());
|
||||
file_put_contents($privateKey, $key->toString('PKCS1'));
|
||||
}
|
||||
}
|
||||
|
@@ -135,38 +135,6 @@ class ConvertController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the conversion.
|
||||
*
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function postIndex(Request $request, TransactionType $destinationType, TransactionGroup $group)
|
||||
{
|
||||
if (!$this->isEditableGroup($group)) {
|
||||
return $this->redirectGroupToAccount($group);
|
||||
}
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($group->transactionJournals as $journal) {
|
||||
// catch FF exception.
|
||||
try {
|
||||
$this->convertJournal($journal, $destinationType, $request->all());
|
||||
} catch (FireflyException $e) {
|
||||
session()->flash('error', $e->getMessage());
|
||||
|
||||
return redirect()->route('transactions.convert.index', [strtolower($destinationType->type), $group->id])->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
// correct transfers:
|
||||
$group->refresh();
|
||||
|
||||
session()->flash('success', (string)trans('firefly.converted_to_'.$destinationType->type));
|
||||
event(new UpdatedTransactionGroup($group, true, true));
|
||||
|
||||
return redirect(route('transactions.show', [$group->id]));
|
||||
}
|
||||
|
||||
private function getValidDepositSources(): array
|
||||
{
|
||||
// make repositories
|
||||
@@ -291,6 +259,38 @@ class ConvertController extends Controller
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the conversion.
|
||||
*
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function postIndex(Request $request, TransactionType $destinationType, TransactionGroup $group)
|
||||
{
|
||||
if (!$this->isEditableGroup($group)) {
|
||||
return $this->redirectGroupToAccount($group);
|
||||
}
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($group->transactionJournals as $journal) {
|
||||
// catch FF exception.
|
||||
try {
|
||||
$this->convertJournal($journal, $destinationType, $request->all());
|
||||
} catch (FireflyException $e) {
|
||||
session()->flash('error', $e->getMessage());
|
||||
|
||||
return redirect()->route('transactions.convert.index', [strtolower($destinationType->type), $group->id])->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
// correct transfers:
|
||||
$group->refresh();
|
||||
|
||||
session()->flash('success', (string)trans('firefly.converted_to_'.$destinationType->type));
|
||||
event(new UpdatedTransactionGroup($group, true, true));
|
||||
|
||||
return redirect(route('transactions.show', [$group->id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
|
@@ -73,22 +73,6 @@ class Installer
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is access denied error.
|
||||
*/
|
||||
protected function isAccessDenied(string $message): bool
|
||||
{
|
||||
return false !== stripos($message, 'Access denied');
|
||||
}
|
||||
|
||||
/**
|
||||
* Is no tables exist error.
|
||||
*/
|
||||
protected function noTablesExist(string $message): bool
|
||||
{
|
||||
return false !== stripos($message, 'Base table or view not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the tables are created and accounted for.
|
||||
*
|
||||
@@ -125,6 +109,22 @@ class Installer
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is access denied error.
|
||||
*/
|
||||
protected function isAccessDenied(string $message): bool
|
||||
{
|
||||
return false !== stripos($message, 'Access denied');
|
||||
}
|
||||
|
||||
/**
|
||||
* Is no tables exist error.
|
||||
*/
|
||||
protected function noTablesExist(string $message): bool
|
||||
{
|
||||
return false !== stripos($message, 'Base table or view not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the "db_version" variable is correct.
|
||||
*/
|
||||
|
41
app/Http/Middleware/TrustHosts.php
Normal file
41
app/Http/Middleware/TrustHosts.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/*
|
||||
* TrustHosts.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\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||
|
||||
class TrustHosts extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the host patterns that should be trusted.
|
||||
*
|
||||
* @return array<int, null|string>
|
||||
*/
|
||||
public function hosts(): array
|
||||
{
|
||||
return [
|
||||
$this->allSubdomainsOfApplicationUrl(),
|
||||
];
|
||||
}
|
||||
}
|
@@ -150,6 +150,38 @@ class RecurrenceFormRequest extends FormRequest
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses repetition data.
|
||||
*/
|
||||
private function parseRepetitionData(): array
|
||||
{
|
||||
$value = $this->convertString('repetition_type');
|
||||
$return = [
|
||||
'type' => '',
|
||||
'moment' => '',
|
||||
];
|
||||
|
||||
if ('daily' === $value) {
|
||||
$return['type'] = $value;
|
||||
}
|
||||
// monthly,17
|
||||
// ndom,3,7
|
||||
if (in_array(substr($value, 0, 6), ['yearly', 'weekly'], true)) {
|
||||
$return['type'] = substr($value, 0, 6);
|
||||
$return['moment'] = substr($value, 7);
|
||||
}
|
||||
if (str_starts_with($value, 'monthly')) {
|
||||
$return['type'] = substr($value, 0, 7);
|
||||
$return['moment'] = substr($value, 8);
|
||||
}
|
||||
if (str_starts_with($value, 'ndom')) {
|
||||
$return['type'] = substr($value, 0, 4);
|
||||
$return['moment'] = substr($value, 5);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules for this request.
|
||||
*/
|
||||
@@ -316,36 +348,4 @@ class RecurrenceFormRequest extends FormRequest
|
||||
$validator->errors()->add('withdrawal_destination_id', $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses repetition data.
|
||||
*/
|
||||
private function parseRepetitionData(): array
|
||||
{
|
||||
$value = $this->convertString('repetition_type');
|
||||
$return = [
|
||||
'type' => '',
|
||||
'moment' => '',
|
||||
];
|
||||
|
||||
if ('daily' === $value) {
|
||||
$return['type'] = $value;
|
||||
}
|
||||
// monthly,17
|
||||
// ndom,3,7
|
||||
if (in_array(substr($value, 0, 6), ['yearly', 'weekly'], true)) {
|
||||
$return['type'] = substr($value, 0, 6);
|
||||
$return['moment'] = substr($value, 7);
|
||||
}
|
||||
if (str_starts_with($value, 'monthly')) {
|
||||
$return['type'] = substr($value, 0, 7);
|
||||
$return['moment'] = substr($value, 8);
|
||||
}
|
||||
if (str_starts_with($value, 'ndom')) {
|
||||
$return['type'] = substr($value, 0, 4);
|
||||
$return['moment'] = substr($value, 5);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -58,6 +58,28 @@ class RuleFormRequest extends FormRequest
|
||||
];
|
||||
}
|
||||
|
||||
private function getRuleTriggerData(): array
|
||||
{
|
||||
$return = [];
|
||||
$triggerData = $this->get('triggers');
|
||||
if (is_array($triggerData)) {
|
||||
foreach ($triggerData as $trigger) {
|
||||
$stopProcessing = $trigger['stop_processing'] ?? '0';
|
||||
$prohibited = $trigger['prohibited'] ?? '0';
|
||||
$set = [
|
||||
'type' => $trigger['type'] ?? 'invalid',
|
||||
'value' => $trigger['value'] ?? '',
|
||||
'stop_processing' => 1 === (int)$stopProcessing,
|
||||
'prohibited' => 1 === (int)$prohibited,
|
||||
];
|
||||
$set = self::replaceAmountTrigger($set);
|
||||
$return[] = $set;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public static function replaceAmountTrigger(array $array): array
|
||||
{
|
||||
// do some sneaky search and replace.
|
||||
@@ -83,6 +105,24 @@ class RuleFormRequest extends FormRequest
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function getRuleActionData(): array
|
||||
{
|
||||
$return = [];
|
||||
$actionData = $this->get('actions');
|
||||
if (is_array($actionData)) {
|
||||
foreach ($actionData as $action) {
|
||||
$stopProcessing = $action['stop_processing'] ?? '0';
|
||||
$return[] = [
|
||||
'type' => $action['type'] ?? 'invalid',
|
||||
'value' => $action['value'] ?? '',
|
||||
'stop_processing' => 1 === (int)$stopProcessing,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rules for this request.
|
||||
*/
|
||||
@@ -127,44 +167,4 @@ class RuleFormRequest extends FormRequest
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
private function getRuleTriggerData(): array
|
||||
{
|
||||
$return = [];
|
||||
$triggerData = $this->get('triggers');
|
||||
if (is_array($triggerData)) {
|
||||
foreach ($triggerData as $trigger) {
|
||||
$stopProcessing = $trigger['stop_processing'] ?? '0';
|
||||
$prohibited = $trigger['prohibited'] ?? '0';
|
||||
$set = [
|
||||
'type' => $trigger['type'] ?? 'invalid',
|
||||
'value' => $trigger['value'] ?? '',
|
||||
'stop_processing' => 1 === (int) $stopProcessing,
|
||||
'prohibited' => 1 === (int) $prohibited,
|
||||
];
|
||||
$set = self::replaceAmountTrigger($set);
|
||||
$return[] = $set;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleActionData(): array
|
||||
{
|
||||
$return = [];
|
||||
$actionData = $this->get('actions');
|
||||
if (is_array($actionData)) {
|
||||
foreach ($actionData as $action) {
|
||||
$stopProcessing = $action['stop_processing'] ?? '0';
|
||||
$return[] = [
|
||||
'type' => $action['type'] ?? 'invalid',
|
||||
'value' => $action['value'] ?? '',
|
||||
'stop_processing' => 1 === (int) $stopProcessing,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -77,13 +77,6 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
public function setDate(Carbon $date): void
|
||||
{
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -362,4 +355,11 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
}
|
||||
app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
}
|
||||
|
||||
public function setDate(Carbon $date): void
|
||||
{
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
}
|
||||
|
@@ -152,23 +152,6 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
app('preferences')->mark();
|
||||
}
|
||||
|
||||
public function setDate(Carbon $date): void
|
||||
{
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
|
||||
public function setForce(bool $force): void
|
||||
{
|
||||
$this->force = $force;
|
||||
}
|
||||
|
||||
public function setRecurrences(Collection $recurrences): void
|
||||
{
|
||||
$this->recurrences = $recurrences;
|
||||
}
|
||||
|
||||
private function filterRecurrences(Collection $recurrences): Collection
|
||||
{
|
||||
return $recurrences->filter(
|
||||
@@ -465,4 +448,21 @@ class CreateRecurringTransactions implements ShouldQueue
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function setDate(Carbon $date): void
|
||||
{
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
|
||||
public function setForce(bool $force): void
|
||||
{
|
||||
$this->force = $force;
|
||||
}
|
||||
|
||||
public function setRecurrences(Collection $recurrences): void
|
||||
{
|
||||
$this->recurrences = $recurrences;
|
||||
}
|
||||
}
|
||||
|
@@ -88,13 +88,6 @@ class DownloadExchangeRates implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
public function setDate(Carbon $date): void
|
||||
{
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
@@ -185,4 +178,11 @@ class DownloadExchangeRates implements ShouldQueue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setDate(Carbon $date): void
|
||||
{
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
}
|
||||
|
@@ -92,18 +92,6 @@ class WarnAboutBills implements ShouldQueue
|
||||
app('preferences')->mark();
|
||||
}
|
||||
|
||||
public function setDate(Carbon $date): void
|
||||
{
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
|
||||
public function setForce(bool $force): void
|
||||
{
|
||||
$this->force = $force;
|
||||
}
|
||||
|
||||
private function hasDateFields(Bill $bill): bool
|
||||
{
|
||||
if (false === $bill->active) {
|
||||
@@ -149,4 +137,16 @@ class WarnAboutBills implements ShouldQueue
|
||||
app('log')->debug('Will now send warning!');
|
||||
event(new WarnUserAboutBill($bill, $field, $diff));
|
||||
}
|
||||
|
||||
public function setDate(Carbon $date): void
|
||||
{
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
}
|
||||
|
||||
public function setForce(bool $force): void
|
||||
{
|
||||
$this->force = $force;
|
||||
}
|
||||
}
|
||||
|
@@ -63,7 +63,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
* @method static Builder|Location whereUpdatedAt($value)
|
||||
* @method static Builder|Location whereZoomLevel($value)
|
||||
*
|
||||
* @property Collection<int, \FireflyIII\Models\TransactionJournal> $transactionJournals
|
||||
* @property Collection<int, TransactionJournal> $transactionJournals
|
||||
* @property null|int $transaction_journals_count
|
||||
*
|
||||
* @mixin Eloquent
|
||||
@@ -101,11 +101,6 @@ class Location extends Model
|
||||
return $this->morphMany(Account::class, 'locatable');
|
||||
}
|
||||
|
||||
public function transactionJournals(): MorphMany
|
||||
{
|
||||
return $this->morphMany(TransactionJournal::class, 'locatable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the owning attachable models.
|
||||
*/
|
||||
@@ -114,6 +109,11 @@ class Location extends Model
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function transactionJournals(): MorphMany
|
||||
{
|
||||
return $this->morphMany(TransactionJournal::class, 'locatable');
|
||||
}
|
||||
|
||||
protected function locatableId(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
@@ -112,11 +112,11 @@ class RuleActionFailed extends Notification
|
||||
$slackUrl = '';
|
||||
}
|
||||
if (UrlValidator::isValidWebhookURL((string)$slackUrl)) {
|
||||
app('log')->debug('Will send ruleActionFailed through Slack!');
|
||||
app('log')->debug('Will send ruleActionFailed through Slack or Discord!');
|
||||
|
||||
return ['slack'];
|
||||
}
|
||||
app('log')->debug('Will NOT send ruleActionFailed through Slack');
|
||||
app('log')->debug('Will NOT send ruleActionFailed through Slack or Discord');
|
||||
|
||||
return [];
|
||||
}
|
||||
|
@@ -138,41 +138,6 @@ class AccountTasker implements AccountTaskerInterface
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array
|
||||
{
|
||||
// get all incomes for the given accounts in the given period!
|
||||
// also transfers!
|
||||
// get all transactions:
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setDestinationAccounts($accounts)->setRange($start, $end);
|
||||
$collector->excludeSourceAccounts($accounts);
|
||||
$collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])->withAccountInformation();
|
||||
$report = $this->groupIncomeBySource($collector->getExtractedJournals());
|
||||
|
||||
// sort the result
|
||||
// Obtain a list of columns
|
||||
$sum = [];
|
||||
foreach ($report['accounts'] as $accountId => $row) {
|
||||
$sum[$accountId] = (float)$row['sum']; // intentional float
|
||||
}
|
||||
|
||||
array_multisort($sum, SORT_DESC, $report['accounts']);
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
public function setUser(null|Authenticatable|User $user): void
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -233,6 +198,34 @@ class AccountTasker implements AccountTaskerInterface
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getIncomeReport(Carbon $start, Carbon $end, Collection $accounts): array
|
||||
{
|
||||
// get all incomes for the given accounts in the given period!
|
||||
// also transfers!
|
||||
// get all transactions:
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setDestinationAccounts($accounts)->setRange($start, $end);
|
||||
$collector->excludeSourceAccounts($accounts);
|
||||
$collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])->withAccountInformation();
|
||||
$report = $this->groupIncomeBySource($collector->getExtractedJournals());
|
||||
|
||||
// sort the result
|
||||
// Obtain a list of columns
|
||||
$sum = [];
|
||||
foreach ($report['accounts'] as $accountId => $row) {
|
||||
$sum[$accountId] = (float)$row['sum']; // intentional float
|
||||
}
|
||||
|
||||
array_multisort($sum, SORT_DESC, $report['accounts']);
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -291,4 +284,11 @@ class AccountTasker implements AccountTaskerInterface
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
public function setUser(null|Authenticatable|User $user): void
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -50,122 +50,6 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
return $this->sortByCurrency($journals, 'negative');
|
||||
}
|
||||
|
||||
public function setUser(null|Authenticatable|User $user): void
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a list of all the deposit transaction journals (as arrays) set in that period
|
||||
* which have the specified accounts. It's grouped per currency, with as few details in the array
|
||||
* as possible. Amounts are always positive.
|
||||
*/
|
||||
public function listIncome(Carbon $start, Carbon $end, ?Collection $accounts = null): array
|
||||
{
|
||||
$journals = $this->getTransactions($start, $end, $accounts, TransactionType::DEPOSIT);
|
||||
|
||||
return $this->sortByCurrency($journals, 'positive');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumExpenses(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $expense = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::WITHDRAWAL, $start, $end, $accounts, $expense, $currency);
|
||||
|
||||
return $this->groupByCurrency($journals, 'negative');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumExpensesByDestination(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $expense = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::WITHDRAWAL, $start, $end, $accounts, $expense, $currency);
|
||||
|
||||
return $this->groupByDirection($journals, 'destination', 'negative');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumExpensesBySource(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $expense = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::WITHDRAWAL, $start, $end, $accounts, $expense, $currency);
|
||||
|
||||
return $this->groupByDirection($journals, 'source', 'negative');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumIncome(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $revenue = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency);
|
||||
|
||||
return $this->groupByCurrency($journals, 'positive');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumIncomeByDestination(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $revenue = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency);
|
||||
|
||||
return $this->groupByDirection($journals, 'destination', 'positive');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumIncomeBySource(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $revenue = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency);
|
||||
|
||||
return $this->groupByDirection($journals, 'source', 'positive');
|
||||
}
|
||||
|
||||
public function sumTransfers(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array
|
||||
{
|
||||
$journals = $this->getTransactionsForSum(TransactionType::TRANSFER, $start, $end, $accounts, null, $currency);
|
||||
|
||||
return $this->groupByEither($journals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect transactions with some parameters
|
||||
*/
|
||||
@@ -180,6 +64,13 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
return $collector->getExtractedJournals();
|
||||
}
|
||||
|
||||
public function setUser(null|Authenticatable|User $user): void
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
|
||||
private function sortByCurrency(array $journals, string $direction): array
|
||||
{
|
||||
$array = [];
|
||||
@@ -216,6 +107,33 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a list of all the deposit transaction journals (as arrays) set in that period
|
||||
* which have the specified accounts. It's grouped per currency, with as few details in the array
|
||||
* as possible. Amounts are always positive.
|
||||
*/
|
||||
public function listIncome(Carbon $start, Carbon $end, ?Collection $accounts = null): array
|
||||
{
|
||||
$journals = $this->getTransactions($start, $end, $accounts, TransactionType::DEPOSIT);
|
||||
|
||||
return $this->sortByCurrency($journals, 'positive');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumExpenses(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $expense = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::WITHDRAWAL, $start, $end, $accounts, $expense, $currency);
|
||||
|
||||
return $this->groupByCurrency($journals, 'negative');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
@@ -329,6 +247,21 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumExpensesByDestination(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $expense = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::WITHDRAWAL, $start, $end, $accounts, $expense, $currency);
|
||||
|
||||
return $this->groupByDirection($journals, 'destination', 'negative');
|
||||
}
|
||||
|
||||
private function groupByDirection(array $journals, string $direction, string $method): array
|
||||
{
|
||||
$array = [];
|
||||
@@ -369,6 +302,73 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumExpensesBySource(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $expense = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::WITHDRAWAL, $start, $end, $accounts, $expense, $currency);
|
||||
|
||||
return $this->groupByDirection($journals, 'source', 'negative');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumIncome(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $revenue = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency);
|
||||
|
||||
return $this->groupByCurrency($journals, 'positive');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumIncomeByDestination(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $revenue = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency);
|
||||
|
||||
return $this->groupByDirection($journals, 'destination', 'positive');
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function sumIncomeBySource(
|
||||
Carbon $start,
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $revenue = null,
|
||||
?TransactionCurrency $currency = null
|
||||
): array {
|
||||
$journals = $this->getTransactionsForSum(TransactionType::DEPOSIT, $start, $end, $accounts, $revenue, $currency);
|
||||
|
||||
return $this->groupByDirection($journals, 'source', 'positive');
|
||||
}
|
||||
|
||||
public function sumTransfers(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array
|
||||
{
|
||||
$journals = $this->getTransactionsForSum(TransactionType::TRANSFER, $start, $end, $accounts, null, $currency);
|
||||
|
||||
return $this->groupByEither($journals);
|
||||
}
|
||||
|
||||
private function groupByEither(array $journals): array
|
||||
{
|
||||
$return = [];
|
||||
|
@@ -201,8 +201,8 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
|
||||
$availableBudget = new AvailableBudget();
|
||||
$availableBudget->user()->associate($this->user);
|
||||
$availableBudget->transactionCurrency()->associate($currency);
|
||||
$availableBudget->start_date = $start->startOfDay();
|
||||
$availableBudget->end_date = $end->endOfDay();
|
||||
$availableBudget->start_date = $start->startOfDay()->format('Y-m-d'); // @phpstan-ignore-line
|
||||
$availableBudget->end_date = $end->endOfDay()->format('Y-m-d'); // @phpstan-ignore-line
|
||||
}
|
||||
$availableBudget->amount = $amount;
|
||||
$availableBudget->save();
|
||||
@@ -234,8 +234,8 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
|
||||
'user_group_id' => $this->user->user_group_id,
|
||||
'transaction_currency_id' => $data['currency_id'],
|
||||
'amount' => $data['amount'],
|
||||
'start_date' => $start,
|
||||
'end_date' => $end,
|
||||
'start_date' => $start->format('Y-m-d'),
|
||||
'end_date' => $end->format('Y-m-d'),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -256,7 +256,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
|
||||
$start = $data['start'];
|
||||
if ($start instanceof Carbon) {
|
||||
$start = $data['start']->startOfDay();
|
||||
$availableBudget->start_date = $start;
|
||||
$availableBudget->start_date = $start->format('Y-m-d');
|
||||
$availableBudget->save();
|
||||
}
|
||||
}
|
||||
@@ -265,7 +265,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
|
||||
$end = $data['end'];
|
||||
if ($end instanceof Carbon) {
|
||||
$end = $data['end']->endOfDay();
|
||||
$availableBudget->end_date = $end;
|
||||
$availableBudget->end_date = $end->format('Y-m-d');
|
||||
$availableBudget->save();
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,6 @@ use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Destroy\BudgetDestroyService;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
@@ -170,6 +169,40 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many days of this budget limit are between start and end?
|
||||
*/
|
||||
private function daysInOverlap(BudgetLimit $limit, Carbon $start, Carbon $end): int
|
||||
{
|
||||
// start1 = $start
|
||||
// start2 = $limit->start_date
|
||||
// start1 = $end
|
||||
// start2 = $limit->end_date
|
||||
|
||||
// limit is larger than start and end (inclusive)
|
||||
// |-----------|
|
||||
// |----------------|
|
||||
if ($start->gte($limit->start_date) && $end->lte($limit->end_date)) {
|
||||
return $start->diffInDays($end) + 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
|
||||
}
|
||||
// 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 0;
|
||||
}
|
||||
|
||||
public function budgetedInPeriodForBudget(Budget $budget, Carbon $start, Carbon $end): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in budgetedInPeriod(#%d, "%s", "%s")', $budget->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
@@ -291,11 +324,115 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
return $budget;
|
||||
}
|
||||
|
||||
private function updateRuleActions(string $oldName, string $newName): void
|
||||
{
|
||||
$types = ['set_budget'];
|
||||
$actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id')
|
||||
->where('rules.user_id', $this->user->id)
|
||||
->whereIn('rule_actions.action_type', $types)
|
||||
->where('rule_actions.action_value', $oldName)
|
||||
->get(['rule_actions.*'])
|
||||
;
|
||||
app('log')->debug(sprintf('Found %d actions to update.', $actions->count()));
|
||||
|
||||
/** @var RuleAction $action */
|
||||
foreach ($actions as $action) {
|
||||
$action->action_value = $newName;
|
||||
$action->save();
|
||||
app('log')->debug(sprintf('Updated action %d: %s', $action->id, $action->action_value));
|
||||
}
|
||||
}
|
||||
|
||||
private function updateRuleTriggers(string $oldName, string $newName): void
|
||||
{
|
||||
$types = ['budget_is'];
|
||||
$triggers = RuleTrigger::leftJoin('rules', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rules.user_id', $this->user->id)
|
||||
->whereIn('rule_triggers.trigger_type', $types)
|
||||
->where('rule_triggers.trigger_value', $oldName)
|
||||
->get(['rule_triggers.*'])
|
||||
;
|
||||
app('log')->debug(sprintf('Found %d triggers to update.', $triggers->count()));
|
||||
|
||||
/** @var RuleTrigger $trigger */
|
||||
foreach ($triggers as $trigger) {
|
||||
$trigger->trigger_value = $newName;
|
||||
$trigger->save();
|
||||
app('log')->debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value));
|
||||
}
|
||||
}
|
||||
|
||||
private function setNoteText(Budget $budget, string $text): void
|
||||
{
|
||||
$dbNote = $budget->notes()->first();
|
||||
if ('' !== $text) {
|
||||
if (null === $dbNote) {
|
||||
$dbNote = new Note();
|
||||
$dbNote->noteable()->associate($budget);
|
||||
}
|
||||
$dbNote->text = trim($text);
|
||||
$dbNote->save();
|
||||
|
||||
return;
|
||||
}
|
||||
if (null !== $dbNote) {
|
||||
$dbNote->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function getAutoBudget(Budget $budget): ?AutoBudget
|
||||
{
|
||||
return $budget->autoBudgets()->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function updateAutoBudget(Budget $budget, array $data): void
|
||||
{
|
||||
// update or create auto-budget:
|
||||
$autoBudget = $this->getAutoBudget($budget);
|
||||
|
||||
// grab default currency:
|
||||
$currency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup);
|
||||
|
||||
if (null === $autoBudget) {
|
||||
// at this point it's a blind assumption auto_budget_type is 1 or 2.
|
||||
$autoBudget = new AutoBudget();
|
||||
$autoBudget->auto_budget_type = $data['auto_budget_type'];
|
||||
$autoBudget->budget_id = $budget->id;
|
||||
$autoBudget->transaction_currency_id = $currency->id;
|
||||
}
|
||||
|
||||
// set or update the currency.
|
||||
if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) {
|
||||
/** @var CurrencyRepositoryInterface $repos */
|
||||
$repos = app(CurrencyRepositoryInterface::class);
|
||||
$currencyId = (int)($data['currency_id'] ?? 0);
|
||||
$currencyCode = (string)($data['currency_code'] ?? '');
|
||||
$currency = $repos->find($currencyId);
|
||||
if (null === $currency) {
|
||||
$currency = $repos->findByCode($currencyCode);
|
||||
}
|
||||
if (null !== $currency) {
|
||||
$autoBudget->transaction_currency_id = $currency->id;
|
||||
}
|
||||
}
|
||||
|
||||
// change values if submitted or presented:
|
||||
if (array_key_exists('auto_budget_type', $data)) {
|
||||
$autoBudget->auto_budget_type = $data['auto_budget_type'];
|
||||
}
|
||||
if (array_key_exists('auto_budget_amount', $data)) {
|
||||
$autoBudget->amount = $data['auto_budget_amount'];
|
||||
}
|
||||
if (array_key_exists('auto_budget_period', $data)) {
|
||||
$autoBudget->period = $data['auto_budget_period'];
|
||||
}
|
||||
|
||||
$autoBudget->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a budget or return NULL
|
||||
*
|
||||
@@ -676,142 +813,4 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
{
|
||||
return (int)$this->user->budgets()->max('order');
|
||||
}
|
||||
|
||||
/**
|
||||
* How many days of this budget limit are between start and end?
|
||||
*/
|
||||
private function daysInOverlap(BudgetLimit $limit, Carbon $start, Carbon $end): int
|
||||
{
|
||||
// start1 = $start
|
||||
// start2 = $limit->start_date
|
||||
// start1 = $end
|
||||
// start2 = $limit->end_date
|
||||
|
||||
// limit is larger than start and end (inclusive)
|
||||
// |-----------|
|
||||
// |----------------|
|
||||
if ($start->gte($limit->start_date) && $end->lte($limit->end_date)) {
|
||||
return $start->diffInDays($end) + 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
|
||||
}
|
||||
// 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 0;
|
||||
}
|
||||
|
||||
private function updateRuleActions(string $oldName, string $newName): void
|
||||
{
|
||||
$types = ['set_budget'];
|
||||
$actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id')
|
||||
->where('rules.user_id', $this->user->id)
|
||||
->whereIn('rule_actions.action_type', $types)
|
||||
->where('rule_actions.action_value', $oldName)
|
||||
->get(['rule_actions.*'])
|
||||
;
|
||||
app('log')->debug(sprintf('Found %d actions to update.', $actions->count()));
|
||||
|
||||
/** @var RuleAction $action */
|
||||
foreach ($actions as $action) {
|
||||
$action->action_value = $newName;
|
||||
$action->save();
|
||||
app('log')->debug(sprintf('Updated action %d: %s', $action->id, $action->action_value));
|
||||
}
|
||||
}
|
||||
|
||||
private function updateRuleTriggers(string $oldName, string $newName): void
|
||||
{
|
||||
$types = ['budget_is'];
|
||||
$triggers = RuleTrigger::leftJoin('rules', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rules.user_id', $this->user->id)
|
||||
->whereIn('rule_triggers.trigger_type', $types)
|
||||
->where('rule_triggers.trigger_value', $oldName)
|
||||
->get(['rule_triggers.*'])
|
||||
;
|
||||
app('log')->debug(sprintf('Found %d triggers to update.', $triggers->count()));
|
||||
|
||||
/** @var RuleTrigger $trigger */
|
||||
foreach ($triggers as $trigger) {
|
||||
$trigger->trigger_value = $newName;
|
||||
$trigger->save();
|
||||
app('log')->debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value));
|
||||
}
|
||||
}
|
||||
|
||||
private function setNoteText(Budget $budget, string $text): void
|
||||
{
|
||||
$dbNote = $budget->notes()->first();
|
||||
if ('' !== $text) {
|
||||
if (null === $dbNote) {
|
||||
$dbNote = new Note();
|
||||
$dbNote->noteable()->associate($budget);
|
||||
}
|
||||
$dbNote->text = trim($text);
|
||||
$dbNote->save();
|
||||
|
||||
return;
|
||||
}
|
||||
if (null !== $dbNote) {
|
||||
$dbNote->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function updateAutoBudget(Budget $budget, array $data): void
|
||||
{
|
||||
// update or create auto-budget:
|
||||
$autoBudget = $this->getAutoBudget($budget);
|
||||
|
||||
// grab default currency:
|
||||
$currency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup);
|
||||
|
||||
if (null === $autoBudget) {
|
||||
// at this point it's a blind assumption auto_budget_type is 1 or 2.
|
||||
$autoBudget = new AutoBudget();
|
||||
$autoBudget->auto_budget_type = $data['auto_budget_type'];
|
||||
$autoBudget->budget_id = $budget->id;
|
||||
$autoBudget->transaction_currency_id = $currency->id;
|
||||
}
|
||||
|
||||
// set or update the currency.
|
||||
if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) {
|
||||
/** @var CurrencyRepositoryInterface $repos */
|
||||
$repos = app(CurrencyRepositoryInterface::class);
|
||||
$currencyId = (int) ($data['currency_id'] ?? 0);
|
||||
$currencyCode = (string) ($data['currency_code'] ?? '');
|
||||
$currency = $repos->find($currencyId);
|
||||
if (null === $currency) {
|
||||
$currency = $repos->findByCode($currencyCode);
|
||||
}
|
||||
if (null !== $currency) {
|
||||
$autoBudget->transaction_currency_id = $currency->id;
|
||||
}
|
||||
}
|
||||
|
||||
// change values if submitted or presented:
|
||||
if (array_key_exists('auto_budget_type', $data)) {
|
||||
$autoBudget->auto_budget_type = $data['auto_budget_type'];
|
||||
}
|
||||
if (array_key_exists('auto_budget_amount', $data)) {
|
||||
$autoBudget->amount = $data['auto_budget_amount'];
|
||||
}
|
||||
if (array_key_exists('auto_budget_period', $data)) {
|
||||
$autoBudget->period = $data['auto_budget_period'];
|
||||
}
|
||||
|
||||
$autoBudget->save();
|
||||
}
|
||||
}
|
||||
|
@@ -189,6 +189,14 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function getBudgets(): Collection
|
||||
{
|
||||
/** @var BudgetRepositoryInterface $repos */
|
||||
$repos = app(BudgetRepositoryInterface::class);
|
||||
|
||||
return $repos->getActiveBudgets();
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
@@ -288,12 +296,4 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function getBudgets(): Collection
|
||||
{
|
||||
/** @var BudgetRepositoryInterface $repos */
|
||||
$repos = app(BudgetRepositoryInterface::class);
|
||||
|
||||
return $repos->getActiveBudgets();
|
||||
}
|
||||
}
|
||||
|
@@ -210,6 +210,34 @@ class CategoryRepository implements CategoryRepositoryInterface
|
||||
return $firstJournalDate;
|
||||
}
|
||||
|
||||
private function getFirstJournalDate(Category $category): ?Carbon
|
||||
{
|
||||
$query = $category->transactionJournals()->orderBy('date', 'ASC');
|
||||
$result = $query->first(['transaction_journals.*']);
|
||||
|
||||
if (null !== $result) {
|
||||
return $result->date;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getFirstTransactionDate(Category $category): ?Carbon
|
||||
{
|
||||
// check transactions:
|
||||
$query = $category->transactions()
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->orderBy('transaction_journals.date', 'ASC')
|
||||
;
|
||||
|
||||
$lastTransaction = $query->first(['transaction_journals.*']);
|
||||
if (null !== $lastTransaction) {
|
||||
return new Carbon($lastTransaction->date);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getAttachments(Category $category): Collection
|
||||
{
|
||||
$set = $category->attachments()->get();
|
||||
@@ -271,56 +299,6 @@ class CategoryRepository implements CategoryRepositoryInterface
|
||||
return $lastJournalDate;
|
||||
}
|
||||
|
||||
public function searchCategory(string $query, int $limit): Collection
|
||||
{
|
||||
$search = $this->user->categories();
|
||||
if ('' !== $query) {
|
||||
$search->where('name', 'LIKE', sprintf('%%%s%%', $query));
|
||||
}
|
||||
|
||||
return $search->take($limit)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update(Category $category, array $data): Category
|
||||
{
|
||||
/** @var CategoryUpdateService $service */
|
||||
$service = app(CategoryUpdateService::class);
|
||||
$service->setUser($this->user);
|
||||
|
||||
return $service->update($category, $data);
|
||||
}
|
||||
|
||||
private function getFirstJournalDate(Category $category): ?Carbon
|
||||
{
|
||||
$query = $category->transactionJournals()->orderBy('date', 'ASC');
|
||||
$result = $query->first(['transaction_journals.*']);
|
||||
|
||||
if (null !== $result) {
|
||||
return $result->date;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getFirstTransactionDate(Category $category): ?Carbon
|
||||
{
|
||||
// check transactions:
|
||||
$query = $category->transactions()
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->orderBy('transaction_journals.date', 'ASC')
|
||||
;
|
||||
|
||||
$lastTransaction = $query->first(['transaction_journals.*']);
|
||||
if (null !== $lastTransaction) {
|
||||
return new Carbon($lastTransaction->date);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getLastJournalDate(Category $category, Collection $accounts): ?Carbon
|
||||
{
|
||||
$query = $category->transactionJournals()->orderBy('date', 'DESC');
|
||||
@@ -361,4 +339,26 @@ class CategoryRepository implements CategoryRepositoryInterface
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function searchCategory(string $query, int $limit): Collection
|
||||
{
|
||||
$search = $this->user->categories();
|
||||
if ('' !== $query) {
|
||||
$search->where('name', 'LIKE', sprintf('%%%s%%', $query));
|
||||
}
|
||||
|
||||
return $search->take($limit)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update(Category $category, array $data): Category
|
||||
{
|
||||
/** @var CategoryUpdateService $service */
|
||||
$service = app(CategoryUpdateService::class);
|
||||
$service->setUser($this->user);
|
||||
|
||||
return $service->update($category, $data);
|
||||
}
|
||||
}
|
||||
|
@@ -115,6 +115,14 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the categories belonging to a user.
|
||||
*/
|
||||
private function getCategories(): Collection
|
||||
{
|
||||
return $this->user->categories()->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a list of all the deposit transaction journals (as arrays) set in that period
|
||||
* which have the specified category set to them. It's grouped per currency, with as few details in the array
|
||||
@@ -420,12 +428,4 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the categories belonging to a user.
|
||||
*/
|
||||
private function getCategories(): Collection
|
||||
{
|
||||
return $this->user->categories()->get();
|
||||
}
|
||||
}
|
||||
|
@@ -245,6 +245,25 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function setNoteText(TransactionJournalLink $link, string $text): void
|
||||
{
|
||||
$dbNote = $link->notes()->first();
|
||||
if ('' !== $text) {
|
||||
if (null === $dbNote) {
|
||||
$dbNote = new Note();
|
||||
$dbNote->noteable()->associate($link);
|
||||
}
|
||||
$dbNote->text = trim($text);
|
||||
$dbNote->save();
|
||||
|
||||
return;
|
||||
}
|
||||
$dbNote?->delete();
|
||||
}
|
||||
|
||||
public function switchLinkById(int $linkId): bool
|
||||
{
|
||||
/** @var null|TransactionJournalLink $link */
|
||||
@@ -293,23 +312,4 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface
|
||||
|
||||
return $journalLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function setNoteText(TransactionJournalLink $link, string $text): void
|
||||
{
|
||||
$dbNote = $link->notes()->first();
|
||||
if ('' !== $text) {
|
||||
if (null === $dbNote) {
|
||||
$dbNote = new Note();
|
||||
$dbNote->noteable()->associate($link);
|
||||
}
|
||||
$dbNote->text = trim($text);
|
||||
$dbNote->save();
|
||||
|
||||
return;
|
||||
}
|
||||
$dbNote?->delete();
|
||||
}
|
||||
}
|
||||
|
@@ -279,6 +279,25 @@ trait ModifiesPiggyBanks
|
||||
return true;
|
||||
}
|
||||
|
||||
private function updateNote(PiggyBank $piggyBank, string $note): bool
|
||||
{
|
||||
if ('' === $note) {
|
||||
$dbNote = $piggyBank->notes()->first();
|
||||
$dbNote?->delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
$dbNote = $piggyBank->notes()->first();
|
||||
if (null === $dbNote) {
|
||||
$dbNote = new Note();
|
||||
$dbNote->noteable()->associate($piggyBank);
|
||||
}
|
||||
$dbNote->text = trim($note);
|
||||
$dbNote->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function update(PiggyBank $piggyBank, array $data): PiggyBank
|
||||
{
|
||||
$piggyBank = $this->updateProperties($piggyBank, $data);
|
||||
@@ -339,25 +358,6 @@ trait ModifiesPiggyBanks
|
||||
return $piggyBank;
|
||||
}
|
||||
|
||||
private function updateNote(PiggyBank $piggyBank, string $note): bool
|
||||
{
|
||||
if ('' === $note) {
|
||||
$dbNote = $piggyBank->notes()->first();
|
||||
$dbNote?->delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
$dbNote = $piggyBank->notes()->first();
|
||||
if (null === $dbNote) {
|
||||
$dbNote = new Note();
|
||||
$dbNote->noteable()->associate($piggyBank);
|
||||
}
|
||||
$dbNote->text = trim($note);
|
||||
$dbNote->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function updateProperties(PiggyBank $piggyBank, array $data): PiggyBank
|
||||
{
|
||||
if (array_key_exists('name', $data) && '' !== $data['name']) {
|
||||
|
@@ -400,6 +400,21 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
return $this->filterMaxDate($repeatUntil, $occurrences);
|
||||
}
|
||||
|
||||
private function filterMaxDate(?Carbon $max, array $occurrences): array
|
||||
{
|
||||
if (null === $max) {
|
||||
return $occurrences;
|
||||
}
|
||||
$filtered = [];
|
||||
foreach ($occurrences as $date) {
|
||||
if ($date->lte($max)) {
|
||||
$filtered[] = $date;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the repetition in a string that is user readable.
|
||||
*
|
||||
@@ -555,19 +570,4 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
|
||||
return $service->update($recurrence, $data);
|
||||
}
|
||||
|
||||
private function filterMaxDate(?Carbon $max, array $occurrences): array
|
||||
{
|
||||
if (null === $max) {
|
||||
return $occurrences;
|
||||
}
|
||||
$filtered = [];
|
||||
foreach ($occurrences as $date) {
|
||||
if ($date->lte($max)) {
|
||||
$filtered[] = $date;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
}
|
||||
|
@@ -280,6 +280,26 @@ class RuleRepository implements RuleRepositoryInterface
|
||||
return $this->user->rules()->find($ruleId);
|
||||
}
|
||||
|
||||
private function setRuleTrigger(string $moment, Rule $rule): void
|
||||
{
|
||||
/** @var null|RuleTrigger $trigger */
|
||||
$trigger = $rule->ruleTriggers()->where('trigger_type', 'user_action')->first();
|
||||
if (null !== $trigger) {
|
||||
$trigger->trigger_value = $moment;
|
||||
$trigger->save();
|
||||
|
||||
return;
|
||||
}
|
||||
$trigger = new RuleTrigger();
|
||||
$trigger->order = 0;
|
||||
$trigger->trigger_type = 'user_action';
|
||||
$trigger->trigger_value = $moment;
|
||||
$trigger->rule_id = $rule->id;
|
||||
$trigger->active = true;
|
||||
$trigger->stop_processing = false;
|
||||
$trigger->save();
|
||||
}
|
||||
|
||||
public function resetRuleOrder(RuleGroup $ruleGroup): bool
|
||||
{
|
||||
$groupRepository = app(RuleGroupRepositoryInterface::class);
|
||||
@@ -336,6 +356,59 @@ class RuleRepository implements RuleRepositoryInterface
|
||||
return (int)$ruleGroup->rules()->max('order');
|
||||
}
|
||||
|
||||
private function storeTriggers(Rule $rule, array $data): void
|
||||
{
|
||||
$order = 1;
|
||||
foreach ($data['triggers'] as $trigger) {
|
||||
$value = $trigger['value'] ?? '';
|
||||
$stopProcessing = $trigger['stop_processing'] ?? false;
|
||||
$active = $trigger['active'] ?? true;
|
||||
$type = $trigger['type'];
|
||||
if (true === ($trigger['prohibited'] ?? false) && !str_starts_with($type, '-')) {
|
||||
$type = sprintf('-%s', $type);
|
||||
}
|
||||
|
||||
// empty the value in case the rule needs no context
|
||||
// TODO create a helper to automatically return these.
|
||||
$needTrue = [
|
||||
'reconciled',
|
||||
'has_attachments',
|
||||
'has_any_category',
|
||||
'has_any_budget',
|
||||
'has_any_bill',
|
||||
'has_any_tag',
|
||||
'any_notes',
|
||||
'any_external_url',
|
||||
'has_no_attachments',
|
||||
'has_no_category',
|
||||
'has_no_budget',
|
||||
'has_no_bill',
|
||||
'has_no_tag',
|
||||
'no_notes',
|
||||
'no_external_url',
|
||||
'source_is_cash',
|
||||
'destination_is_cash',
|
||||
'account_is_cash',
|
||||
'exists',
|
||||
'no_external_id',
|
||||
'any_external_id',
|
||||
];
|
||||
if (in_array($type, $needTrue, true)) {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
$triggerValues = [
|
||||
'action' => $type,
|
||||
'value' => $value,
|
||||
'stop_processing' => $stopProcessing,
|
||||
'order' => $order,
|
||||
'active' => $active,
|
||||
];
|
||||
$this->storeTrigger($rule, $triggerValues);
|
||||
++$order;
|
||||
}
|
||||
}
|
||||
|
||||
public function storeTrigger(Rule $rule, array $values): RuleTrigger
|
||||
{
|
||||
$ruleTrigger = new RuleTrigger();
|
||||
@@ -350,6 +423,25 @@ class RuleRepository implements RuleRepositoryInterface
|
||||
return $ruleTrigger;
|
||||
}
|
||||
|
||||
private function storeActions(Rule $rule, array $data): void
|
||||
{
|
||||
$order = 1;
|
||||
foreach ($data['actions'] as $action) {
|
||||
$value = $action['value'] ?? '';
|
||||
$stopProcessing = $action['stop_processing'] ?? false;
|
||||
$active = $action['active'] ?? true;
|
||||
$actionValues = [
|
||||
'action' => $action['type'],
|
||||
'value' => $value,
|
||||
'stop_processing' => $stopProcessing,
|
||||
'order' => $order,
|
||||
'active' => $active,
|
||||
];
|
||||
$this->storeAction($rule, $actionValues);
|
||||
++$order;
|
||||
}
|
||||
}
|
||||
|
||||
public function storeAction(Rule $rule, array $values): RuleAction
|
||||
{
|
||||
$ruleAction = new RuleAction();
|
||||
@@ -426,96 +518,4 @@ class RuleRepository implements RuleRepositoryInterface
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
private function setRuleTrigger(string $moment, Rule $rule): void
|
||||
{
|
||||
/** @var null|RuleTrigger $trigger */
|
||||
$trigger = $rule->ruleTriggers()->where('trigger_type', 'user_action')->first();
|
||||
if (null !== $trigger) {
|
||||
$trigger->trigger_value = $moment;
|
||||
$trigger->save();
|
||||
|
||||
return;
|
||||
}
|
||||
$trigger = new RuleTrigger();
|
||||
$trigger->order = 0;
|
||||
$trigger->trigger_type = 'user_action';
|
||||
$trigger->trigger_value = $moment;
|
||||
$trigger->rule_id = $rule->id;
|
||||
$trigger->active = true;
|
||||
$trigger->stop_processing = false;
|
||||
$trigger->save();
|
||||
}
|
||||
|
||||
private function storeTriggers(Rule $rule, array $data): void
|
||||
{
|
||||
$order = 1;
|
||||
foreach ($data['triggers'] as $trigger) {
|
||||
$value = $trigger['value'] ?? '';
|
||||
$stopProcessing = $trigger['stop_processing'] ?? false;
|
||||
$active = $trigger['active'] ?? true;
|
||||
$type = $trigger['type'];
|
||||
if (true === ($trigger['prohibited'] ?? false) && !str_starts_with($type, '-')) {
|
||||
$type = sprintf('-%s', $type);
|
||||
}
|
||||
|
||||
// empty the value in case the rule needs no context
|
||||
// TODO create a helper to automatically return these.
|
||||
$needTrue = [
|
||||
'reconciled',
|
||||
'has_attachments',
|
||||
'has_any_category',
|
||||
'has_any_budget',
|
||||
'has_any_bill',
|
||||
'has_any_tag',
|
||||
'any_notes',
|
||||
'any_external_url',
|
||||
'has_no_attachments',
|
||||
'has_no_category',
|
||||
'has_no_budget',
|
||||
'has_no_bill',
|
||||
'has_no_tag',
|
||||
'no_notes',
|
||||
'no_external_url',
|
||||
'source_is_cash',
|
||||
'destination_is_cash',
|
||||
'account_is_cash',
|
||||
'exists',
|
||||
'no_external_id',
|
||||
'any_external_id',
|
||||
];
|
||||
if(in_array($type, $needTrue, true)) {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
$triggerValues = [
|
||||
'action' => $type,
|
||||
'value' => $value,
|
||||
'stop_processing' => $stopProcessing,
|
||||
'order' => $order,
|
||||
'active' => $active,
|
||||
];
|
||||
$this->storeTrigger($rule, $triggerValues);
|
||||
++$order;
|
||||
}
|
||||
}
|
||||
|
||||
private function storeActions(Rule $rule, array $data): void
|
||||
{
|
||||
$order = 1;
|
||||
foreach ($data['actions'] as $action) {
|
||||
$value = $action['value'] ?? '';
|
||||
$stopProcessing = $action['stop_processing'] ?? false;
|
||||
$active = $action['active'] ?? true;
|
||||
$actionValues = [
|
||||
'action' => $action['type'],
|
||||
'value' => $value,
|
||||
'stop_processing' => $stopProcessing,
|
||||
'order' => $order,
|
||||
'active' => $active,
|
||||
];
|
||||
$this->storeAction($rule, $actionValues);
|
||||
++$order;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -151,6 +151,49 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
private function resetRuleActionOrder(Rule $rule): void
|
||||
{
|
||||
$actions = $rule->ruleActions()
|
||||
->orderBy('order', 'ASC')
|
||||
->orderBy('active', 'DESC')
|
||||
->orderBy('action_type', 'ASC')
|
||||
->get()
|
||||
;
|
||||
$index = 1;
|
||||
|
||||
/** @var RuleAction $action */
|
||||
foreach ($actions as $action) {
|
||||
if ($action->order !== $index) {
|
||||
$action->order = $index;
|
||||
$action->save();
|
||||
app('log')->debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index));
|
||||
}
|
||||
++$index;
|
||||
}
|
||||
}
|
||||
|
||||
private function resetRuleTriggerOrder(Rule $rule): void
|
||||
{
|
||||
$triggers = $rule->ruleTriggers()
|
||||
->orderBy('order', 'ASC')
|
||||
->orderBy('active', 'DESC')
|
||||
->orderBy('trigger_type', 'ASC')
|
||||
->get()
|
||||
;
|
||||
$index = 1;
|
||||
|
||||
/** @var RuleTrigger $trigger */
|
||||
foreach ($triggers as $trigger) {
|
||||
$order = $trigger->order;
|
||||
if ($order !== $index) {
|
||||
$trigger->order = $index;
|
||||
$trigger->save();
|
||||
app('log')->debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index));
|
||||
}
|
||||
++$index;
|
||||
}
|
||||
}
|
||||
|
||||
public function destroyAll(): void
|
||||
{
|
||||
Log::channel('audit')->info('Delete all rule groups through destroyAll');
|
||||
@@ -412,47 +455,4 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
|
||||
|
||||
return $ruleGroup;
|
||||
}
|
||||
|
||||
private function resetRuleActionOrder(Rule $rule): void
|
||||
{
|
||||
$actions = $rule->ruleActions()
|
||||
->orderBy('order', 'ASC')
|
||||
->orderBy('active', 'DESC')
|
||||
->orderBy('action_type', 'ASC')
|
||||
->get()
|
||||
;
|
||||
$index = 1;
|
||||
|
||||
/** @var RuleAction $action */
|
||||
foreach ($actions as $action) {
|
||||
if ($action->order !== $index) {
|
||||
$action->order = $index;
|
||||
$action->save();
|
||||
app('log')->debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index));
|
||||
}
|
||||
++$index;
|
||||
}
|
||||
}
|
||||
|
||||
private function resetRuleTriggerOrder(Rule $rule): void
|
||||
{
|
||||
$triggers = $rule->ruleTriggers()
|
||||
->orderBy('order', 'ASC')
|
||||
->orderBy('active', 'DESC')
|
||||
->orderBy('trigger_type', 'ASC')
|
||||
->get()
|
||||
;
|
||||
$index = 1;
|
||||
|
||||
/** @var RuleTrigger $trigger */
|
||||
foreach ($triggers as $trigger) {
|
||||
$order = $trigger->order;
|
||||
if ($order !== $index) {
|
||||
$trigger->order = $index;
|
||||
$trigger->save();
|
||||
app('log')->debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index));
|
||||
}
|
||||
++$index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -120,6 +120,14 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function getTags(): Collection
|
||||
{
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
|
||||
return $repository->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a list of all the deposit transaction journals (as arrays) set in that period
|
||||
* which have the specified tag(s) set to them. It's grouped per currency, with as few details in the array
|
||||
@@ -215,12 +223,4 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
{
|
||||
throw new FireflyException(sprintf('%s is not yet implemented.', __METHOD__));
|
||||
}
|
||||
|
||||
private function getTags(): Collection
|
||||
{
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
|
||||
return $repository->get();
|
||||
}
|
||||
}
|
||||
|
@@ -90,6 +90,46 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function expandJournal(TransactionJournal $journal): array
|
||||
{
|
||||
$array = $journal->toArray();
|
||||
$array['transactions'] = [];
|
||||
$array['meta'] = $journal->transactionJournalMeta->toArray();
|
||||
$array['tags'] = $journal->tags->toArray();
|
||||
$array['categories'] = $journal->categories->toArray();
|
||||
$array['budgets'] = $journal->budgets->toArray();
|
||||
$array['notes'] = $journal->notes->toArray();
|
||||
$array['locations'] = [];
|
||||
$array['attachments'] = $journal->attachments->toArray();
|
||||
$array['links'] = [];
|
||||
$array['piggy_bank_events'] = $journal->piggyBankEvents->toArray();
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journal->transactions as $transaction) {
|
||||
$array['transactions'][] = $this->expandTransaction($transaction);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function expandTransaction(Transaction $transaction): array
|
||||
{
|
||||
$array = $transaction->toArray();
|
||||
$array['account'] = $transaction->account->toArray();
|
||||
$array['budgets'] = [];
|
||||
$array['categories'] = [];
|
||||
|
||||
foreach ($transaction->categories as $category) {
|
||||
$array['categories'][] = $category->toArray();
|
||||
}
|
||||
|
||||
foreach ($transaction->budgets as $budget) {
|
||||
$array['budgets'][] = $budget->toArray();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all attachments for all journals in the group.
|
||||
*/
|
||||
@@ -201,6 +241,48 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getFormattedAmount(TransactionJournal $journal): string
|
||||
{
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $journal->transactions->first();
|
||||
$currency = $transaction->transactionCurrency;
|
||||
$type = $journal->transactionType->type;
|
||||
$amount = app('steam')->positive($transaction->amount);
|
||||
$return = '';
|
||||
if (TransactionType::WITHDRAWAL === $type) {
|
||||
$return = app('amount')->formatAnything($currency, app('steam')->negative($amount));
|
||||
}
|
||||
if (TransactionType::WITHDRAWAL !== $type) {
|
||||
$return = app('amount')->formatAnything($currency, $amount);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getFormattedForeignAmount(TransactionJournal $journal): string
|
||||
{
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $journal->transactions->first();
|
||||
if (null === $transaction->foreign_amount || '' === $transaction->foreign_amount) {
|
||||
return '';
|
||||
}
|
||||
if (0 === bccomp('0', $transaction->foreign_amount)) {
|
||||
return '';
|
||||
}
|
||||
$currency = $transaction->foreignCurrency;
|
||||
$type = $journal->transactionType->type;
|
||||
$amount = app('steam')->positive($transaction->foreign_amount);
|
||||
$return = '';
|
||||
if (TransactionType::WITHDRAWAL === $type) {
|
||||
$return = app('amount')->formatAnything($currency, app('steam')->negative($amount));
|
||||
}
|
||||
if (TransactionType::WITHDRAWAL !== $type) {
|
||||
$return = app('amount')->formatAnything($currency, $amount);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function getLocation(int $journalId): ?Location
|
||||
{
|
||||
/** @var TransactionJournal $journal */
|
||||
@@ -351,86 +433,4 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
|
||||
|
||||
return $service->update($transactionGroup, $data);
|
||||
}
|
||||
|
||||
private function expandJournal(TransactionJournal $journal): array
|
||||
{
|
||||
$array = $journal->toArray();
|
||||
$array['transactions'] = [];
|
||||
$array['meta'] = $journal->transactionJournalMeta->toArray();
|
||||
$array['tags'] = $journal->tags->toArray();
|
||||
$array['categories'] = $journal->categories->toArray();
|
||||
$array['budgets'] = $journal->budgets->toArray();
|
||||
$array['notes'] = $journal->notes->toArray();
|
||||
$array['locations'] = [];
|
||||
$array['attachments'] = $journal->attachments->toArray();
|
||||
$array['links'] = [];
|
||||
$array['piggy_bank_events'] = $journal->piggyBankEvents->toArray();
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journal->transactions as $transaction) {
|
||||
$array['transactions'][] = $this->expandTransaction($transaction);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function expandTransaction(Transaction $transaction): array
|
||||
{
|
||||
$array = $transaction->toArray();
|
||||
$array['account'] = $transaction->account->toArray();
|
||||
$array['budgets'] = [];
|
||||
$array['categories'] = [];
|
||||
|
||||
foreach ($transaction->categories as $category) {
|
||||
$array['categories'][] = $category->toArray();
|
||||
}
|
||||
|
||||
foreach ($transaction->budgets as $budget) {
|
||||
$array['budgets'][] = $budget->toArray();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function getFormattedAmount(TransactionJournal $journal): string
|
||||
{
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $journal->transactions->first();
|
||||
$currency = $transaction->transactionCurrency;
|
||||
$type = $journal->transactionType->type;
|
||||
$amount = app('steam')->positive($transaction->amount);
|
||||
$return = '';
|
||||
if (TransactionType::WITHDRAWAL === $type) {
|
||||
$return = app('amount')->formatAnything($currency, app('steam')->negative($amount));
|
||||
}
|
||||
if (TransactionType::WITHDRAWAL !== $type) {
|
||||
$return = app('amount')->formatAnything($currency, $amount);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getFormattedForeignAmount(TransactionJournal $journal): string
|
||||
{
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $journal->transactions->first();
|
||||
if (null === $transaction->foreign_amount || '' === $transaction->foreign_amount) {
|
||||
return '';
|
||||
}
|
||||
if (0 === bccomp('0', $transaction->foreign_amount)) {
|
||||
return '';
|
||||
}
|
||||
$currency = $transaction->foreignCurrency;
|
||||
$type = $journal->transactionType->type;
|
||||
$amount = app('steam')->positive($transaction->foreign_amount);
|
||||
$return = '';
|
||||
if (TransactionType::WITHDRAWAL === $type) {
|
||||
$return = app('amount')->formatAnything($currency, app('steam')->negative($amount));
|
||||
}
|
||||
if (TransactionType::WITHDRAWAL !== $type) {
|
||||
$return = app('amount')->formatAnything($currency, $amount);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -112,6 +112,34 @@ class UserGroupRepository implements UserGroupRepositoryInterface
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because there is the chance that a group with this name already exists,
|
||||
* Firefly III runs a little loop of combinations to make sure the group name is unique.
|
||||
*/
|
||||
private function createNewUserGroup(User $user): UserGroup
|
||||
{
|
||||
$loop = 0;
|
||||
$groupName = $user->email;
|
||||
$exists = true;
|
||||
$existingGroup = null;
|
||||
while ($exists && $loop < 10) {
|
||||
$existingGroup = $this->findByName($groupName);
|
||||
if (null === $existingGroup) {
|
||||
$exists = false;
|
||||
|
||||
/** @var null|UserGroup $existingGroup */
|
||||
$existingGroup = $this->store(['user' => $user, 'title' => $groupName]);
|
||||
}
|
||||
if (null !== $existingGroup) {
|
||||
// group already exists
|
||||
$groupName = sprintf('%s-%s', $user->email, substr(sha1(rand(1000, 9999).microtime()), 0, 4));
|
||||
}
|
||||
++$loop;
|
||||
}
|
||||
|
||||
return $existingGroup;
|
||||
}
|
||||
|
||||
public function findByName(string $title): ?UserGroup
|
||||
{
|
||||
return UserGroup::whereTitle($title)->first();
|
||||
@@ -239,34 +267,6 @@ class UserGroupRepository implements UserGroupRepositoryInterface
|
||||
return $userGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because there is the chance that a group with this name already exists,
|
||||
* Firefly III runs a little loop of combinations to make sure the group name is unique.
|
||||
*/
|
||||
private function createNewUserGroup(User $user): UserGroup
|
||||
{
|
||||
$loop = 0;
|
||||
$groupName = $user->email;
|
||||
$exists = true;
|
||||
$existingGroup = null;
|
||||
while ($exists && $loop < 10) {
|
||||
$existingGroup = $this->findByName($groupName);
|
||||
if (null === $existingGroup) {
|
||||
$exists = false;
|
||||
|
||||
/** @var null|UserGroup $existingGroup */
|
||||
$existingGroup = $this->store(['user' => $user, 'title' => $groupName]);
|
||||
}
|
||||
if (null !== $existingGroup) {
|
||||
// group already exists
|
||||
$groupName = sprintf('%s-%s', $user->email, substr(sha1(rand(1000, 9999).microtime()), 0, 4));
|
||||
}
|
||||
++$loop;
|
||||
}
|
||||
|
||||
return $existingGroup;
|
||||
}
|
||||
|
||||
private function simplifyListByName(array $roles): array
|
||||
{
|
||||
if (in_array(UserRoleEnum::OWNER->value, $roles, true)) {
|
||||
|
@@ -150,6 +150,14 @@ class CurrencyRepository implements CurrencyRepositoryInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
private function countJournals(TransactionCurrency $currency): int
|
||||
{
|
||||
$count = $currency->transactions()->whereNull('deleted_at')->count() + $currency->transactionJournals()->whereNull('deleted_at')->count();
|
||||
|
||||
// also count foreign:
|
||||
return $count + Transaction::where('foreign_currency_id', $currency->id)->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ALL currencies, regardless of whether they are enabled or not.
|
||||
*/
|
||||
@@ -372,12 +380,4 @@ class CurrencyRepository implements CurrencyRepositoryInterface
|
||||
|
||||
return $service->update($currency, $data);
|
||||
}
|
||||
|
||||
private function countJournals(TransactionCurrency $currency): int
|
||||
{
|
||||
$count = $currency->transactions()->whereNull('deleted_at')->count() + $currency->transactionJournals()->whereNull('deleted_at')->count();
|
||||
|
||||
// also count foreign:
|
||||
return $count + Transaction::where('foreign_currency_id', $currency->id)->count();
|
||||
}
|
||||
}
|
||||
|
@@ -65,32 +65,6 @@ class BelongsUser implements ValidationRule
|
||||
}
|
||||
}
|
||||
|
||||
protected function countField(string $class, string $field, string $value): int
|
||||
{
|
||||
$value = trim($value);
|
||||
$objects = [];
|
||||
// get all objects belonging to user:
|
||||
if (PiggyBank::class === $class) {
|
||||
$objects = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')
|
||||
->where('accounts.user_id', '=', auth()->user()->id)->get(['piggy_banks.*'])
|
||||
;
|
||||
}
|
||||
if (PiggyBank::class !== $class) {
|
||||
$objects = $class::where('user_id', '=', auth()->user()->id)->get();
|
||||
}
|
||||
$count = 0;
|
||||
foreach ($objects as $object) {
|
||||
$objectValue = trim((string)$object->{$field}); // @phpstan-ignore-line
|
||||
app('log')->debug(sprintf('Comparing object "%s" with value "%s"', $objectValue, $value));
|
||||
if ($objectValue === $value) {
|
||||
++$count;
|
||||
app('log')->debug(sprintf('Hit! Count is now %d', $count));
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function parseAttribute(string $attribute): string
|
||||
{
|
||||
$parts = explode('.', $attribute);
|
||||
@@ -121,6 +95,32 @@ class BelongsUser implements ValidationRule
|
||||
return 1 === $count;
|
||||
}
|
||||
|
||||
protected function countField(string $class, string $field, string $value): int
|
||||
{
|
||||
$value = trim($value);
|
||||
$objects = [];
|
||||
// get all objects belonging to user:
|
||||
if (PiggyBank::class === $class) {
|
||||
$objects = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')
|
||||
->where('accounts.user_id', '=', auth()->user()->id)->get(['piggy_banks.*'])
|
||||
;
|
||||
}
|
||||
if (PiggyBank::class !== $class) {
|
||||
$objects = $class::where('user_id', '=', auth()->user()->id)->get();
|
||||
}
|
||||
$count = 0;
|
||||
foreach ($objects as $object) {
|
||||
$objectValue = trim((string)$object->{$field}); // @phpstan-ignore-line
|
||||
app('log')->debug(sprintf('Comparing object "%s" with value "%s"', $objectValue, $value));
|
||||
if ($objectValue === $value) {
|
||||
++$count;
|
||||
app('log')->debug(sprintf('Hit! Count is now %d', $count));
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function validateBillId(int $value): bool
|
||||
{
|
||||
if (0 === $value) {
|
||||
|
@@ -78,32 +78,6 @@ class BelongsUserGroup implements ValidationRule
|
||||
}
|
||||
}
|
||||
|
||||
protected function countField(string $class, string $field, string $value): int
|
||||
{
|
||||
$value = trim($value);
|
||||
$objects = [];
|
||||
// get all objects belonging to user:
|
||||
if (PiggyBank::class === $class) {
|
||||
$objects = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')
|
||||
->where('accounts.user_group_id', '=', $this->userGroup->id)->get(['piggy_banks.*'])
|
||||
;
|
||||
}
|
||||
if (PiggyBank::class !== $class) {
|
||||
$objects = $class::where('user_group_id', '=', $this->userGroup->id)->get();
|
||||
}
|
||||
$count = 0;
|
||||
foreach ($objects as $object) {
|
||||
$objectValue = trim((string)$object->{$field}); // @phpstan-ignore-line
|
||||
app('log')->debug(sprintf('Comparing object "%s" with value "%s"', $objectValue, $value));
|
||||
if ($objectValue === $value) {
|
||||
++$count;
|
||||
app('log')->debug(sprintf('Hit! Count is now %d', $count));
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function parseAttribute(string $attribute): string
|
||||
{
|
||||
$parts = explode('.', $attribute);
|
||||
@@ -134,6 +108,32 @@ class BelongsUserGroup implements ValidationRule
|
||||
return 1 === $count;
|
||||
}
|
||||
|
||||
protected function countField(string $class, string $field, string $value): int
|
||||
{
|
||||
$value = trim($value);
|
||||
$objects = [];
|
||||
// get all objects belonging to user:
|
||||
if (PiggyBank::class === $class) {
|
||||
$objects = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')
|
||||
->where('accounts.user_group_id', '=', $this->userGroup->id)->get(['piggy_banks.*'])
|
||||
;
|
||||
}
|
||||
if (PiggyBank::class !== $class) {
|
||||
$objects = $class::where('user_group_id', '=', $this->userGroup->id)->get();
|
||||
}
|
||||
$count = 0;
|
||||
foreach ($objects as $object) {
|
||||
$objectValue = trim((string)$object->{$field}); // @phpstan-ignore-line
|
||||
app('log')->debug(sprintf('Comparing object "%s" with value "%s"', $objectValue, $value));
|
||||
if ($objectValue === $value) {
|
||||
++$count;
|
||||
app('log')->debug(sprintf('Hit! Count is now %d', $count));
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function validateBillId(int $value): bool
|
||||
{
|
||||
if (0 === $value) {
|
||||
|
@@ -57,6 +57,15 @@ class IsValidAttachmentModel implements ValidationRule
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
private function normalizeModel(string $model): string
|
||||
{
|
||||
$search = ['FireflyIII\Models\\'];
|
||||
$replace = '';
|
||||
$model = str_replace($search, $replace, $model);
|
||||
|
||||
return sprintf('FireflyIII\Models\%s', $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
@@ -84,15 +93,6 @@ class IsValidAttachmentModel implements ValidationRule
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizeModel(string $model): string
|
||||
{
|
||||
$search = ['FireflyIII\Models\\'];
|
||||
$replace = '';
|
||||
$model = str_replace($search, $replace, $model);
|
||||
|
||||
return sprintf('FireflyIII\Models\%s', $model);
|
||||
}
|
||||
|
||||
private function validateAccount(int $value): bool
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
|
@@ -28,6 +28,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Events\NewVersionAvailable;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class UpdateRequest
|
||||
@@ -42,7 +43,7 @@ class UpdateRequest implements UpdateRequestInterface
|
||||
'message' => (string)trans('firefly.unknown_error'),
|
||||
];
|
||||
|
||||
// try get array from update server:
|
||||
// try to get array from update server:
|
||||
$updateInfo = $this->contactServer($channel);
|
||||
if ('error' === $updateInfo['level']) {
|
||||
app('log')->error('Update information contains an error.');
|
||||
@@ -128,20 +129,22 @@ class UpdateRequest implements UpdateRequestInterface
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO make shorter
|
||||
*/
|
||||
private function parseResult(array $information): array
|
||||
{
|
||||
app('log')->debug('Now in parseResult()', $information);
|
||||
$return = [
|
||||
'level' => 'error',
|
||||
'message' => (string)trans('firefly.unknown_error'),
|
||||
];
|
||||
$current = config('firefly.version');
|
||||
$latest = $information['version'];
|
||||
$current = (string)config('firefly.version');
|
||||
$latest = (string)$information['version'];
|
||||
|
||||
// strip the 'v' from the version if it's there.
|
||||
if (str_starts_with($latest, 'v')) {
|
||||
$latest = substr($latest, 1);
|
||||
}
|
||||
if (str_starts_with($current, 'develop')) {
|
||||
return $this->parseResultDevelop($current, $latest, $information);
|
||||
}
|
||||
|
||||
$compare = version_compare($latest, $current);
|
||||
|
||||
@@ -149,73 +152,129 @@ class UpdateRequest implements UpdateRequestInterface
|
||||
|
||||
// -1: you're running a newer version:
|
||||
if (-1 === $compare) {
|
||||
$return['level'] = 'info';
|
||||
$return['message'] = (string)trans('firefly.update_newer_version_alert', ['your_version' => $current, 'new_version' => $latest]);
|
||||
app('log')->debug('User is running a newer version', $return);
|
||||
|
||||
return $return;
|
||||
return $this->runsNewerVersion($current, $latest);
|
||||
}
|
||||
// running the current version:
|
||||
if (0 === $compare) {
|
||||
$return['level'] = 'info';
|
||||
$return['message'] = (string)trans('firefly.update_current_version_alert', ['version' => $current]);
|
||||
app('log')->debug('User is the current version.', $return);
|
||||
|
||||
return $return;
|
||||
return $this->runsSameVersion($current);
|
||||
}
|
||||
|
||||
// a newer version is available!
|
||||
/** @var Carbon $released */
|
||||
$released = $information['date'];
|
||||
$today = today(config('app.timezone'))->startOfDay();
|
||||
$diff = $today->diffInDays($released);
|
||||
$expectedDiff = config('firefly.update_minimum_age') ?? 6;
|
||||
// it's still very fresh, and user wants a stable release:
|
||||
if ($diff <= $expectedDiff) {
|
||||
$isBeta = $information['is_beta'] ?? false;
|
||||
$isAlpha = $information['is_alpha'] ?? false;
|
||||
|
||||
// it's new but alpha:
|
||||
if (true === $isAlpha) {
|
||||
return $this->releasedNewAlpha($current, $latest, $released);
|
||||
}
|
||||
|
||||
if (true === $isBeta) {
|
||||
return $this->releasedNewBeta($current, $latest, $released);
|
||||
}
|
||||
|
||||
return $this->releasedNewVersion($current, $latest, $released);
|
||||
}
|
||||
|
||||
private function parseResultDevelop(string $current, string $latest, array $information): array
|
||||
{
|
||||
Log::debug(sprintf('User is running develop version "%s"', $current));
|
||||
$parts = explode('/', $current);
|
||||
$return = [];
|
||||
|
||||
/** @var Carbon $devDate */
|
||||
$devDate = Carbon::createFromFormat('Y-m-d', $parts[1]);
|
||||
|
||||
if ($devDate->lte($information['date'])) {
|
||||
Log::debug(sprintf('This development release is older, release = %s, latest version %s = %s', $devDate->format('Y-m-d'), $latest, $information['date']->format('Y-m-d')));
|
||||
$return['level'] = 'info';
|
||||
$return['message'] = (string)trans(
|
||||
'firefly.just_new_release',
|
||||
[
|
||||
'version' => $latest,
|
||||
'date' => $released->isoFormat((string)trans('config.month_and_day_js')),
|
||||
'days' => $expectedDiff,
|
||||
]
|
||||
);
|
||||
app('log')->debug('Release is very fresh.', $return);
|
||||
$return['message'] = (string)trans('firefly.update_current_dev_older', ['version' => $current, 'new_version' => $latest]);
|
||||
|
||||
return $return;
|
||||
}
|
||||
Log::debug(sprintf('This development release is newer, release = %s, latest version %s = %s', $devDate->format('Y-m-d'), $latest, $information['date']->format('Y-m-d')));
|
||||
$return['level'] = 'info';
|
||||
$return['message'] = (string)trans('firefly.update_current_dev_newer', ['version' => $current, 'new_version' => $latest]);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
// it's been around for a while:
|
||||
$return['level'] = 'success';
|
||||
$return['message'] = (string)trans(
|
||||
private function runsNewerVersion(string $current, string $latest): array
|
||||
{
|
||||
$return = [
|
||||
'level' => 'info',
|
||||
'message' => (string)trans('firefly.update_newer_version_alert', ['your_version' => $current, 'new_version' => $latest]),
|
||||
];
|
||||
app('log')->debug('User is running a newer version', $return);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function runsSameVersion(string $current): array
|
||||
{
|
||||
$return = [
|
||||
'level' => 'info',
|
||||
'message' => (string)trans('firefly.update_current_version_alert', ['version' => $current]),
|
||||
];
|
||||
app('log')->debug('User is the current version.', $return);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function releasedNewAlpha(string $current, string $latest, Carbon $date): array
|
||||
{
|
||||
app('log')->debug('New release is also a alpha!');
|
||||
$message = (string)trans(
|
||||
'firefly.update_new_version_alert',
|
||||
[
|
||||
'your_version' => $current,
|
||||
'new_version' => $latest,
|
||||
'date' => $released->isoFormat((string)trans('config.month_and_day_js')),
|
||||
'date' => $date->isoFormat((string)trans('config.month_and_day_js')),
|
||||
]
|
||||
);
|
||||
app('log')->debug('New release is old enough.');
|
||||
|
||||
// add warning in case of alpha or beta:
|
||||
// append warning if beta or alpha.
|
||||
$isBeta = $information['is_beta'] ?? false;
|
||||
if (true === $isBeta) {
|
||||
$return['message'] = sprintf('%s %s', $return['message'], trans('firefly.update_version_beta'));
|
||||
return [
|
||||
'level' => 'success',
|
||||
'message' => sprintf('%s %s', $message, trans('firefly.update_version_alpha')),
|
||||
];
|
||||
}
|
||||
|
||||
private function releasedNewBeta(string $current, string $latest, Carbon $date): array
|
||||
{
|
||||
app('log')->debug('New release is also a beta!');
|
||||
$message = (string)trans(
|
||||
'firefly.update_new_version_alert',
|
||||
[
|
||||
'your_version' => $current,
|
||||
'new_version' => $latest,
|
||||
'date' => $date->isoFormat((string)trans('config.month_and_day_js')),
|
||||
]
|
||||
);
|
||||
|
||||
return [
|
||||
'level' => 'success',
|
||||
'message' => sprintf('%s %s', $message, trans('firefly.update_version_beta')),
|
||||
];
|
||||
}
|
||||
|
||||
$isAlpha = $information['is_alpha'] ?? false;
|
||||
if (true === $isAlpha) {
|
||||
$return['message'] = sprintf('%s %s', $return['message'], trans('firefly.update_version_alpha'));
|
||||
app('log')->debug('New release is also a alpha!');
|
||||
}
|
||||
app('log')->debug('New release is here!', $return);
|
||||
private function releasedNewVersion(string $current, string $latest, Carbon $date): array
|
||||
{
|
||||
app('log')->debug('New release is old enough.');
|
||||
$message = (string)trans(
|
||||
'firefly.update_new_version_alert',
|
||||
[
|
||||
'your_version' => $current,
|
||||
'new_version' => $latest,
|
||||
'date' => $date->isoFormat((string)trans('config.month_and_day_js')),
|
||||
]
|
||||
);
|
||||
app('log')->debug('New release is here!', [$message]);
|
||||
event(new NewVersionAvailable($message));
|
||||
|
||||
// send event, this may result in a notification.
|
||||
event(new NewVersionAvailable($return['message']));
|
||||
|
||||
return $return;
|
||||
return [
|
||||
'level' => 'success',
|
||||
'message' => $message,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user