Compare commits

...

62 Commits

Author SHA1 Message Date
github-actions
90d58ec8fa Auto commit for release 'v6.1.10' on 2024-03-02 2024-03-02 17:12:03 +01:00
github-actions
e92dd7f464 Merge branch 'develop' 2024-03-02 16:08:11 +00:00
James Cole
3bdf9eeed2 Jump to v34 2024-03-02 17:06:47 +01:00
James Cole
558ac7b0da Update changelog. 2024-03-02 16:53:19 +01:00
James Cole
9d0488ffbc fix phpstan issues. 2024-02-29 06:39:58 +01:00
James Cole
d7fa8b283e Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-02-29 06:27:24 +01:00
James Cole
a0097bd613 Fix issues with available budgets. 2024-02-29 06:26:23 +01:00
github-actions
ffc2156e5f Auto commit for release 'develop' on 2024-02-29 2024-02-29 01:28:57 +01:00
James Cole
e0a89bb5fe Rebuild JS 2024-02-26 20:08:14 +01:00
James Cole
647179cd3c Rebuild JS 2024-02-26 19:56:19 +01:00
James Cole
5106ccdbd7 Merge branch 'main' into develop 2024-02-26 19:55:14 +01:00
James Cole
7103098fe7 Better error handling 2024-02-26 19:54:45 +01:00
James Cole
f8072f0bfc Merge pull request #8588 from firefly-iii/dependabot/github_actions/JC5/firefly-iii-dev-33
Bump JC5/firefly-iii-dev from 32 to 33
2024-02-26 06:17:04 +01:00
dependabot[bot]
96ac3a95c8 Bump JC5/firefly-iii-dev from 32 to 33
Bumps [JC5/firefly-iii-dev](https://github.com/jc5/firefly-iii-dev) from 32 to 33.
- [Commits](https://github.com/jc5/firefly-iii-dev/compare/v32...v33)

---
updated-dependencies:
- dependency-name: JC5/firefly-iii-dev
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 03:23:03 +00:00
github-actions
cd713dc40f Auto commit for release 'develop' on 2024-02-26 2024-02-26 01:29:50 +01:00
James Cole
d9fba39d80 Fix detection of transaction type. 2024-02-25 18:24:10 +01:00
James Cole
2564470197 Update source account detection. 2024-02-25 18:14:30 +01:00
James Cole
9222c82af0 Expand frontend, first attempt at sorting. 2024-02-25 18:09:52 +01:00
James Cole
243f283bfd Fix tests 2024-02-24 08:36:57 +01:00
James Cole
5b60aaecc0 Update translations. 2024-02-24 08:19:23 +01:00
James Cole
20a4caec60 Add some missing translations 2024-02-24 06:03:34 +01:00
James Cole
99cc096b71 Fix https://github.com/firefly-iii/firefly-iii/issues/8578 2024-02-24 05:38:02 +01:00
James Cole
5626d1c56d Fix https://github.com/firefly-iii/firefly-iii/issues/8575 2024-02-23 17:06:25 +01:00
James Cole
68c9c4ec3c PHPStorm can order methods by alphabet, who knew. 2024-02-22 20:11:09 +01:00
James Cole
f9d4a43e05 This should fix the ci issues. 2024-02-22 06:57:57 +01:00
James Cole
92e7f344e0 Refresh update code. 2024-02-22 06:52:58 +01:00
github-actions
89ce2838d5 Auto commit for release 'develop' on 2024-02-22 2024-02-22 01:29:01 +01:00
James Cole
356b217692 Add missing array 2024-02-20 20:33:33 +01:00
James Cole
950e39b753 Clean up methods. 2024-02-20 20:05:43 +01:00
James Cole
8f14979717 Clean up rule set. 2024-02-20 19:59:55 +01:00
James Cole
aa2afd162e Fix https://github.com/orgs/firefly-iii/discussions/8557 2024-02-20 19:51:13 +01:00
James Cole
fe33352ec1 Fix translation for https://github.com/firefly-iii/firefly-iii/issues/8555 2024-02-19 05:57:06 +01:00
github-actions
65c5249815 Auto commit for release 'develop' on 2024-02-19 2024-02-19 01:30:23 +01:00
James Cole
b1afaea1aa Various updates. 2024-02-18 11:35:08 +01:00
James Cole
997dc3814b Add host header validation 2024-02-17 08:18:49 +01:00
James Cole
b37b5b86d4 Fix https://github.com/firefly-iii/firefly-iii/issues/8544 2024-02-16 20:42:04 +01:00
github-actions
b13a4e1016 Auto commit for release 'develop' on 2024-02-15 2024-02-15 01:29:20 +01:00
James Cole
7897ebc4d5 Update code 2024-02-14 19:47:35 +01:00
James Cole
ac17b82d85 Do not complain when the category is already set. 2024-02-14 19:34:36 +01:00
James Cole
1b1712d998 Make sure that errors in the incoming webhook url are caught. 2024-02-14 19:32:15 +01:00
James Cole
a2c0d9f7d0 The update checker can handle the development releases 2024-02-14 06:34:38 +01:00
James Cole
5b68b25c85 Merge pull request #8538 from firefly-iii/dependabot/composer/develop/phpunit/phpunit-11.0.3
Bump phpunit/phpunit from 10.5.10 to 11.0.3
2024-02-12 19:52:51 +01:00
dependabot[bot]
d3a215b575 Bump phpunit/phpunit from 10.5.10 to 11.0.3
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 10.5.10 to 11.0.3.
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/11.0.3/ChangeLog-11.0.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/10.5.10...11.0.3)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 03:35:09 +00:00
github-actions
5c352a0d3e Auto commit for release 'develop' on 2024-02-12 2024-02-12 01:29:15 +01:00
James Cole
fded058ea6 Rebuild frontend. 2024-02-11 19:16:27 +01:00
James Cole
99f041b114 Editable up until date. 2024-02-11 10:43:14 +01:00
James Cole
283b594995 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	app/Helpers/Collector/GroupCollector.php
2024-02-10 08:29:59 +01:00
James Cole
723aa65e7a Inline edit for v2 2024-02-10 08:28:59 +01:00
github-actions
64d315ad51 Auto commit for release 'develop' on 2024-02-08 2024-02-08 01:29:34 +01:00
James Cole
d0844356cb Fix https://github.com/firefly-iii/firefly-iii/issues/8521 2024-02-07 06:14:40 +01:00
James Cole
ba8d65835a Remove unused translation. 2024-02-07 06:02:43 +01:00
github-actions
fa3343f437 Auto commit for release 'v6.1.9' on 2024-02-05 2024-02-05 19:53:12 +01:00
James Cole
c5b8a951d2 Update changelog for new release. 2024-02-05 06:37:16 +01:00
James Cole
20b1fc05cb Don't put a "v" in front of the version when it's a develop version. 2024-02-05 06:29:19 +01:00
github-actions
ab441d7d0c Auto commit for release 'develop' on 2024-02-05 2024-02-05 01:31:01 +01:00
James Cole
b621d14bdf Merge branch 'main' into develop 2024-02-04 06:42:12 +01:00
James Cole
94730e998b Better workflow for prune. 2024-02-04 06:39:55 +01:00
James Cole
23c2f76e52 Merge branch 'main' into develop 2024-02-04 06:36:42 +01:00
James Cole
40196d48b2 Back to monday/thursday 2024-02-04 06:36:32 +01:00
James Cole
ef3c2eb701 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-02-04 06:35:50 +01:00
James Cole
6f79ab2a70 Merge branch 'main' into develop 2024-02-03 20:00:52 +01:00
James Cole
c1469f016e Add version to develop branch. 2024-02-03 20:00:41 +01:00
532 changed files with 93579 additions and 90469 deletions

View File

@@ -35,36 +35,39 @@ $finder = PhpCsFixer\Finder::create()
$config = new PhpCsFixer\Config();
return $config->setRules([
'no_unused_imports' => true,
'@PhpCsFixer' => true,
'@PHP83Migration' => true,
'@PhpCsFixer:risky' => 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'],
'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,
'type_declaration_spaces' => false,
'cast_spaces' => false,
'binary_operator_spaces' => [
'default' => 'at_least_single_space',
'operators' => [
'=>' => 'align_single_space_by_scope',
'=' => 'align_single_space_minimal_by_scope',
'??=' => 'align_single_space_minimal_by_scope',
],
],
'void_return' => 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,
'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.
'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' => [
'=>' => 'align_single_space_by_scope',
'=' => 'align_single_space_minimal_by_scope',
'??=' => 'align_single_space_minimal_by_scope',
],
],
])
->setFinder($finder);

View File

@@ -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": [],

View File

@@ -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": [],

View File

@@ -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

View File

@@ -2,6 +2,9 @@
name: "Chore - Prune old builds"
permissions:
actions: write
on:
schedule:
- cron: '0 0 * * *'
@@ -14,7 +17,7 @@ jobs:
- name: Prune cancelled/skipped runs
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_ACTIONS_PERSONAL_ACCESS_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const cancelled = await github.rest.actions.listWorkflowRunsForRepo({
owner: context.repo.owner,
@@ -44,7 +47,7 @@ jobs:
- name: Prune runs older than 3 days
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_ACTIONS_PERSONAL_ACCESS_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const days_to_expiration = 3;
const ms_in_day = 86400000;

View File

@@ -51,7 +51,7 @@ jobs:
CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
- name: Cleanup translations
id: cleanup-transactions
uses: JC5/firefly-iii-dev@v30
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@v30
uses: JC5/firefly-iii-dev@v34
with:
action: 'ff3:changelog'
output: ''
@@ -69,16 +69,26 @@ jobs:
GH_TOKEN: ${{ secrets.CHANGELOG_TOKEN }}
- name: Extract changelog
id: extract-changelog
uses: JC5/firefly-iii-dev@v30
uses: JC5/firefly-iii-dev@v34
with:
action: 'ff3:extract-changelog'
output: 'output'
env:
FIREFLY_III_ROOT: /github/workspace
GH_TOKEN: ""
- name: Replace version
id: replace-version
uses: JC5/firefly-iii-dev@v34
with:
action: 'ff3:version'
output: ''
env:
FIREFLY_III_ROOT: /github/workspace
GH_TOKEN: ""
FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
- name: Generate JSON v1
id: json-v1
uses: JC5/firefly-iii-dev@v30
uses: JC5/firefly-iii-dev@v34
with:
action: 'ff3:json-translations v1'
output: ''
@@ -87,7 +97,7 @@ jobs:
GH_TOKEN: ''
- name: Generate JSON v2
id: json-v2
uses: JC5/firefly-iii-dev@v30
uses: JC5/firefly-iii-dev@v34
with:
action: 'ff3:json-translations v2'
output: ''
@@ -96,7 +106,7 @@ jobs:
GH_TOKEN: ''
- name: Code cleanup
id: code-cleanup
uses: JC5/firefly-iii-dev@v30
uses: JC5/firefly-iii-dev@v34
with:
action: 'ff3:code'
output: ''
@@ -109,7 +119,7 @@ jobs:
npm run build
- name: Build old JS
id: old-js
uses: JC5/firefly-iii-dev@v30
uses: JC5/firefly-iii-dev@v34
with:
action: 'ff3:old-js'
output: ''

View File

@@ -77,7 +77,7 @@ class AccountController extends Controller
$query = $data['query'];
$date = $data['date'] ?? today(config('app.timezone'));
$return = [];
$result = $this->repository->searchAccount((string) $query, $types, $this->parameters->get('limit'));
$result = $this->repository->searchAccount((string)$query, $types, $this->parameters->get('limit'));
// TODO this code is duplicated in the V2 Autocomplete controller, which means this code is due to be deprecated.
$defaultCurrency = app('amount')->getDefaultCurrency();
@@ -97,11 +97,11 @@ class AccountController extends Controller
}
$return[] = [
'id' => (string) $account->id,
'id' => (string)$account->id,
'name' => $account->name,
'name_with_balance' => $nameWithBalance,
'type' => $account->accountType->type,
'currency_id' => (string) $currency->id,
'currency_id' => (string)$currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
@@ -114,8 +114,8 @@ class AccountController extends Controller
$return,
static function (array $left, array $right) {
$order = [AccountType::ASSET, AccountType::REVENUE, AccountType::EXPENSE];
$posA = (int) array_search($left['type'], $order, true);
$posB = (int) array_search($right['type'], $order, true);
$posA = (int)array_search($left['type'], $order, true);
$posB = (int)array_search($right['type'], $order, true);
return $posA - $posB;
}

View File

@@ -66,7 +66,7 @@ class BillController extends Controller
$filtered = $result->map(
static function (Bill $item) {
return [
'id' => (string) $item->id,
'id' => (string)$item->id,
'name' => $item->name,
'active' => $item->active,
];

View File

@@ -66,7 +66,7 @@ class BudgetController extends Controller
$filtered = $result->map(
static function (Budget $item) {
return [
'id' => (string) $item->id,
'id' => (string)$item->id,
'name' => $item->name,
];
}

View File

@@ -66,7 +66,7 @@ class CategoryController extends Controller
$filtered = $result->map(
static function (Category $item) {
return [
'id' => (string) $item->id,
'id' => (string)$item->id,
'name' => $item->name,
];
}

View File

@@ -68,7 +68,7 @@ class CurrencyController extends Controller
/** @var TransactionCurrency $currency */
foreach ($collection as $currency) {
$result[] = [
'id' => (string) $currency->id,
'id' => (string)$currency->id,
'name' => $currency->name,
'code' => $currency->code,
'symbol' => $currency->symbol,
@@ -94,7 +94,7 @@ class CurrencyController extends Controller
/** @var TransactionCurrency $currency */
foreach ($collection as $currency) {
$result[] = [
'id' => (string) $currency->id,
'id' => (string)$currency->id,
'name' => sprintf('%s (%s)', $currency->name, $currency->code),
'code' => $currency->code,
'symbol' => $currency->symbol,

View File

@@ -68,7 +68,7 @@ class ObjectGroupController extends Controller
/** @var ObjectGroup $objectGroup */
foreach ($result as $objectGroup) {
$return[] = [
'id' => (string) $objectGroup->id,
'id' => (string)$objectGroup->id,
'name' => $objectGroup->title,
'title' => $objectGroup->title,
];

View File

@@ -75,14 +75,14 @@ class PiggyBankController extends Controller
$currency = $this->accountRepository->getAccountCurrency($piggy->account) ?? $defaultCurrency;
$objectGroup = $piggy->objectGroups()->first();
$response[] = [
'id' => (string) $piggy->id,
'id' => (string)$piggy->id,
'name' => $piggy->name,
'currency_id' => (string) $currency->id,
'currency_id' => (string)$currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'object_group_id' => null === $objectGroup ? null : (string) $objectGroup->id,
'object_group_id' => null === $objectGroup ? null : (string)$objectGroup->id,
'object_group_title' => $objectGroup?->title,
];
}
@@ -107,7 +107,7 @@ class PiggyBankController extends Controller
$currentAmount = $this->piggyRepository->getRepetition($piggy)->currentamount ?? '0';
$objectGroup = $piggy->objectGroups()->first();
$response[] = [
'id' => (string) $piggy->id,
'id' => (string)$piggy->id,
'name' => $piggy->name,
'name_with_balance' => sprintf(
'%s (%s / %s)',
@@ -115,12 +115,12 @@ class PiggyBankController extends Controller
app('amount')->formatAnything($currency, $currentAmount, false),
app('amount')->formatAnything($currency, $piggy->targetamount, false),
),
'currency_id' => (string) $currency->id,
'currency_id' => (string)$currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'object_group_id' => null === $objectGroup ? null : (string) $objectGroup->id,
'object_group_id' => null === $objectGroup ? null : (string)$objectGroup->id,
'object_group_title' => $objectGroup?->title,
];
}

View File

@@ -66,7 +66,7 @@ class RecurrenceController extends Controller
/** @var Recurrence $recurrence */
foreach ($recurrences as $recurrence) {
$response[] = [
'id' => (string) $recurrence->id,
'id' => (string)$recurrence->id,
'name' => $recurrence->title,
'description' => $recurrence->description,
];

View File

@@ -65,7 +65,7 @@ class RuleController extends Controller
/** @var Rule $rule */
foreach ($rules as $rule) {
$response[] = [
'id' => (string) $rule->id,
'id' => (string)$rule->id,
'name' => $rule->title,
'description' => $rule->description,
];

View File

@@ -65,7 +65,7 @@ class RuleGroupController extends Controller
/** @var RuleGroup $group */
foreach ($groups as $group) {
$response[] = [
'id' => (string) $group->id,
'id' => (string)$group->id,
'name' => $group->title,
'description' => $group->description,
];

View File

@@ -68,7 +68,7 @@ class TagController extends Controller
/** @var Tag $tag */
foreach ($result as $tag) {
$array[] = [
'id' => (string) $tag->id,
'id' => (string)$tag->id,
'name' => $tag->tag,
'tag' => $tag->tag,
];

View File

@@ -76,8 +76,8 @@ class TransactionController extends Controller
/** @var TransactionJournal $journal */
foreach ($filtered as $journal) {
$array[] = [
'id' => (string) $journal->id,
'transaction_group_id' => (string) $journal->transaction_group_id,
'id' => (string)$journal->id,
'transaction_group_id' => (string)$journal->transaction_group_id,
'name' => $journal->description,
'description' => $journal->description,
];
@@ -96,7 +96,7 @@ class TransactionController extends Controller
$result = new Collection();
if (is_numeric($data['query'])) {
// search for group, not journal.
$firstResult = $this->groupRepository->find((int) $data['query']);
$firstResult = $this->groupRepository->find((int)$data['query']);
if (null !== $firstResult) {
// group may contain multiple journals, each a result:
foreach ($firstResult->transactionJournals as $journal) {
@@ -114,8 +114,8 @@ class TransactionController extends Controller
/** @var TransactionJournal $journal */
foreach ($result as $journal) {
$array[] = [
'id' => (string) $journal->id,
'transaction_group_id' => (string) $journal->transaction_group_id,
'id' => (string)$journal->id,
'transaction_group_id' => (string)$journal->transaction_group_id,
'name' => sprintf('#%d: %s', $journal->transaction_group_id, $journal->description),
'description' => sprintf('#%d: %s', $journal->transaction_group_id, $journal->description),
];

View File

@@ -65,7 +65,7 @@ class TransactionTypeController extends Controller
foreach ($types as $type) {
// different key for consistency.
$array[] = [
'id' => (string) $type->id,
'id' => (string)$type->id,
'name' => $type->type,
'type' => $type->type,
];

View File

@@ -104,7 +104,7 @@ class AccountController extends Controller
}
$currentSet = [
'label' => $account->name,
'currency_id' => (string) $currency->id,
'currency_id' => (string)$currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,

View File

@@ -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;
}
}

View File

@@ -70,8 +70,8 @@ class TransactionController extends Controller
// to respond to what is in the $query.
// this is OK because only one thing can be in the query at the moment.
if ($this->isUpdateTransactionAccount($params)) {
$original = $this->repository->find((int) $params['where']['account_id']);
$destination = $this->repository->find((int) $params['update']['account_id']);
$original = $this->repository->find((int)$params['where']['account_id']);
$destination = $this->repository->find((int)$params['update']['account_id']);
/** @var AccountDestroyService $service */
$service = app(AccountDestroyService::class);

View File

@@ -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(),

View File

@@ -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;
}
}

View File

@@ -75,28 +75,28 @@ class TagController extends Controller
$genericSet = $collector->getExtractedJournals();
foreach ($genericSet as $journal) {
$currencyId = (int) $journal['currency_id'];
$foreignCurrencyId = (int) $journal['foreign_currency_id'];
$currencyId = (int)$journal['currency_id'];
$foreignCurrencyId = (int)$journal['foreign_currency_id'];
if (0 !== $currencyId) {
$response[$currencyId] ??= [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,
'currency_id' => (string)$currencyId,
'currency_code' => $journal['currency_code'],
];
$response[$currencyId]['difference'] = bcadd($response[$currencyId]['difference'], $journal['amount']);
$response[$currencyId]['difference_float'] = (float) $response[$currencyId]['difference']; // float but on purpose.
$response[$currencyId]['difference_float'] = (float)$response[$currencyId]['difference']; // float but on purpose.
}
if (0 !== $foreignCurrencyId) {
$response[$foreignCurrencyId] ??= [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $foreignCurrencyId,
'currency_id' => (string)$foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignCurrencyId]['difference'] = bcadd($response[$foreignCurrencyId]['difference'], $journal['foreign_amount']);
$response[$foreignCurrencyId]['difference_float'] = (float) $response[$foreignCurrencyId]['difference']; // float but on purpose.
$response[$foreignCurrencyId]['difference_float'] = (float)$response[$foreignCurrencyId]['difference']; // float but on purpose.
}
}
@@ -130,8 +130,8 @@ class TagController extends Controller
/** @var array $journal */
foreach ($genericSet as $journal) {
$currencyId = (int) $journal['currency_id'];
$foreignCurrencyId = (int) $journal['foreign_currency_id'];
$currencyId = (int)$journal['currency_id'];
$foreignCurrencyId = (int)$journal['foreign_currency_id'];
/** @var array $tag */
foreach ($journal['tags'] as $tag) {
@@ -142,15 +142,15 @@ class TagController extends Controller
// on currency ID
if (0 !== $currencyId) {
$response[$key] ??= [
'id' => (string) $tagId,
'id' => (string)$tagId,
'name' => $tag['name'],
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $currencyId,
'currency_id' => (string)$currencyId,
'currency_code' => $journal['currency_code'],
];
$response[$key]['difference'] = bcadd($response[$key]['difference'], $journal['amount']);
$response[$key]['difference_float'] = (float) $response[$key]['difference']; // float but on purpose.
$response[$key]['difference_float'] = (float)$response[$key]['difference']; // float but on purpose.
}
// on foreign ID
@@ -158,11 +158,11 @@ class TagController extends Controller
$response[$foreignKey] = $journal[$foreignKey] ?? [
'difference' => '0',
'difference_float' => 0,
'currency_id' => (string) $foreignCurrencyId,
'currency_id' => (string)$foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd($response[$foreignKey]['difference'], $journal['foreign_amount']);
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference']; // float but on purpose.
$response[$foreignKey]['difference_float'] = (float)$response[$foreignKey]['difference']; // float but on purpose.
}
}
}

View File

@@ -66,7 +66,7 @@ class DestroyController extends Controller
*/
public function destroy(Attachment $attachment): JsonResponse
{
if(true === auth()->user()->hasRole('demo')) {
if (true === auth()->user()->hasRole('demo')) {
Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
throw new NotFoundHttpException();

View File

@@ -75,7 +75,7 @@ class ShowController extends Controller
*/
public function download(Attachment $attachment): LaravelResponse
{
if(true === auth()->user()->hasRole('demo')) {
if (true === auth()->user()->hasRole('demo')) {
Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
throw new NotFoundHttpException();
@@ -123,7 +123,7 @@ class ShowController extends Controller
*/
public function index(): JsonResponse
{
if(true === auth()->user()->hasRole('demo')) {
if (true === auth()->user()->hasRole('demo')) {
Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
throw new NotFoundHttpException();
@@ -161,7 +161,7 @@ class ShowController extends Controller
*/
public function show(Attachment $attachment): JsonResponse
{
if(true === auth()->user()->hasRole('demo')) {
if (true === auth()->user()->hasRole('demo')) {
Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
throw new NotFoundHttpException();

View File

@@ -74,7 +74,7 @@ class StoreController extends Controller
*/
public function store(StoreRequest $request): JsonResponse
{
if(true === auth()->user()->hasRole('demo')) {
if (true === auth()->user()->hasRole('demo')) {
Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
throw new NotFoundHttpException();
@@ -98,7 +98,7 @@ class StoreController extends Controller
*/
public function upload(Request $request, Attachment $attachment): JsonResponse
{
if(true === auth()->user()->hasRole('demo')) {
if (true === auth()->user()->hasRole('demo')) {
Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
throw new NotFoundHttpException();

View File

@@ -69,7 +69,7 @@ class UpdateController extends Controller
*/
public function update(UpdateRequest $request, Attachment $attachment): JsonResponse
{
if(true === auth()->user()->hasRole('demo')) {
if (true === auth()->user()->hasRole('demo')) {
Log::channel('audit')->warning(sprintf('Demo user tries to access attachment API in %s', __METHOD__));
throw new NotFoundHttpException();

View File

@@ -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:
@@ -152,7 +135,7 @@ class BasicController extends Controller
/** @var array $transactionJournal */
foreach ($set as $transactionJournal) {
$currencyId = (int) $transactionJournal['currency_id'];
$currencyId = (int)$transactionJournal['currency_id'];
$incomes[$currencyId] ??= '0';
$incomes[$currencyId] = bcadd(
$incomes[$currencyId],
@@ -170,7 +153,7 @@ class BasicController extends Controller
/** @var array $transactionJournal */
foreach ($set as $transactionJournal) {
$currencyId = (int) $transactionJournal['currency_id'];
$currencyId = (int)$transactionJournal['currency_id'];
$expenses[$currencyId] ??= '0';
$expenses[$currencyId] = bcadd($expenses[$currencyId], $transactionJournal['amount']);
$sums[$currencyId] ??= '0';
@@ -189,7 +172,7 @@ class BasicController extends Controller
'key' => sprintf('balance-in-%s', $currency->code),
'title' => trans('firefly.box_balance_in_currency', ['currency' => $currency->symbol]),
'monetary_value' => $sums[$currencyId] ?? '0',
'currency_id' => (string) $currency->id,
'currency_id' => (string)$currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
@@ -202,7 +185,7 @@ class BasicController extends Controller
'key' => sprintf('spent-in-%s', $currency->code),
'title' => trans('firefly.box_spent_in_currency', ['currency' => $currency->symbol]),
'monetary_value' => $expenses[$currencyId] ?? '0',
'currency_id' => (string) $currency->id,
'currency_id' => (string)$currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
@@ -214,7 +197,7 @@ class BasicController extends Controller
'key' => sprintf('earned-in-%s', $currency->code),
'title' => trans('firefly.box_earned_in_currency', ['currency' => $currency->symbol]),
'monetary_value' => $incomes[$currencyId] ?? '0',
'currency_id' => (string) $currency->id,
'currency_id' => (string)$currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
@@ -248,7 +231,7 @@ class BasicController extends Controller
'key' => sprintf('bills-paid-in-%s', $info['code']),
'title' => trans('firefly.box_bill_paid_in_currency', ['currency' => $info['symbol']]),
'monetary_value' => $amount,
'currency_id' => (string) $info['id'],
'currency_id' => (string)$info['id'],
'currency_code' => $info['code'],
'currency_symbol' => $info['symbol'],
'currency_decimal_places' => $info['decimal_places'],
@@ -267,7 +250,7 @@ class BasicController extends Controller
'key' => sprintf('bills-unpaid-in-%s', $info['code']),
'title' => trans('firefly.box_bill_unpaid_in_currency', ['currency' => $info['symbol']]),
'monetary_value' => $amount,
'currency_id' => (string) $info['id'],
'currency_id' => (string)$info['id'],
'currency_code' => $info['code'],
'currency_symbol' => $info['symbol'],
'currency_decimal_places' => $info['decimal_places'],
@@ -294,21 +277,21 @@ class BasicController extends Controller
foreach ($spent as $row) {
// either an amount was budgeted or 0 is available.
$amount = (string) ($available[$row['currency_id']] ?? '0');
$amount = (string)($available[$row['currency_id']] ?? '0');
$spentInCurrency = $row['sum'];
$leftToSpend = bcadd($amount, $spentInCurrency);
$days = $today->diffInDays($end) + 1;
$perDay = '0';
if (0 !== $days && bccomp($leftToSpend, '0') > -1) {
$perDay = bcdiv($leftToSpend, (string) $days);
$perDay = bcdiv($leftToSpend, (string)$days);
}
$return[] = [
'key' => sprintf('left-to-spend-in-%s', $row['currency_code']),
'title' => trans('firefly.box_left_to_spend_in_currency', ['currency' => $row['currency_symbol']]),
'monetary_value' => $leftToSpend,
'currency_id' => (string) $row['currency_id'],
'currency_id' => (string)$row['currency_id'],
'currency_code' => $row['currency_code'],
'currency_symbol' => $row['currency_symbol'],
'currency_decimal_places' => $row['currency_decimal_places'],
@@ -368,7 +351,7 @@ class BasicController extends Controller
'key' => sprintf('net-worth-in-%s', $data['currency_code']),
'title' => trans('firefly.box_net_worth_in_currency', ['currency' => $data['currency_symbol']]),
'monetary_value' => $amount,
'currency_id' => (string) $data['currency_id'],
'currency_id' => (string)$data['currency_id'],
'currency_code' => $data['currency_code'],
'currency_symbol' => $data['currency_symbol'],
'currency_decimal_places' => $data['currency_decimal_places'],
@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -70,7 +70,7 @@ class AttemptController extends Controller
if ($message->webhook_id !== $webhook->id) {
throw new FireflyException('200040: Webhook and webhook message are no match');
}
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->warning(sprintf('User lists webhook attempts of webhook #%d and message #%d, but webhooks are DISABLED.', $webhook->id, $message->id));
throw new NotFoundHttpException('Webhooks are not enabled.');
@@ -114,7 +114,7 @@ class AttemptController extends Controller
throw new FireflyException('200041: Webhook message and webhook attempt are no match');
}
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->warning(sprintf('User views single webhook attempt #%d of webhook #%d and message #%d, but webhooks are DISABLED', $attempt->id, $webhook->id, $message->id));
throw new NotFoundHttpException('Webhooks are not enabled.');

View File

@@ -62,7 +62,7 @@ class DestroyController extends Controller
*/
public function destroy(Webhook $webhook): JsonResponse
{
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->warning(sprintf('User tries to destroy webhook #%d. but webhooks are DISABLED.', $webhook->id));
throw new NotFoundHttpException('Webhooks are not enabled.');
@@ -120,7 +120,7 @@ class DestroyController extends Controller
throw new FireflyException('200040: Webhook and webhook message are no match');
}
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->warning(sprintf('User tries to destroy webhook #%d, message #%d, but webhooks are DISABLED.', $webhook->id, $message->id));
throw new NotFoundHttpException('Webhooks are not enabled.');

View File

@@ -66,7 +66,7 @@ class MessageController extends Controller
*/
public function index(Webhook $webhook): JsonResponse
{
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->warning(sprintf('User tries to view messages of webhook #%d, but webhooks are DISABLED.', $webhook->id));
throw new NotFoundHttpException('Webhooks are not enabled.');
@@ -106,7 +106,7 @@ class MessageController extends Controller
if ($message->webhook_id !== $webhook->id) {
throw new FireflyException('200040: Webhook and webhook message are no match');
}
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->warning(sprintf('User tries to view message #%d of webhook #%d, but webhooks are DISABLED.', $message->id, $webhook->id));
throw new NotFoundHttpException('Webhooks are not enabled.');

View File

@@ -71,7 +71,7 @@ class ShowController extends Controller
*/
public function index(): JsonResponse
{
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->info('User tries to view all webhooks, but webhooks are DISABLED.');
throw new NotFoundHttpException('Webhooks are not enabled.');
@@ -106,7 +106,7 @@ class ShowController extends Controller
*/
public function show(Webhook $webhook): JsonResponse
{
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->info(sprintf('User tries to view webhook #%d, but webhooks are DISABLED.', $webhook->id));
throw new NotFoundHttpException('Webhooks are not enabled.');
@@ -131,7 +131,7 @@ class ShowController extends Controller
*/
public function triggerTransaction(Webhook $webhook, TransactionGroup $group): JsonResponse
{
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->info(sprintf('User tries to trigger webhook #%d on transaction group #%d, but webhooks are DISABLED.', $webhook->id, $group->id));
throw new NotFoundHttpException('Webhooks are not enabled.');

View File

@@ -60,7 +60,7 @@ class StoreController extends Controller
public function store(CreateRequest $request): JsonResponse
{
$data = $request->getData();
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->info('User tries to store new webhook, but webhooks are DISABLED.', $data);
throw new NotFoundHttpException('Webhooks are not enabled.');

View File

@@ -57,7 +57,7 @@ class SubmitController extends Controller
*/
public function submit(Webhook $webhook): JsonResponse
{
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->info(sprintf('User tries to submit webhook #%d, but webhooks are DISABLED.', $webhook->id));
throw new NotFoundHttpException('Webhooks are not enabled.');

View File

@@ -60,7 +60,7 @@ class UpdateController extends Controller
public function update(Webhook $webhook, UpdateRequest $request): JsonResponse
{
$data = $request->getData();
if(false === config('firefly.allow_webhooks')) {
if (false === config('firefly.allow_webhooks')) {
Log::channel('audit')->info(sprintf('User tries to update webhook #%d, but webhooks are DISABLED.', $webhook->id), $data);
throw new NotFoundHttpException('Webhooks are not enabled.');

View File

@@ -73,7 +73,7 @@ class MoveTransactionsRequest extends FormRequest
}
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}
@@ -83,12 +83,12 @@ class MoveTransactionsRequest extends FormRequest
$data = $validator->getData();
$repository = app(AccountRepositoryInterface::class);
$repository->setUser(auth()->user());
$original = $repository->find((int) $data['original_account']);
$destination = $repository->find((int) $data['destination_account']);
$original = $repository->find((int)$data['original_account']);
$destination = $repository->find((int)$data['destination_account']);
// not the same type:
if ($original->accountType->type !== $destination->accountType->type) {
$validator->errors()->add('title', (string) trans('validation.same_account_type'));
$validator->errors()->add('title', (string)trans('validation.same_account_type'));
return;
}
@@ -98,7 +98,7 @@ class MoveTransactionsRequest extends FormRequest
// check different scenario's.
if (null === $originalCurrency xor null === $destinationCurrency) {
$validator->errors()->add('title', (string) trans('validation.same_account_currency'));
$validator->errors()->add('title', (string)trans('validation.same_account_currency'));
return;
}
@@ -107,7 +107,7 @@ class MoveTransactionsRequest extends FormRequest
return;
}
if ($originalCurrency->code !== $destinationCurrency->code) {
$validator->errors()->add('title', (string) trans('validation.same_account_currency'));
$validator->errors()->add('title', (string)trans('validation.same_account_currency'));
}
}
}

View File

@@ -73,7 +73,7 @@ class TransactionRequest extends FormRequest
$this->validateTransactionQuery($validator);
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -46,7 +46,7 @@ class DateRequest extends FormRequest
{
$start = $this->getCarbonDate('start');
$end = $this->getCarbonDate('end');
if($start->diffInYears($end) > 5) {
if ($start->diffInYears($end) > 5) {
throw new FireflyException('Date range out of range.');
}

View File

@@ -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',
];
}
}

View File

@@ -88,7 +88,7 @@ class Request extends FormRequest
}
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -104,7 +104,7 @@ class StoreRequest extends FormRequest
}
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -109,7 +109,7 @@ class UpdateRequest extends FormRequest
}
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -92,7 +92,7 @@ class StoreRequest extends FormRequest
$this->validateAutoBudgetAmount($validator);
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -104,7 +104,7 @@ class UpdateRequest extends FormRequest
$this->validateAutoBudgetAmount($validator);
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -83,12 +83,12 @@ class UpdateRequest extends FormRequest
$start = new Carbon($data['start']);
$end = new Carbon($data['end']);
if ($end->isBefore($start)) {
$validator->errors()->add('end', (string) trans('validation.date_after'));
$validator->errors()->add('end', (string)trans('validation.date_after'));
}
}
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -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.
*/
@@ -132,67 +191,8 @@ class StoreRequest extends FormRequest
$this->validateAccountInformation($validator);
}
);
if($validator->fails()) {
if ($validator->fails()) {
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;
}
}

View File

@@ -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.
*/
@@ -142,72 +206,8 @@ class UpdateRequest extends FormRequest
$this->valUpdateAccountInfo($validator);
}
);
if($validator->fails()) {
if ($validator->fails()) {
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;
}
}

View File

@@ -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.
*/
@@ -109,7 +145,7 @@ class StoreRequest extends FormRequest
$this->atLeastOneActiveAction($validator);
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}
@@ -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;
}
}

View File

@@ -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',
];
}
}

View File

@@ -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',
];
}
}

View File

@@ -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.
*/
@@ -119,7 +163,7 @@ class UpdateRequest extends FormRequest
$this->atLeastOneValidAction($validator);
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}
@@ -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;
}
}

View File

@@ -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',
];
}
}

View File

@@ -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);
@@ -71,10 +63,18 @@ class TriggerRequest extends FormRequest
private function getAccounts(): array
{
if(null === $this->get('accounts')) {
if (null === $this->get('accounts')) {
return [];
}
return $this->get('accounts');
}
public function rules(): array
{
return [
'start' => 'date',
'end' => 'date|after_or_equal:start',
];
}
}

View File

@@ -69,134 +69,6 @@ class StoreRequest extends FormRequest
// TODO include location and ability to process it.
}
/**
* The rules that the incoming request must be matched against.
*/
public function rules(): array
{
app('log')->debug('Collect rules of TransactionStoreRequest');
$validProtocols = config('firefly.valid_url_protocols');
return [
// basic fields for group:
'group_title' => 'min:1|max:1000|nullable',
'error_if_duplicate_hash' => [new IsBoolean()],
'apply_rules' => [new IsBoolean()],
// transaction rules (in array for splits):
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
'transactions.*.date' => ['required', new IsDateOrTime()],
'transactions.*.order' => 'numeric|min:0',
// 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' => 'numeric|exists:transaction_currencies,id|nullable',
'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
// amount
'transactions.*.amount' => ['required', 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',
'transactions.*.source_iban' => 'min:1|max:255|nullable|iban',
'transactions.*.source_number' => 'min:1|max:255|nullable',
'transactions.*.source_bic' => 'min:1|max:255|nullable|bic',
// destination of transaction
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()],
'transactions.*.destination_name' => 'min:1|max:255|nullable',
'transactions.*.destination_iban' => 'min:1|max:255|nullable|iban',
'transactions.*.destination_number' => 'min:1|max:255|nullable',
'transactions.*.destination_bic' => 'min:1|max:255|nullable|bic',
// budget, category, bill and piggy
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser()],
'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()],
'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser()],
'transactions.*.piggy_bank_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',
'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
{
$validator->after(
function (Validator $validator): void {
// must be valid array.
$this->validateTransactionArray($validator);
// must submit at least one transaction.
app('log')->debug('Now going to validateOneTransaction');
$this->validateOneTransaction($validator);
app('log')->debug('Now done with validateOneTransaction');
// all journals must have a description
$this->validateDescriptions($validator);
// all transaction types must be equal:
$this->validateTransactionTypes($validator);
// validate foreign currency info
$this->validateForeignCurrencyInformation($validator);
// validate all account info
$this->validateAccountInformation($validator);
// validate source/destination is equal, depending on the transaction journal type.
$this->validateEqualAccounts($validator);
// the group must have a description if > 1 journal.
$this->validateGroupDescription($validator);
}
);
if($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}
/**
* Get transaction data.
*/
@@ -291,4 +163,132 @@ class StoreRequest extends FormRequest
return $return;
}
/**
* The rules that the incoming request must be matched against.
*/
public function rules(): array
{
app('log')->debug('Collect rules of TransactionStoreRequest');
$validProtocols = config('firefly.valid_url_protocols');
return [
// basic fields for group:
'group_title' => 'min:1|max:1000|nullable',
'error_if_duplicate_hash' => [new IsBoolean()],
'apply_rules' => [new IsBoolean()],
// transaction rules (in array for splits):
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
'transactions.*.date' => ['required', new IsDateOrTime()],
'transactions.*.order' => 'numeric|min:0',
// 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' => 'numeric|exists:transaction_currencies,id|nullable',
'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
// amount
'transactions.*.amount' => ['required', 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',
'transactions.*.source_iban' => 'min:1|max:255|nullable|iban',
'transactions.*.source_number' => 'min:1|max:255|nullable',
'transactions.*.source_bic' => 'min:1|max:255|nullable|bic',
// destination of transaction
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()],
'transactions.*.destination_name' => 'min:1|max:255|nullable',
'transactions.*.destination_iban' => 'min:1|max:255|nullable|iban',
'transactions.*.destination_number' => 'min:1|max:255|nullable',
'transactions.*.destination_bic' => 'min:1|max:255|nullable|bic',
// budget, category, bill and piggy
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser()],
'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()],
'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser()],
'transactions.*.piggy_bank_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',
'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
{
$validator->after(
function (Validator $validator): void {
// must be valid array.
$this->validateTransactionArray($validator);
// must submit at least one transaction.
app('log')->debug('Now going to validateOneTransaction');
$this->validateOneTransaction($validator);
app('log')->debug('Now done with validateOneTransaction');
// all journals must have a description
$this->validateDescriptions($validator);
// all transaction types must be equal:
$this->validateTransactionTypes($validator);
// validate foreign currency info
$this->validateForeignCurrencyInformation($validator);
// validate all account info
$this->validateAccountInformation($validator);
// validate source/destination is equal, depending on the transaction journal type.
$this->validateEqualAccounts($validator);
// the group must have a description if > 1 journal.
$this->validateGroupDescription($validator);
}
);
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}
}

View File

@@ -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.
*
@@ -258,7 +137,7 @@ class UpdateRequest extends FormRequest
{
foreach ($this->integerFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->integerFromValue((string) $transaction[$fieldName]);
$current[$fieldName] = $this->integerFromValue((string)$transaction[$fieldName]);
}
}
@@ -273,7 +152,7 @@ class UpdateRequest extends FormRequest
{
foreach ($this->stringFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->clearString((string) $transaction[$fieldName]);
$current[$fieldName] = $this->clearString((string)$transaction[$fieldName]);
}
}
@@ -288,7 +167,7 @@ class UpdateRequest extends FormRequest
{
foreach ($this->textareaFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->clearStringKeepNewlines((string) $transaction[$fieldName]); // keep newlines
$current[$fieldName] = $this->clearStringKeepNewlines((string)$transaction[$fieldName]); // keep newlines
}
}
@@ -304,8 +183,8 @@ class UpdateRequest extends FormRequest
foreach ($this->dateFields as $fieldName) {
app('log')->debug(sprintf('Now at date field %s', $fieldName));
if (array_key_exists($fieldName, $transaction)) {
app('log')->debug(sprintf('New value: "%s"', (string) $transaction[$fieldName]));
$current[$fieldName] = $this->dateFromValue((string) $transaction[$fieldName]);
app('log')->debug(sprintf('New value: "%s"', (string)$transaction[$fieldName]));
$current[$fieldName] = $this->dateFromValue((string)$transaction[$fieldName]);
}
}
@@ -320,7 +199,7 @@ class UpdateRequest extends FormRequest
{
foreach ($this->booleanFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->convertBoolean((string) $transaction[$fieldName]);
$current[$fieldName] = $this->convertBoolean((string)$transaction[$fieldName]);
}
}
@@ -355,11 +234,132 @@ class UpdateRequest extends FormRequest
$current[$fieldName] = sprintf('%.12f', $value);
}
if (!is_float($value)) {
$current[$fieldName] = (string) $value;
$current[$fieldName] = (string)$value;
}
}
}
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());
}
}
}

View File

@@ -78,7 +78,7 @@ class StoreRequest extends FormRequest
$this->validateExistingLink($validator);
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -78,7 +78,7 @@ class UpdateRequest extends FormRequest
$this->validateUpdate($validator);
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -98,7 +98,7 @@ class UserUpdateRequest extends FormRequest
}
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -72,7 +72,7 @@ class CategoryController extends Controller
$filtered = $result->map(
static function (Category $item) {
return [
'id' => (string) $item->id,
'id' => (string)$item->id,
'name' => $item->name,
];
}

View File

@@ -72,10 +72,10 @@ class TagController extends Controller
$filtered = $result->map(
static function (Tag $item) {
return [
'id' => (string) $item->id,
'name' => $item->tag,
'value' => (string) $item->id,
'label' => $item->tag,
'id' => (string)$item->id,
'name' => $item->tag,
'value' => (string)$item->id,
'label' => $item->tag,
];
}
);

View File

@@ -126,23 +126,23 @@ class AccountController extends Controller
$currency = $default;
}
$currentSet = [
'label' => $account->name,
'label' => $account->name,
// the currency that belongs to the account.
'currency_id' => (string)$currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'currency_id' => (string)$currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
// the default currency of the user (could be the same!)
'native_currency_id' => (string)$default->id,
'native_currency_code' => $default->code,
'native_currency_symbol' => $default->symbol,
'native_currency_decimal_places' => $default->decimal_places,
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'period' => '1D',
'entries' => [],
'native_entries' => [],
'native_currency_id' => (string)$default->id,
'native_currency_code' => $default->code,
'native_currency_symbol' => $default->symbol,
'native_currency_decimal_places' => $default->decimal_places,
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'period' => '1D',
'entries' => [],
'native_entries' => [],
];
$currentStart = clone $start;
$range = app('steam')->balanceInRange($account, $start, clone $end, $currency);

View File

@@ -124,11 +124,11 @@ class BudgetController extends Controller
foreach ($rows as $row) {
$current = [
'label' => $budget->name,
'currency_id' => (string) $row['currency_id'],
'currency_id' => (string)$row['currency_id'],
'currency_code' => $row['currency_code'],
'currency_name' => $row['currency_name'],
'currency_decimal_places' => $row['currency_decimal_places'],
'native_currency_id' => (string) $row['native_currency_id'],
'native_currency_id' => (string)$row['native_currency_id'],
'native_currency_code' => $row['native_currency_code'],
'native_currency_name' => $row['native_currency_name'],
'native_currency_decimal_places' => $row['native_currency_decimal_places'],
@@ -189,12 +189,12 @@ class BudgetController extends Controller
foreach ($array as $currencyId => $block) {
$this->currencies[$currencyId] ??= TransactionCurrency::find($currencyId);
$return[$currencyId] ??= [
'currency_id' => (string) $currencyId,
'currency_id' => (string)$currencyId,
'currency_code' => $block['currency_code'],
'currency_name' => $block['currency_name'],
'currency_symbol' => $block['currency_symbol'],
'currency_decimal_places' => (int) $block['currency_decimal_places'],
'native_currency_id' => (string) $this->currency->id,
'currency_decimal_places' => (int)$block['currency_decimal_places'],
'native_currency_id' => (string)$this->currency->id,
'native_currency_code' => $this->currency->code,
'native_currency_name' => $this->currency->name,
'native_currency_symbol' => $this->currency->symbol,

View File

@@ -112,22 +112,22 @@ class CategoryController extends Controller
}
// create arrays
$return[$key] ??= [
'label' => $categoryName,
'currency_id' => (string)$currency->id,
'currency_code' => $currency->code,
'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => (string)$default->id,
'native_currency_code' => $default->code,
'native_currency_name' => $default->name,
'native_currency_symbol' => $default->symbol,
'native_currency_decimal_places' => $default->decimal_places,
'period' => null,
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'amount' => '0',
'native_amount' => '0',
'label' => $categoryName,
'currency_id' => (string)$currency->id,
'currency_code' => $currency->code,
'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => (string)$default->id,
'native_currency_code' => $default->code,
'native_currency_name' => $default->name,
'native_currency_symbol' => $default->symbol,
'native_currency_decimal_places' => $default->decimal_places,
'period' => null,
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'amount' => '0',
'native_amount' => '0',
];
// add monies

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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)
;
}
}

View File

@@ -80,7 +80,7 @@ class BalanceChartRequest extends FormRequest
}
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View File

@@ -78,7 +78,7 @@ class DashboardChartRequest extends FormRequest
}
}
);
if($validator->fails()) {
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}

View 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',
];
}
}

View File

@@ -78,145 +78,6 @@ class StoreRequest extends FormRequest
];
}
/**
* The rules that the incoming request must be matched against.
*/
public function rules(): array
{
app('log')->debug('V2: Collect rules of TransactionStoreRequest');
// at this point the userGroup can't be NULL because the
// authorize() method will complain. Loudly.
/** @var UserGroup $userGroup */
$userGroup = $this->getUserGroup();
return [
// basic fields for group:
'group_title' => 'min:1|max:1000|nullable',
'error_if_duplicate_hash' => [new IsBoolean()],
'apply_rules' => [new IsBoolean()],
// transaction rules (in array for splits):
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
'transactions.*.date' => ['required', new IsDateOrTime()],
'transactions.*.order' => 'numeric|min:0',
// 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' => 'numeric|exists:transaction_currencies,id|nullable',
'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
// amount
'transactions.*.amount' => ['required', new IsValidPositiveAmount()],
'transactions.*.foreign_amount' => ['nullable', new IsValidPositiveAmount()],
// description
'transactions.*.description' => 'nullable|min:1|max:1000',
// source of transaction
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.source_name' => 'min:1|max:255|nullable',
'transactions.*.source_iban' => 'min:1|max:255|nullable|iban',
'transactions.*.source_number' => 'min:1|max:255|nullable',
'transactions.*.source_bic' => 'min:1|max:255|nullable|bic',
// destination of transaction
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.destination_name' => 'min:1|max:255|nullable',
'transactions.*.destination_iban' => 'min:1|max:255|nullable|iban',
'transactions.*.destination_number' => 'min:1|max:255|nullable',
'transactions.*.destination_bic' => 'min:1|max:255|nullable|bic',
// budget, category, bill and piggy
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUserGroup($userGroup)],
'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUserGroup($userGroup), 'nullable'],
'transactions.*.category_name' => 'min:1|max:255|nullable',
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUserGroup($userGroup)],
'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUserGroup($userGroup)],
'transactions.*.piggy_bank_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)],
// other interesting fields
'transactions.*.reconciled' => [new IsBoolean()],
'transactions.*.notes' => 'min:1|max:32768|nullable',
'transactions.*.tags' => 'min:0|max:255',
'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' => 'min:1|max:255|nullable|url',
// SEPA fields:
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
// 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',
// TODO include location and ability to process it.
];
}
/**
* Configure the validator instance.
*/
public function withValidator(Validator $validator): void
{
/** @var User $user */
$user = auth()->user();
/** @var UserGroup $userGroup */
$userGroup = $this->getUserGroup();
$validator->after(
function (Validator $validator) use ($user, $userGroup): void {
// must be valid array.
$this->validateTransactionArray($validator); // does not need group validation.
// must submit at least one transaction.
app('log')->debug('Now going to validateOneTransaction');
$this->validateOneTransaction($validator); // does not need group validation.
app('log')->debug('Now done with validateOneTransaction');
// all journals must have a description
$this->validateDescriptions($validator); // does not need group validation.
// all transaction types must be equal:
$this->validateTransactionTypes($validator); // does not need group validation.
// validate foreign currency info
$this->validateForeignCurrencyInformation($validator); // does not need group validation.
// validate all account info
$this->validateAccountInformation($validator, $user, $userGroup);
// validate source/destination is equal, depending on the transaction journal type.
$this->validateEqualAccounts($validator);
// the group must have a description if > 1 journal.
$this->validateGroupDescription($validator);
}
);
if($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}
/**
* Get transaction data.
*/
@@ -313,4 +174,143 @@ class StoreRequest extends FormRequest
return $return;
}
/**
* The rules that the incoming request must be matched against.
*/
public function rules(): array
{
app('log')->debug('V2: Collect rules of TransactionStoreRequest');
// at this point the userGroup can't be NULL because the
// authorize() method will complain. Loudly.
/** @var UserGroup $userGroup */
$userGroup = $this->getUserGroup();
return [
// basic fields for group:
'group_title' => 'min:1|max:1000|nullable',
'error_if_duplicate_hash' => [new IsBoolean()],
'apply_rules' => [new IsBoolean()],
// transaction rules (in array for splits):
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
'transactions.*.date' => ['required', new IsDateOrTime()],
'transactions.*.order' => 'numeric|min:0',
// 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' => 'numeric|exists:transaction_currencies,id|nullable',
'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
// amount
'transactions.*.amount' => ['required', new IsValidPositiveAmount()],
'transactions.*.foreign_amount' => ['nullable', new IsValidPositiveAmount()],
// description
'transactions.*.description' => 'nullable|min:1|max:1000',
// source of transaction
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.source_name' => 'min:1|max:255|nullable',
'transactions.*.source_iban' => 'min:1|max:255|nullable|iban',
'transactions.*.source_number' => 'min:1|max:255|nullable',
'transactions.*.source_bic' => 'min:1|max:255|nullable|bic',
// destination of transaction
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.destination_name' => 'min:1|max:255|nullable',
'transactions.*.destination_iban' => 'min:1|max:255|nullable|iban',
'transactions.*.destination_number' => 'min:1|max:255|nullable',
'transactions.*.destination_bic' => 'min:1|max:255|nullable|bic',
// budget, category, bill and piggy
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUserGroup($userGroup)],
'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUserGroup($userGroup), 'nullable'],
'transactions.*.category_name' => 'min:1|max:255|nullable',
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUserGroup($userGroup)],
'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUserGroup($userGroup)],
'transactions.*.piggy_bank_name' => ['min:1', 'max:255', 'nullable', new BelongsUserGroup($userGroup)],
// other interesting fields
'transactions.*.reconciled' => [new IsBoolean()],
'transactions.*.notes' => 'min:1|max:32768|nullable',
'transactions.*.tags' => 'min:0|max:255',
'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' => 'min:1|max:255|nullable|url',
// SEPA fields:
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
// 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',
// TODO include location and ability to process it.
];
}
/**
* Configure the validator instance.
*/
public function withValidator(Validator $validator): void
{
/** @var User $user */
$user = auth()->user();
/** @var UserGroup $userGroup */
$userGroup = $this->getUserGroup();
$validator->after(
function (Validator $validator) use ($user, $userGroup): void {
// must be valid array.
$this->validateTransactionArray($validator); // does not need group validation.
// must submit at least one transaction.
app('log')->debug('Now going to validateOneTransaction');
$this->validateOneTransaction($validator); // does not need group validation.
app('log')->debug('Now done with validateOneTransaction');
// all journals must have a description
$this->validateDescriptions($validator); // does not need group validation.
// all transaction types must be equal:
$this->validateTransactionTypes($validator); // does not need group validation.
// validate foreign currency info
$this->validateForeignCurrencyInformation($validator); // does not need group validation.
// validate all account info
$this->validateAccountInformation($validator, $user, $userGroup);
// validate source/destination is equal, depending on the transaction journal type.
$this->validateEqualAccounts($validator);
// the group must have a description if > 1 journal.
$this->validateGroupDescription($validator);
}
);
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}
}

View File

@@ -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.
*
@@ -259,7 +138,7 @@ class UpdateRequest extends Request
{
foreach ($this->integerFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->integerFromValue((string) $transaction[$fieldName]);
$current[$fieldName] = $this->integerFromValue((string)$transaction[$fieldName]);
}
}
@@ -274,7 +153,7 @@ class UpdateRequest extends Request
{
foreach ($this->stringFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->clearString((string) $transaction[$fieldName]);
$current[$fieldName] = $this->clearString((string)$transaction[$fieldName]);
}
}
@@ -289,7 +168,7 @@ class UpdateRequest extends Request
{
foreach ($this->textareaFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->clearStringKeepNewlines((string) $transaction[$fieldName]); // keep newlines
$current[$fieldName] = $this->clearStringKeepNewlines((string)$transaction[$fieldName]); // keep newlines
}
}
@@ -305,8 +184,8 @@ class UpdateRequest extends Request
foreach ($this->dateFields as $fieldName) {
app('log')->debug(sprintf('Now at date field %s', $fieldName));
if (array_key_exists($fieldName, $transaction)) {
app('log')->debug(sprintf('New value: "%s"', (string) $transaction[$fieldName]));
$current[$fieldName] = $this->dateFromValue((string) $transaction[$fieldName]);
app('log')->debug(sprintf('New value: "%s"', (string)$transaction[$fieldName]));
$current[$fieldName] = $this->dateFromValue((string)$transaction[$fieldName]);
}
}
@@ -321,7 +200,7 @@ class UpdateRequest extends Request
{
foreach ($this->booleanFields as $fieldName) {
if (array_key_exists($fieldName, $transaction)) {
$current[$fieldName] = $this->convertBoolean((string) $transaction[$fieldName]);
$current[$fieldName] = $this->convertBoolean((string)$transaction[$fieldName]);
}
}
@@ -356,11 +235,132 @@ class UpdateRequest extends Request
$current[$fieldName] = sprintf('%.12f', $value);
}
if (!is_float($value)) {
$current[$fieldName] = (string) $value;
$current[$fieldName] = (string)$value;
}
}
}
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());
}
}
}

View File

@@ -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));

View File

@@ -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);
}
}
}

View File

@@ -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();

View File

@@ -71,7 +71,7 @@ class MigrateRecurrenceMeta extends Command
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool) $configVar->data;
return (bool)$configVar->data;
}
return false;

View File

@@ -104,7 +104,7 @@ class MigrateToGroups extends Command
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
if (null !== $configVar) {
return (bool) $configVar->data;
return (bool)$configVar->data;
}
return false;
@@ -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()');

View File

@@ -30,8 +30,8 @@ namespace FireflyIII\Enums;
*/
enum StringPosition
{
case STARTS;
case ENDS;
case CONTAINS;
case IS;
case STARTS;
case ENDS;
case CONTAINS;
case IS;
}

View File

@@ -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

View File

@@ -128,7 +128,7 @@ class Handler extends ExceptionHandler
$errorCode = 500;
$errorCode = $e instanceof MethodNotAllowedHttpException ? 405 : $errorCode;
$isDebug = (bool) config('app.debug', false);
$isDebug = (bool)config('app.debug', false);
if ($isDebug) {
app('log')->debug(sprintf('Return JSON %s with debug.', get_class($e)));
@@ -185,7 +185,7 @@ class Handler extends ExceptionHandler
*/
public function report(\Throwable $e): void
{
$doMailError = (bool) config('firefly.send_error_message');
$doMailError = (bool)config('firefly.send_error_message');
if ($this->shouldntReportLocal($e) || !$doMailError) {
parent::report($e);
@@ -221,12 +221,22 @@ class Handler extends ExceptionHandler
// create job that will mail.
$ipAddress = request()->ip() ?? '0.0.0.0';
$job = new MailError($userData, (string) config('firefly.site_owner'), $ipAddress, $data);
$job = new MailError($userData, (string)config('firefly.site_owner'), $ipAddress, $data);
dispatch($job);
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.
*/

View File

@@ -48,7 +48,7 @@ final class IntervalException extends \Exception
Periodicity $periodicity,
array $intervals,
int $code = 0,
?\Throwable $previous = null
?\Throwable $previous = null
): self {
$message = sprintf(
'The periodicity %s is unknown. Choose one of available periodicity: %s',

View File

@@ -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);
}
}

View File

@@ -79,7 +79,7 @@ class RecurrenceFactory
$firstDate = $data['recurrence']['first_date'];
}
if (array_key_exists('nr_of_repetitions', $data['recurrence'])) {
$repetitions = (int) $data['recurrence']['nr_of_repetitions'];
$repetitions = (int)$data['recurrence']['nr_of_repetitions'];
}
if (array_key_exists('repeat_until', $data['recurrence'])) {
$repeatUntil = $data['recurrence']['repeat_until'];
@@ -116,7 +116,7 @@ class RecurrenceFactory
$recurrence->save();
if (array_key_exists('notes', $data['recurrence'])) {
$this->updateNote($recurrence, (string) $data['recurrence']['notes']);
$this->updateNote($recurrence, (string)$data['recurrence']['notes']);
}
$this->createRepetitions($recurrence, $data['repetitions'] ?? []);

View File

@@ -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.
}
}

View File

@@ -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.
@@ -203,11 +160,11 @@ class TransactionJournalFactory
$type = $this->typeRepository->findTransactionType(null, $row['type']);
$carbon = $row['date'] ?? today(config('app.timezone'));
$order = $row['order'] ?? 0;
$currency = $this->currencyRepository->findCurrency((int) $row['currency_id'], $row['currency_code']);
$currency = $this->currencyRepository->findCurrency((int)$row['currency_id'], $row['currency_code']);
$foreignCurrency = $this->currencyRepository->findCurrencyNull($row['foreign_currency_id'], $row['foreign_currency_code']);
$bill = $this->billRepository->findBill((int) $row['bill_id'], $row['bill_name']);
$bill = $this->billRepository->findBill((int)$row['bill_id'], $row['bill_name']);
$billId = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null;
$description = (string) $row['description'];
$description = (string)$row['description'];
// Manipulate basic fields
$carbon->setTimezone(config('app.timezone'));
@@ -286,7 +243,7 @@ class TransactionJournalFactory
$transactionFactory->setReconciled($row['reconciled'] ?? false);
try {
$negative = $transactionFactory->createNegative((string) $row['amount'], (string) $row['foreign_amount']);
$negative = $transactionFactory->createNegative((string)$row['amount'], (string)$row['foreign_amount']);
} catch (FireflyException $e) {
app('log')->error(sprintf('Exception creating negative transaction: %s', $e->getMessage()));
$this->forceDeleteOnError(new Collection([$journal]));
@@ -305,7 +262,7 @@ class TransactionJournalFactory
$transactionFactory->setReconciled($row['reconciled'] ?? false);
try {
$transactionFactory->createPositive((string) $row['amount'], (string) $row['foreign_amount']);
$transactionFactory->createPositive((string)$row['amount'], (string)$row['foreign_amount']);
} catch (FireflyException $e) {
app('log')->error(sprintf('Exception creating positive transaction: %s', $e->getMessage()));
$this->forceTrDelete($negative);
@@ -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();
@@ -382,7 +327,7 @@ class TransactionJournalFactory
app('log')->warning(sprintf('Found a duplicate in errorIfDuplicate because hash %s is not unique!', $hash));
$journal = $result->transactionJournal()->withTrashed()->first();
$group = $journal?->transactionGroup()->withTrashed()->first();
$groupId = (int) $group?->id;
$groupId = (int)$group?->id;
throw new DuplicateTransactionException(sprintf('Duplicate of transaction #%d.', $groupId));
}
@@ -400,10 +345,10 @@ class TransactionJournalFactory
// validate source account.
$array = [
'id' => null !== $data['source_id'] ? (int) $data['source_id'] : null,
'name' => null !== $data['source_name'] ? (string) $data['source_name'] : null,
'iban' => null !== $data['source_iban'] ? (string) $data['source_iban'] : null,
'number' => null !== $data['source_number'] ? (string) $data['source_number'] : null,
'id' => null !== $data['source_id'] ? (int)$data['source_id'] : null,
'name' => null !== $data['source_name'] ? (string)$data['source_name'] : null,
'iban' => null !== $data['source_iban'] ? (string)$data['source_iban'] : null,
'number' => null !== $data['source_number'] ? (string)$data['source_number'] : null,
];
$validSource = $this->accountValidator->validateSource($array);
@@ -415,10 +360,10 @@ class TransactionJournalFactory
// validate destination account
$array = [
'id' => null !== $data['destination_id'] ? (int) $data['destination_id'] : null,
'name' => null !== $data['destination_name'] ? (string) $data['destination_name'] : null,
'iban' => null !== $data['destination_iban'] ? (string) $data['destination_iban'] : null,
'number' => null !== $data['destination_number'] ? (string) $data['destination_number'] : null,
'id' => null !== $data['destination_id'] ? (int)$data['destination_id'] : null,
'name' => null !== $data['destination_name'] ? (string)$data['destination_name'] : null,
'iban' => null !== $data['destination_iban'] ? (string)$data['destination_iban'] : null,
'number' => null !== $data['destination_number'] ? (string)$data['destination_number'] : null,
];
$validDestination = $this->accountValidator->validateDestination($array);
@@ -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__));
@@ -550,7 +510,7 @@ class TransactionJournalFactory
{
app('log')->debug('Will now store piggy event.');
$piggyBank = $this->piggyRepository->findPiggyBank((int) $data['piggy_bank_id'], $data['piggy_bank_name']);
$piggyBank = $this->piggyRepository->findPiggyBank((int)$data['piggy_bank_id'], $data['piggy_bank_name']);
if (null !== $piggyBank) {
$this->piggyEventFactory->create($journal, $piggyBank);
@@ -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.');
}
}
}

View File

@@ -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';
}
}

View File

@@ -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;
}
}

View File

@@ -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.
*/

View File

@@ -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);
}
}

View File

@@ -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];
Notification::send($user, new RuleActionFailed($params));
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];
Notification::send($user, new RuleActionFailed($params));
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()));
}
}
}

View File

@@ -220,7 +220,7 @@ class UserEventHandler
public function sendAdminRegistrationNotification(RegisteredUser $event): void
{
$sendMail = (bool) app('fireflyconfig')->get('notification_admin_new_reg', true)->data;
$sendMail = (bool)app('fireflyconfig')->get('notification_admin_new_reg', true)->data;
if ($sendMail) {
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
@@ -285,7 +285,7 @@ class UserEventHandler
$oldEmail = $event->oldEmail;
$user = $event->user;
$token = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid');
$hashed = hash('sha256', sprintf('%s%s', (string) config('app.key'), $oldEmail));
$hashed = hash('sha256', sprintf('%s%s', (string)config('app.key'), $oldEmail));
$url = route('profile.undo-email-change', [$token->data, $hashed]);
try {
@@ -347,7 +347,7 @@ class UserEventHandler
*/
public function sendRegistrationMail(RegisteredUser $event): void
{
$sendMail = (bool) app('fireflyconfig')->get('notification_user_new_reg', true)->data;
$sendMail = (bool)app('fireflyconfig')->get('notification_user_new_reg', true)->data;
if ($sendMail) {
try {
Notification::send($event->user, new UserRegistrationNotification());

View File

@@ -40,7 +40,7 @@ class BudgetObserver
$budgetLimits = $budget->budgetlimits()->get();
/** @var BudgetLimit $budgetLimit */
foreach($budgetLimits as $budgetLimit) {
foreach ($budgetLimits as $budgetLimit) {
// this loop exists so several events are fired.
$budgetLimit->delete();
}

View File

@@ -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');
}
)
;
}
}
}

View File

@@ -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').

View File

@@ -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');
}
}
}

View File

@@ -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;
}
}

View File

@@ -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.
*/

Some files were not shown because too many files have changed in this diff Show More