mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 02:36:28 +00:00 
			
		
		
		
	Compare commits
	
		
			266 Commits
		
	
	
		
			develop-20
			...
			develop-20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 0c824e21c8 | ||
|  | fab1c68569 | ||
|  | c1534657f2 | ||
|  | 39841de680 | ||
|  | 5ec54de29e | ||
|  | 397e37f344 | ||
|  | b6f84c2b99 | ||
|  | 843f86fc66 | ||
|  | 0e8e364074 | ||
|  | bbccbef578 | ||
|  | ee11a8e3a0 | ||
|  | e8618047bd | ||
|  | f104b76f73 | ||
|  | cb701d8506 | ||
|  | 70a334c56e | ||
|  | e6b2db1e29 | ||
|  | e8dffa0052 | ||
|  | c4f0512f39 | ||
|  | 3268019d0c | ||
|  | a0ef6a1fc8 | ||
|  | 99d0098b20 | ||
|  | a7a54c042c | ||
|  | c44e48a793 | ||
|  | 53b501ca73 | ||
|  | 322f70bcca | ||
|  | 35559c077b | ||
|  | 590ffe7c76 | ||
|  | 8a2d8f148e | ||
|  | 4f0e15e07d | ||
|  | 7463861e0c | ||
|  | 1e70fa28be | ||
|  | 26c6ca470b | ||
|  | 5e54034e0e | ||
|  | 25873ef734 | ||
|  | 1092b04b22 | ||
|  | 01ce74dd72 | ||
|  | 41430d8386 | ||
|  | 01eb19169c | ||
|  | cfaa7d7c68 | ||
|  | 14d3312a10 | ||
|  | 87be478dd8 | ||
|  | 0b6877a20e | ||
|  | 7186f0ef60 | ||
|  | 538933691e | ||
|  | 46c49ddbd8 | ||
|  | bcfb134b6e | ||
|  | 57981f1cf9 | ||
|  | 0310186fb7 | ||
|  | 4dcb38290e | ||
|  | 2f5c37048b | ||
|  | 370c8b16ae | ||
|  | af0555592a | ||
|  | 9c07ddaed6 | ||
|  | bb7355a566 | ||
|  | 1d48347f8c | ||
|  | 060b76ca9c | ||
|  | 2b2b9b6f7a | ||
|  | f3dd05a0c0 | ||
|  | 47a91aa273 | ||
|  | 41bc236603 | ||
|  | 65349451ea | ||
|  | e77b6a55a4 | ||
|  | 2379bcff11 | ||
|  | 7133156fa1 | ||
|  | a59176689d | ||
|  | bc2d8f3dfb | ||
|  | ddf89a9d5a | ||
|  | 7daaba17f6 | ||
|  | 9cb5b1384f | ||
|  | 7d13263482 | ||
|  | d9ff252915 | ||
|  | 51ba550251 | ||
|  | fd21c467ad | ||
|  | 9aa90650b4 | ||
|  | d892257e8b | ||
|  | db0dbcfcf1 | ||
|  | f591996f04 | ||
|  | b08d385586 | ||
|  | 20ef22f67e | ||
|  | c888baf542 | ||
|  | 8b0af3f666 | ||
|  | 7043e1e7c0 | ||
|  | c5854eba23 | ||
|  | ddf1a8cebb | ||
|  | 7dcaf167e9 | ||
|  | b359d51d3a | ||
|  | 3913fa5086 | ||
|  | ab2772abe0 | ||
|  | bc7875b17b | ||
|  | 4938fa9990 | ||
|  | 84df2c80ee | ||
|  | dc17060754 | ||
|  | e2fa81dddc | ||
|  | 182dfc95fe | ||
|  | c8979b6c33 | ||
|  | ab872e8912 | ||
|  | d36b94fabf | ||
|  | e3d4ceaecb | ||
|  | e3a6e5b788 | ||
|  | 57235c0e00 | ||
|  | 2298c3ddaf | ||
|  | 7224f1be6f | ||
|  | 1bd3019c16 | ||
|  | f0fa21dead | ||
|  | 845eaed8d7 | ||
|  | b3649cd4d0 | ||
|  | 55f14c587b | ||
|  | 441a8a8408 | ||
|  | 060c9648f1 | ||
|  | 7680c8733f | ||
|  | 5a0af5c93b | ||
|  | f4b066add1 | ||
|  | 9ecb414b02 | ||
|  | ad4f908c24 | ||
|  | 025f739442 | ||
|  | 6df7354c48 | ||
|  | 3f77c845ca | ||
|  | d4771f7a5c | ||
|  | ec4e2bfa4f | ||
|  | dfdbfae4b5 | ||
|  | 349d38b956 | ||
|  | 2267aa3ac4 | ||
|  | 2323aa454e | ||
|  | 8b3317b665 | ||
|  | 15f893c343 | ||
|  | 309b3e765e | ||
|  | d3fad06e00 | ||
|  | 834f24c99c | ||
|  | 35291e1298 | ||
|  | ac4e9dcbc5 | ||
|  | d57806f2ba | ||
|  | 3b005c317d | ||
|  | e91903fed2 | ||
|  | fee2002b0f | ||
|  | f12e502eb8 | ||
|  | 24e62b1cee | ||
|  | f559ec73e0 | ||
|  | 530b501fcf | ||
|  | d5ea78025e | ||
|  | 3413b9b5b5 | ||
|  | 0b45c1aa76 | ||
|  | 5718d1690a | ||
|  | 67b16cc070 | ||
|  | 5746ac3247 | ||
|  | 8a2c520b11 | ||
|  | f46c14df8c | ||
|  | 009fbba491 | ||
|  | 53d84347c2 | ||
|  | 1961487055 | ||
|  | c9ce5df74b | ||
|  | 1371b6773e | ||
|  | b9f1baf150 | ||
|  | 66b322e844 | ||
|  | 487b65b669 | ||
|  | 9078781d61 | ||
|  | 1ec830521a | ||
|  | c4bf2aae7d | ||
|  | 69ca88d9f8 | ||
|  | b38b7b2534 | ||
|  | f19bfc3b4b | ||
|  | d22f9c09d7 | ||
|  | fc2da9eb42 | ||
|  | f2c9e20aef | ||
|  | 16b8ca2746 | ||
|  | 46ea074821 | ||
|  | d2c89781e2 | ||
|  | e54d711891 | ||
|  | 84d3ad4764 | ||
|  | b908951a2d | ||
|  | 8b87deea58 | ||
|  | 0d7325b3dc | ||
|  | a3fd99a498 | ||
|  | 0ff405d1e0 | ||
|  | 46a60af966 | ||
|  | 591c9e3b39 | ||
|  | c30461b20b | ||
|  | 2c3f86d9bc | ||
|  | 34349e4475 | ||
|  | 6acd5be5dc | ||
|  | 55a2b4e789 | ||
|  | f41397eb43 | ||
|  | 41fc1e8f82 | ||
|  | bee219ebf7 | ||
|  | 438f602961 | ||
|  | 429e72e681 | ||
|  | 7a134781f2 | ||
|  | b572c1dcd3 | ||
|  | 95593f847b | ||
|  | b82fcbd97b | ||
|  | daddee7806 | ||
|  | 930a08ec90 | ||
|  | fd2edf3b23 | ||
|  | 0597255c08 | ||
|  | 955ab38a85 | ||
|  | 1311a0db8b | ||
|  | 0ce9ee6a6c | ||
|  | 3a339382d4 | ||
|  | a5b15bbc16 | ||
|  | fbf89fd514 | ||
|  | b3223feba2 | ||
|  | 88a9bc379e | ||
|  | b442b91b7c | ||
|  | 9fadbbe087 | ||
|  | 1ef7239276 | ||
|  | ea573e9434 | ||
|  | 34fa24e4a8 | ||
|  | a1be4a4d8a | ||
|  | b8e8af1e2a | ||
|  | c13a3fb30c | ||
|  | cb8fa4e1f4 | ||
|  | bf7f4f9887 | ||
|  | af48548e81 | ||
|  | 90d58ec8fa | ||
|  | e92dd7f464 | ||
|  | 3bdf9eeed2 | ||
|  | 558ac7b0da | ||
|  | 9d0488ffbc | ||
|  | d7fa8b283e | ||
|  | a0097bd613 | ||
|  | ffc2156e5f | ||
|  | e0a89bb5fe | ||
|  | 647179cd3c | ||
|  | 5106ccdbd7 | ||
|  | 7103098fe7 | ||
|  | f8072f0bfc | ||
|  | 96ac3a95c8 | ||
|  | cd713dc40f | ||
|  | d9fba39d80 | ||
|  | 2564470197 | ||
|  | 9222c82af0 | ||
|  | 243f283bfd | ||
|  | 5b60aaecc0 | ||
|  | 20a4caec60 | ||
|  | 99cc096b71 | ||
|  | 5626d1c56d | ||
|  | 68c9c4ec3c | ||
|  | f9d4a43e05 | ||
|  | 92e7f344e0 | ||
|  | 89ce2838d5 | ||
|  | 356b217692 | ||
|  | 950e39b753 | ||
|  | 8f14979717 | ||
|  | aa2afd162e | ||
|  | fe33352ec1 | ||
|  | 65c5249815 | ||
|  | b1afaea1aa | ||
|  | 997dc3814b | ||
|  | b37b5b86d4 | ||
|  | b13a4e1016 | ||
|  | 7897ebc4d5 | ||
|  | ac17b82d85 | ||
|  | 1b1712d998 | ||
|  | a2c0d9f7d0 | ||
|  | 5b68b25c85 | ||
|  | d3a215b575 | ||
|  | 5c352a0d3e | ||
|  | fded058ea6 | ||
|  | 99f041b114 | ||
|  | 283b594995 | ||
|  | 723aa65e7a | ||
|  | 64d315ad51 | ||
|  | d0844356cb | ||
|  | ba8d65835a | ||
|  | fa3343f437 | ||
|  | c5b8a951d2 | ||
|  | 20b1fc05cb | 
| @@ -22,6 +22,9 @@ | ||||
|  | ||||
| SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" | ||||
|  | ||||
| echo "Running PHP CS Fixer" | ||||
| $SCRIPT_DIR/phpcs.sh | ||||
| echo "Running PHPStan" | ||||
| $SCRIPT_DIR/phpstan.sh | ||||
| echo "Running PHPMD" | ||||
| $SCRIPT_DIR/phpmd.sh | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
							
								
								
									
										189
									
								
								.ci/php-cs-fixer/composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										189
									
								
								.ci/php-cs-fixer/composer.lock
									
									
									
										generated
									
									
									
								
							| @@ -8,16 +8,16 @@ | ||||
|     "packages": [ | ||||
|         { | ||||
|             "name": "composer/pcre", | ||||
|             "version": "3.1.1", | ||||
|             "version": "3.1.3", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/composer/pcre.git", | ||||
|                 "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" | ||||
|                 "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", | ||||
|                 "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", | ||||
|                 "url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", | ||||
|                 "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -59,7 +59,7 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/composer/pcre/issues", | ||||
|                 "source": "https://github.com/composer/pcre/tree/3.1.1" | ||||
|                 "source": "https://github.com/composer/pcre/tree/3.1.3" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -75,7 +75,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2023-10-11T07:11:09+00:00" | ||||
|             "time": "2024-03-19T10:26:25+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "composer/semver", | ||||
| @@ -160,16 +160,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "composer/xdebug-handler", | ||||
|             "version": "3.0.3", | ||||
|             "version": "3.0.4", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/composer/xdebug-handler.git", | ||||
|                 "reference": "ced299686f41dce890debac69273b47ffe98a40c" | ||||
|                 "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", | ||||
|                 "reference": "ced299686f41dce890debac69273b47ffe98a40c", | ||||
|                 "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255", | ||||
|                 "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -180,7 +180,7 @@ | ||||
|             "require-dev": { | ||||
|                 "phpstan/phpstan": "^1.0", | ||||
|                 "phpstan/phpstan-strict-rules": "^1.1", | ||||
|                 "symfony/phpunit-bridge": "^6.0" | ||||
|                 "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" | ||||
|             }, | ||||
|             "type": "library", | ||||
|             "autoload": { | ||||
| @@ -204,9 +204,9 @@ | ||||
|                 "performance" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "irc": "irc://irc.freenode.org/composer", | ||||
|                 "irc": "ircs://irc.libera.chat:6697/composer", | ||||
|                 "issues": "https://github.com/composer/xdebug-handler/issues", | ||||
|                 "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" | ||||
|                 "source": "https://github.com/composer/xdebug-handler/tree/3.0.4" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -222,20 +222,20 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2022-02-25T21:32:43+00:00" | ||||
|             "time": "2024-03-26T18:29:49+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "friendsofphp/php-cs-fixer", | ||||
|             "version": "v3.49.0", | ||||
|             "version": "v3.52.1", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", | ||||
|                 "reference": "8742f7aa6f72a399688b65e4f58992c2d4681fc2" | ||||
|                 "reference": "6e77207f0d851862ceeb6da63e6e22c01b1587bc" | ||||
|             }, | ||||
|             "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/6e77207f0d851862ceeb6da63e6e22c01b1587bc", | ||||
|                 "reference": "6e77207f0d851862ceeb6da63e6e22c01b1587bc", | ||||
|                 "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.52.1" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -313,7 +314,7 @@ | ||||
|                     "type": "github" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-02-02T00:41:40+00:00" | ||||
|             "time": "2024-03-19T21:02:43+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": [], | ||||
|   | ||||
							
								
								
									
										78
									
								
								.ci/phpmd/composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										78
									
								
								.ci/phpmd/composer.lock
									
									
									
										generated
									
									
									
								
							| @@ -9,16 +9,16 @@ | ||||
|     "packages-dev": [ | ||||
|         { | ||||
|             "name": "composer/pcre", | ||||
|             "version": "3.1.1", | ||||
|             "version": "3.1.2", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/composer/pcre.git", | ||||
|                 "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" | ||||
|                 "reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", | ||||
|                 "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", | ||||
|                 "url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace", | ||||
|                 "reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -60,7 +60,7 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/composer/pcre/issues", | ||||
|                 "source": "https://github.com/composer/pcre/tree/3.1.1" | ||||
|                 "source": "https://github.com/composer/pcre/tree/3.1.2" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -76,7 +76,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2023-10-11T07:11:09+00:00" | ||||
|             "time": "2024-03-07T15:38:35+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "composer/xdebug-handler", | ||||
| @@ -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": [], | ||||
|   | ||||
							
								
								
									
										24
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								.env.example
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| # You can leave this on "local". If you change it to production most console commands will ask for extra confirmation. | ||||
| # Never set it to "testing". | ||||
| APP_ENV=local | ||||
| APP_ENV=production | ||||
|  | ||||
| # Set to true if you want to see debug information in error screens. | ||||
| APP_DEBUG=false | ||||
| @@ -111,7 +111,10 @@ PGSQL_SSL_CERT=null | ||||
| PGSQL_SSL_KEY=null | ||||
| PGSQL_SSL_CRL_FILE=null | ||||
|  | ||||
| # more PostgreSQL settings | ||||
| # For postgresql 15 and up, setting this to public will no longer work as expected, becasuse the | ||||
| # 'public' schema is without grants. This can be worked around by having a super user grant those | ||||
| # necessary privileges, but in security conscious setups that's not viable. | ||||
| # You will need to set this to the schema you want to use. | ||||
| PGSQL_SCHEMA=public | ||||
|  | ||||
| # If you're looking for performance improvements, you could install memcached or redis | ||||
| @@ -184,6 +187,11 @@ SEND_REPORT_JOURNALS=true | ||||
| # Since this involves an external service, it's optional and disabled by default. | ||||
| ENABLE_EXTERNAL_MAP=false | ||||
|  | ||||
| # | ||||
| # Enable or disable exchange rate conversion. This function isn't used yet by Firefly III | ||||
| # | ||||
| ENABLE_EXCHANGE_RATES=false | ||||
|  | ||||
| # Set this value to true if you want Firefly III to download currency exchange rates | ||||
| # from the internet. These rates are hosted by the creator of Firefly III inside | ||||
| # an Azure Storage Container. | ||||
| @@ -332,15 +340,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 | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| # code owners for this Firefly III related repository | ||||
| * @JC5 @SDx3 | ||||
							
								
								
									
										4
									
								
								.github/funding.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/funding.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,6 @@ | ||||
| # These are supported funding model platforms | ||||
| # Firefly III sponsor options | ||||
|  | ||||
| github: jc5 | ||||
| patreon: JC5 | ||||
| ko_fi: jamesc5 | ||||
| liberapay: JC5 | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							| @@ -7,7 +7,7 @@ permissions: | ||||
|  | ||||
| on: | ||||
|   schedule: | ||||
|     - cron: '0 0 * * *' | ||||
|     - cron: '0 1 * * *' | ||||
|   workflow_dispatch: | ||||
| jobs: | ||||
|   prune: | ||||
|   | ||||
							
								
								
									
										20
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,17 +3,27 @@ name: 'Issues - Lock old issues' | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|   schedule: | ||||
|     - cron: '0 0 * * *' | ||||
|     - cron: '0 2 * * *' | ||||
|  | ||||
| concurrency: | ||||
|   group: lock-threads | ||||
|  | ||||
| permissions: | ||||
|   issues: write | ||||
|   pull-requests: write | ||||
|   discussions: write | ||||
|  | ||||
| jobs: | ||||
|   lock: | ||||
|     permissions: | ||||
|       issues: write | ||||
|       pull-requests: write | ||||
|       discussions: write | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: JC5/lock-threads@main | ||||
|       - uses: dessant/lock-threads@v5 | ||||
|         with: | ||||
|           github-token: ${{ github.token }} | ||||
|           issue-inactive-days: 7 | ||||
|           pr-inactive-days: 7 | ||||
|           issue-inactive-days: 21 | ||||
|           pr-inactive-days: 21 | ||||
|           discussion-inactive-days: 21 | ||||
|           log-output: true | ||||
|   | ||||
							
								
								
									
										117
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										117
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -8,7 +8,7 @@ on: | ||||
|         required: true | ||||
|         default: 'develop' | ||||
|   schedule: | ||||
|     - cron:  '15 0 * * MON,THU' | ||||
|     - cron:  '0 3 * * MON,THU' | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
| @@ -51,7 +51,7 @@ jobs: | ||||
|           CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }} | ||||
|       - name: Cleanup translations | ||||
|         id: cleanup-transactions | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v36 | ||||
|         with: | ||||
|           action: 'ff3:crowdin-warning' | ||||
|           output: '' | ||||
| @@ -60,7 +60,7 @@ jobs: | ||||
|           GH_TOKEN: '' | ||||
|       - name: Cleanup changelog | ||||
|         id: cleanup-changelog | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v36 | ||||
|         with: | ||||
|           action: 'ff3:changelog' | ||||
|           output: '' | ||||
| @@ -69,7 +69,7 @@ jobs: | ||||
|           GH_TOKEN: ${{ secrets.CHANGELOG_TOKEN }} | ||||
|       - name: Extract changelog | ||||
|         id: extract-changelog | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v36 | ||||
|         with: | ||||
|           action: 'ff3:extract-changelog' | ||||
|           output: 'output' | ||||
| @@ -78,7 +78,7 @@ jobs: | ||||
|           GH_TOKEN: "" | ||||
|       - name: Replace version | ||||
|         id: replace-version | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v36 | ||||
|         with: | ||||
|           action: 'ff3:version' | ||||
|           output: '' | ||||
| @@ -88,7 +88,7 @@ jobs: | ||||
|           FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }} | ||||
|       - name: Generate JSON v1 | ||||
|         id: json-v1 | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v36 | ||||
|         with: | ||||
|           action: 'ff3:json-translations v1' | ||||
|           output: '' | ||||
| @@ -97,7 +97,7 @@ jobs: | ||||
|           GH_TOKEN: '' | ||||
|       - name: Generate JSON v2 | ||||
|         id: json-v2 | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v36 | ||||
|         with: | ||||
|           action: 'ff3:json-translations v2' | ||||
|           output: '' | ||||
| @@ -106,7 +106,7 @@ jobs: | ||||
|           GH_TOKEN: '' | ||||
|       - name: Code cleanup | ||||
|         id: code-cleanup | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v36 | ||||
|         with: | ||||
|           action: 'ff3:code' | ||||
|           output: '' | ||||
| @@ -115,11 +115,12 @@ jobs: | ||||
|           GH_TOKEN: '' | ||||
|       - name: Build new JS | ||||
|         run: | | ||||
|           npm upgrade | ||||
|           npm install | ||||
|           npm update | ||||
|           npm run build | ||||
|       - name: Build old JS | ||||
|         id: old-js | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v36 | ||||
|         with: | ||||
|           action: 'ff3:old-js' | ||||
|           output: '' | ||||
| @@ -140,10 +141,39 @@ jobs: | ||||
|           git config user.email 41898282+github-actions[bot]@users.noreply.github.com | ||||
|           git config advice.addIgnoredFile false | ||||
|  | ||||
|           # update composer (again) | ||||
|           composer validate --strict | ||||
|           composer update --no-dev --no-scripts --no-plugins | ||||
|           composer dump-autoload | ||||
|  | ||||
|           releaseName=$version | ||||
|           originalName=$version | ||||
|           tarName=FireflyIII-$version.tar.gz | ||||
|  | ||||
|           if [[ "develop" == "$version" ]]; then | ||||
|             [[ -z $(git status --untracked-files=normal --porcelain) ]] && echo "this branch is clean, no need to push..." && exit 0; | ||||
|             releaseName=$version-$(date +'%Y%m%d') | ||||
|             originalName=$releaseName | ||||
|             zipName=FireflyIII-develop.zip | ||||
|             tarName=FireflyIII-develop.tar.gz | ||||
|           fi | ||||
|  | ||||
|           # in both cases, if the release or tag already exists, add ".1" until it no longer exists. | ||||
|           tagFound=true | ||||
|           tagCount=1 | ||||
|           while [ "$tagFound" = true ] | ||||
|           do | ||||
|             if [ $(git tag -l "$releaseName") ]; then | ||||
|               echo "Tag $releaseName exists already." | ||||
|               releaseName="$originalName"."$tagCount" | ||||
|               echo "Tag for release is now $releaseName" | ||||
|             else | ||||
|              echo "Tag $releaseName does not exist, can continue" | ||||
|              tagFound=false | ||||
|             fi | ||||
|           done | ||||
|           echo "Will use tag and release name $releaseName." | ||||
|  | ||||
|           git add -A | ||||
|           if test -f "output.txt"; then | ||||
|             git reset output.txt | ||||
| @@ -151,19 +181,64 @@ jobs: | ||||
|           git commit -m "Auto commit for release '$version' on $(date +'%Y-%m-%d')" || true | ||||
|           git push | ||||
|  | ||||
|           # zip and tar everything | ||||
|           zip -rq $zipName . -x "*.git*" "*.ci*" "*.github*" "*node_modules*" "*output.txt*" | ||||
|           touch $tarName | ||||
|           tar --exclude=$tarName --exclude='./.git' --exclude='./.ci' --exclude='./.github' --exclude='./node_modules' --exclude='./output.txt' -czf $tarName . | ||||
|  | ||||
|           # add sha256 sum | ||||
|           sha256sum -b $zipName > $zipName.sha256 | ||||
|           sha256sum -b $tarName > $tarName.sha256 | ||||
|  | ||||
|           if [[ "develop" == "$version" ]]; then | ||||
|             echo "Create nightly release." | ||||
|             git tag -a $version-$(date +'%Y%m%d') -m "Nightly development release '$version' on $(date +'%Y-%m-%d')" | ||||
|             git push origin $version-$(date +'%Y%m%d') | ||||
|             gh release create $version-$(date +'%Y%m%d') -p --verify-tag \ | ||||
|               -t "Development release for $(date +'%Y-%m-%d')" \ | ||||
|               -n "Bi-weekly development release of Firefly III with the latest fixes, translations and features. This release was created on **$(date +'%Y-%m-%d')** and may contain bugs. Use at your own risk. Docker users can find this release under the \`develop\` tag." | ||||
|           else | ||||
|             echo "Create default release." | ||||
|             git tag -a $version -m "Here be changelog" | ||||
|             git push origin $version | ||||
|             gh release create $version -F output.txt -t "$version" --verify-tag | ||||
|             # add text to output.txt (instructions) | ||||
|             rm output.txt | ||||
|             echo "Bi-weekly development release of Firefly III with the latest fixes, translations and features. Docker users can find this release under the \`develop\` tag." >> output.txt | ||||
|             echo "" >> output.txt | ||||
|             echo "This release was created on **$(date +'%Y-%m-%d')** and may contain unexpected bugs. Data loss is rare but is not impossible." >> output.txt | ||||
|             echo "" >> output.txt | ||||
|             echo "* Please read the installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt | ||||
|             echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt | ||||
|             echo "" >> output.txt | ||||
|             echo ":warning: Please be careful with this pre-release, as is may not work as expected." >> output.txt | ||||
|  | ||||
|             # create the release: | ||||
|             echo "Create nightly release." | ||||
|             git tag -a $releaseName -m "Nightly development release '$version' on $(date +'%Y-%m-%d')" | ||||
|             git push origin $releaseName | ||||
|             gh release create $releaseName -p --verify-tag \ | ||||
|               -t "Development release for $(date +'%Y-%m-%d')" \ | ||||
|               -F output.txt | ||||
|  | ||||
|             # add zip file to release. | ||||
|             gh release upload $releaseName $zipName | ||||
|             gh release upload $releaseName $tarName | ||||
|  | ||||
|             # add sha256 sum to release | ||||
|             gh release upload $releaseName $zipName.sha256 | ||||
|             gh release upload $releaseName $tarName.sha256 | ||||
|  | ||||
|             # rm output.txt again | ||||
|             rm output.txt | ||||
|           else | ||||
|             # add text to output.txt (more instructions) | ||||
|             echo '' >> output.txt | ||||
|             echo '### Instructions' >> output.txt | ||||
|             echo '' >> output.txt | ||||
|             echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt | ||||
|             echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt | ||||
|  | ||||
|             echo "Create default release." | ||||
|             git tag -a $releaseName -m "Here be changelog" | ||||
|             git push origin $releaseName | ||||
|             gh release create $releaseName -F output.txt -t "$releaseName" --verify-tag | ||||
|             # add zip file to release. | ||||
|             gh release upload $releaseName $zipName | ||||
|             # add sha256 sum to release | ||||
|             gh release upload $releaseName $zipName.sha256 | ||||
|             rm output.txt | ||||
|             rm $zipName | ||||
|             rm $zipName.sha256 | ||||
|             git checkout develop | ||||
|             git merge main | ||||
|             git push | ||||
|   | ||||
							
								
								
									
										9
									
								
								.github/workflows/sonarcloud.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/sonarcloud.yml
									
									
									
									
										vendored
									
									
								
							| @@ -45,15 +45,6 @@ jobs: | ||||
|       - name: Install Composer dependencies | ||||
|         run: composer install --prefer-dist --no-interaction --no-progress --no-scripts | ||||
|  | ||||
|       - name: PHPStan | ||||
|         run: .ci/phpstan.sh | ||||
|  | ||||
|       - name: PHPMD | ||||
|         run: .ci/phpmd.sh | ||||
|  | ||||
|       - name: PHP CS Fixer | ||||
|         run: .ci/phpcs.sh | ||||
|  | ||||
|       - name: "Create database file" | ||||
|         run: touch storage/database/database.sqlite | ||||
|  | ||||
|   | ||||
							
								
								
									
										10
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| name: "Issues - Mark and close stale issues" | ||||
| on: | ||||
|   schedule: | ||||
|     - cron: "30 1 * * *" | ||||
|     - cron: "0 4 * * *" | ||||
|   workflow_dispatch: | ||||
|  | ||||
| permissions: | ||||
| @@ -18,16 +18,16 @@ jobs: | ||||
|         with: | ||||
|           repo-token: ${{ secrets.GITHUB_TOKEN }} | ||||
|           stale-issue-message: > | ||||
|             Hi there!  | ||||
|              | ||||
|             Hi there! | ||||
|  | ||||
|             This is an automatic reply. `Share and enjoy` | ||||
|  | ||||
|             This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. | ||||
|  | ||||
|             Thank you for your contributions. | ||||
|           stale-pr-message: > | ||||
|             Hi there!  | ||||
|              | ||||
|             Hi there! | ||||
|  | ||||
|             This is an automatic reply. `Share and enjoy` | ||||
|  | ||||
|             This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. | ||||
|   | ||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -7,3 +7,6 @@ yarn-error.log | ||||
| .env | ||||
| /.ci/php-cs-fixer/vendor | ||||
| coverage.xml | ||||
|  | ||||
| # ignore generated files. | ||||
| public/build | ||||
|   | ||||
| @@ -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; | ||||
|             } | ||||
|   | ||||
| @@ -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, | ||||
|                 ]; | ||||
|   | ||||
| @@ -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, | ||||
|                 ]; | ||||
|             } | ||||
|   | ||||
| @@ -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, | ||||
|                 ]; | ||||
|             } | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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, | ||||
|             ]; | ||||
|   | ||||
| @@ -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, | ||||
|             ]; | ||||
|         } | ||||
|   | ||||
| @@ -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, | ||||
|             ]; | ||||
|   | ||||
| @@ -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, | ||||
|             ]; | ||||
|   | ||||
| @@ -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, | ||||
|             ]; | ||||
|   | ||||
| @@ -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, | ||||
|             ]; | ||||
|   | ||||
| @@ -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), | ||||
|             ]; | ||||
|   | ||||
| @@ -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, | ||||
|             ]; | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -68,7 +68,7 @@ class DestroyController extends Controller | ||||
|         $allExceptAssets = [AccountType::BENEFICIARY, AccountType::CASH, AccountType::CREDITCARD, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::IMPORT, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, AccountType::RECONCILIATION, AccountType::REVENUE]; | ||||
|         $all             = [AccountType::ASSET, AccountType::BENEFICIARY, AccountType::CASH, AccountType::CREDITCARD, AccountType::DEBT, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::IMPORT, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::RECONCILIATION]; | ||||
|         $liabilities     = [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; | ||||
|         $transactions    = [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::RECONCILIATION, TransactionType::OPENING_BALANCE]; | ||||
|         $transactions    = [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER, TransactionType::RECONCILIATION]; | ||||
| 
 | ||||
|         match ($objects) { | ||||
|             'budgets'                => $this->destroyBudgets(), | ||||
|   | ||||
| @@ -68,6 +68,32 @@ class ExportController extends Controller | ||||
|         return $this->returnExport('accounts'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function returnExport(string $key): LaravelResponse | ||||
|     { | ||||
|         $date     = date('Y-m-d-H-i-s'); | ||||
|         $fileName = sprintf('%s-export-%s.csv', $date, $key); | ||||
|         $data     = $this->exporter->export(); | ||||
| 
 | ||||
|         /** @var LaravelResponse $response */ | ||||
|         $response = response($data[$key]); | ||||
|         $response | ||||
|             ->header('Content-Description', 'File Transfer') | ||||
|             ->header('Content-Type', 'application/octet-stream') | ||||
|             ->header('Content-Disposition', 'attachment; filename='.$fileName) | ||||
|             ->header('Content-Transfer-Encoding', 'binary') | ||||
|             ->header('Connection', 'Keep-Alive') | ||||
|             ->header('Expires', '0') | ||||
|             ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') | ||||
|             ->header('Pragma', 'public') | ||||
|             ->header('Content-Length', (string)strlen($data[$key])) | ||||
|         ; | ||||
| 
 | ||||
|         return $response; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This endpoint is documented at: | ||||
|      * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/data/exportBills
 | ||||
| @@ -189,30 +215,4 @@ class ExportController extends Controller | ||||
| 
 | ||||
|         return $this->returnExport('transactions'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function returnExport(string $key): LaravelResponse | ||||
|     { | ||||
|         $date     = date('Y-m-d-H-i-s'); | ||||
|         $fileName = sprintf('%s-export-%s.csv', $date, $key); | ||||
|         $data     = $this->exporter->export(); | ||||
| 
 | ||||
|         /** @var LaravelResponse $response */ | ||||
|         $response = response($data[$key]); | ||||
|         $response | ||||
|             ->header('Content-Description', 'File Transfer') | ||||
|             ->header('Content-Type', 'application/octet-stream') | ||||
|             ->header('Content-Disposition', 'attachment; filename='.$fileName) | ||||
|             ->header('Content-Transfer-Encoding', 'binary') | ||||
|             ->header('Connection', 'Keep-Alive') | ||||
|             ->header('Expires', '0') | ||||
|             ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') | ||||
|             ->header('Pragma', 'public') | ||||
|             ->header('Content-Length', (string) strlen($data[$key])) | ||||
|         ; | ||||
| 
 | ||||
|         return $response; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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.
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
							
								
								
									
										47
									
								
								app/Api/V1/Controllers/Models/Rule/ExpressionController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								app/Api/V1/Controllers/Models/Rule/ExpressionController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| <?php | ||||
| /* | ||||
|  * ExpressionController.php | ||||
|  * Copyright (c) 2024 Michael Thomas | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Api\V1\Controllers\Models\Rule; | ||||
| 
 | ||||
| use FireflyIII\Api\V1\Controllers\Controller; | ||||
| use FireflyIII\Api\V1\Requests\Models\Rule\ValidateExpressionRequest; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| 
 | ||||
| /** | ||||
|  * Class ExpressionController | ||||
|  */ | ||||
| class ExpressionController extends Controller | ||||
| { | ||||
|     /** | ||||
|      * This endpoint is documented at: | ||||
|      * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/rules/validateExpression
 | ||||
|      * | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function validateExpression(ValidateExpressionRequest $request): JsonResponse | ||||
|     { | ||||
|         return response()->json([ | ||||
|             'valid' => true, | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
|             $days            = (int)$today->diffInDays($end, true) + 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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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.'); | ||||
|   | ||||
| @@ -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.'); | ||||
|   | ||||
| @@ -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.'); | ||||
|   | ||||
| @@ -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.'); | ||||
|   | ||||
| @@ -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.'); | ||||
|   | ||||
| @@ -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.'); | ||||
|   | ||||
| @@ -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.'); | ||||
|   | ||||
| @@ -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')); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -46,7 +46,7 @@ class DateRequest extends FormRequest | ||||
|     { | ||||
|         $start = $this->getCarbonDate('start'); | ||||
|         $end   = $this->getCarbonDate('end'); | ||||
|         if($start->diffInYears($end) > 5) { | ||||
|         if ($start->diffInYears($end, true) > 5) { | ||||
|             throw new FireflyException('Date range out of range.'); | ||||
|         } | ||||
| 
 | ||||
|   | ||||
| @@ -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', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,6 +24,7 @@ declare(strict_types=1); | ||||
| namespace FireflyIII\Api\V1\Requests\Models\Rule; | ||||
| 
 | ||||
| use FireflyIII\Rules\IsBoolean; | ||||
| use FireflyIII\Rules\IsValidActionExpression; | ||||
| use FireflyIII\Support\Request\ChecksLogin; | ||||
| use FireflyIII\Support\Request\ConvertsDataTypes; | ||||
| use FireflyIII\Support\Request\GetRuleConfiguration; | ||||
| @@ -57,13 +58,48 @@ class StoreRequest extends FormRequest | ||||
|             'active'           => ['active', 'boolean'], | ||||
|         ]; | ||||
|         $data             = $this->getAllData($fields); | ||||
| 
 | ||||
|         $data['triggers'] = $this->getRuleTriggers(); | ||||
|         $data['actions']  = $this->getRuleActions(); | ||||
| 
 | ||||
|         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. | ||||
|      */ | ||||
| @@ -87,7 +123,7 @@ class StoreRequest extends FormRequest | ||||
|             'triggers.*.stop_processing' => [new IsBoolean()], | ||||
|             'triggers.*.active'          => [new IsBoolean()], | ||||
|             'actions.*.type'             => 'required|in:'.implode(',', $validActions), | ||||
|             'actions.*.value'            => 'required_if:actions.*.type,'.$contextActions.'|ruleActionValue', | ||||
|             'actions.*.value'            => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'], | ||||
|             'actions.*.stop_processing'  => [new IsBoolean()], | ||||
|             'actions.*.active'           => [new IsBoolean()], | ||||
|             'strict'                     => [new IsBoolean()], | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -47,16 +47,6 @@ class TestRequest extends FormRequest | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     private function getPage(): int | ||||
|     { | ||||
|         return 0 === (int)$this->query('page') ? 1 : (int)$this->query('page'); | ||||
| @@ -81,4 +71,14 @@ class TestRequest extends FormRequest | ||||
|     { | ||||
|         return $this->get('accounts'); | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -46,16 +46,6 @@ class TriggerRequest extends FormRequest | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     private function getDate(string $field): ?Carbon | ||||
|     { | ||||
|         $value  = $this->query($field); | ||||
| @@ -75,4 +65,14 @@ class TriggerRequest extends FormRequest | ||||
|     { | ||||
|         return $this->get('accounts') ?? []; | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -25,6 +25,7 @@ namespace FireflyIII\Api\V1\Requests\Models\Rule; | ||||
| 
 | ||||
| use FireflyIII\Models\Rule; | ||||
| use FireflyIII\Rules\IsBoolean; | ||||
| use FireflyIII\Rules\IsValidActionExpression; | ||||
| use FireflyIII\Support\Request\ChecksLogin; | ||||
| use FireflyIII\Support\Request\ConvertsDataTypes; | ||||
| use FireflyIII\Support\Request\GetRuleConfiguration; | ||||
| @@ -70,6 +71,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. | ||||
|      */ | ||||
| @@ -96,7 +141,7 @@ class UpdateRequest extends FormRequest | ||||
|             'triggers.*.stop_processing' => [new IsBoolean()], | ||||
|             'triggers.*.active'          => [new IsBoolean()], | ||||
|             'actions.*.type'             => 'required|in:'.implode(',', $validActions), | ||||
|             'actions.*.value'            => 'required_if:actions.*.type,'.$contextActions.'|ruleActionValue', | ||||
|             'actions.*.value'            => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'], | ||||
|             'actions.*.stop_processing'  => [new IsBoolean()], | ||||
|             'actions.*.active'           => [new IsBoolean()], | ||||
|             'strict'                     => [new IsBoolean()], | ||||
| @@ -119,7 +164,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 +249,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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,42 @@ | ||||
| <?php | ||||
| 
 | ||||
| /** | ||||
|  * ValidateExpressionRequest.php | ||||
|  * Copyright (c) 2024 Michael Thomas | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Api\V1\Requests\Models\Rule; | ||||
| 
 | ||||
| use FireflyIII\Rules\IsValidActionExpression; | ||||
| use FireflyIII\Support\Request\ChecksLogin; | ||||
| use Illuminate\Foundation\Http\FormRequest; | ||||
| 
 | ||||
| /** | ||||
|  * Class ValidateExpressionRequest | ||||
|  */ | ||||
| class ValidateExpressionRequest extends FormRequest | ||||
| { | ||||
|     use ChecksLogin; | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return ['expression' => ['required', new IsValidActionExpression()]]; | ||||
|     } | ||||
| } | ||||
| @@ -46,16 +46,6 @@ class TestRequest extends FormRequest | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     private function getDate(string $field): ?Carbon | ||||
|     { | ||||
|         $value  = $this->query($field); | ||||
| @@ -75,4 +65,14 @@ class TestRequest extends FormRequest | ||||
|     { | ||||
|         return $this->get('accounts'); | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -46,14 +46,6 @@ class TriggerRequest extends FormRequest | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start' => 'date', | ||||
|             'end'   => 'date|after_or_equal:start', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     private function getDate(string $field): ?Carbon | ||||
|     { | ||||
|         $value  = $this->query($field); | ||||
| @@ -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', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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, | ||||
|                 ]; | ||||
|             } | ||||
|   | ||||
| @@ -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, | ||||
|                 ]; | ||||
|             } | ||||
|         ); | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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
 | ||||
|   | ||||
| @@ -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,42 @@ 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.
 | ||||
|         $objects  = $transformer->collectMetaData($objects); | ||||
|         $paginator->setCollection($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(); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										104
									
								
								app/Api/V2/Controllers/Model/Account/IndexController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								app/Api/V2/Controllers/Model/Account/IndexController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| <?php | ||||
| /* | ||||
|  * IndexController.php | ||||
|  * Copyright (c) 2024 james@firefly-iii.org. | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see https://www.gnu.org/licenses/. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Api\V2\Controllers\Model\Account; | ||||
| 
 | ||||
| use FireflyIII\Api\V2\Controllers\Controller; | ||||
| use FireflyIII\Api\V2\Request\Model\Account\IndexRequest; | ||||
| use FireflyIII\Api\V2\Request\Model\Transaction\InfiniteListRequest; | ||||
| use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface; | ||||
| use FireflyIII\Transformers\V2\AccountTransformer; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| use Illuminate\Pagination\LengthAwarePaginator; | ||||
| 
 | ||||
| class IndexController extends Controller | ||||
| { | ||||
|     public const string RESOURCE_KEY = 'accounts'; | ||||
| 
 | ||||
|     private AccountRepositoryInterface $repository; | ||||
| 
 | ||||
|     /** | ||||
|      * AccountController constructor. | ||||
|      */ | ||||
|     public function __construct() | ||||
|     { | ||||
|         parent::__construct(); | ||||
|         $this->middleware( | ||||
|             function ($request, $next) { | ||||
|                 $this->repository = app(AccountRepositoryInterface::class); | ||||
|                 // new way of user group validation
 | ||||
|                 $userGroup        = $this->validateUserGroup($request); | ||||
|                 if (null !== $userGroup) { | ||||
|                     $this->repository->setUserGroup($userGroup); | ||||
|                 } | ||||
| 
 | ||||
|                 return $next($request); | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO see autocomplete/accountcontroller for list. | ||||
|      */ | ||||
|     public function index(IndexRequest $request): JsonResponse | ||||
|     { | ||||
|         $this->repository->resetAccountOrder(); | ||||
|         $types        = $request->getAccountTypes(); | ||||
|         $instructions = $request->getSortInstructions('accounts'); | ||||
|         $accounts     = $this->repository->getAccountsByType($types, $instructions); | ||||
|         $pageSize     = $this->parameters->get('limit'); | ||||
|         $count        = $accounts->count(); | ||||
|         $accounts     = $accounts->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); | ||||
|         $paginator    = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page')); | ||||
|         $transformer  = new AccountTransformer(); | ||||
| 
 | ||||
|         $this->parameters->set('sort', $instructions); | ||||
|         $transformer->setParameters($this->parameters); // give params to transformer
 | ||||
| 
 | ||||
|         return response() | ||||
|             ->json($this->jsonApiList('accounts', $paginator, $transformer)) | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     public function infiniteList(InfiniteListRequest $request): JsonResponse | ||||
|     { | ||||
|         $this->repository->resetAccountOrder(); | ||||
| 
 | ||||
|         // get accounts of the specified type, and return.
 | ||||
|         $types       = $request->getAccountTypes(); | ||||
| 
 | ||||
|         // get from repository
 | ||||
|         $accounts    = $this->repository->getAccountsInOrder($types, $request->getSortInstructions('accounts'), $request->getStartRow(), $request->getEndRow()); | ||||
|         $total       = $this->repository->countAccounts($types); | ||||
|         $count       = $request->getEndRow() - $request->getStartRow(); | ||||
|         $paginator   = new LengthAwarePaginator($accounts, $total, $count, $this->parameters->get('page')); | ||||
|         $transformer = new AccountTransformer(); | ||||
|         $transformer->setParameters($this->parameters); // give params to transformer
 | ||||
| 
 | ||||
|         return response() | ||||
|             ->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer)) | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										79
									
								
								app/Api/V2/Controllers/Model/Account/UpdateController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								app/Api/V2/Controllers/Model/Account/UpdateController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| <?php | ||||
| /* | ||||
|  * UpdateController.php | ||||
|  * Copyright (c) 2024 james@firefly-iii.org. | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see https://www.gnu.org/licenses/. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Api\V2\Controllers\Model\Account; | ||||
| 
 | ||||
| use FireflyIII\Api\V2\Controllers\Controller; | ||||
| use FireflyIII\Api\V2\Request\Model\Account\UpdateRequest; | ||||
| use FireflyIII\Models\Account; | ||||
| use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface; | ||||
| use FireflyIII\Transformers\V2\AccountTransformer; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| 
 | ||||
| class UpdateController extends Controller | ||||
| { | ||||
|     public const string RESOURCE_KEY = 'accounts'; | ||||
| 
 | ||||
|     private AccountRepositoryInterface $repository; | ||||
| 
 | ||||
|     /** | ||||
|      * AccountController constructor. | ||||
|      */ | ||||
|     public function __construct() | ||||
|     { | ||||
|         parent::__construct(); | ||||
|         $this->middleware( | ||||
|             function ($request, $next) { | ||||
|                 $this->repository = app(AccountRepositoryInterface::class); | ||||
|                 // new way of user group validation
 | ||||
|                 $userGroup        = $this->validateUserGroup($request); | ||||
|                 if (null !== $userGroup) { | ||||
|                     $this->repository->setUserGroup($userGroup); | ||||
|                 } | ||||
| 
 | ||||
|                 return $next($request); | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO this endpoint is not yet reachable. | ||||
|      */ | ||||
|     public function update(UpdateRequest $request, Account $account): JsonResponse | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now in %s', __METHOD__)); | ||||
|         $data         = $request->getUpdateData(); | ||||
|         $data['type'] = config('firefly.shortNamesByFullName.'.$account->accountType->type); | ||||
|         $account      = $this->repository->update($account, $data); | ||||
|         $account->refresh(); | ||||
|         app('preferences')->mark(); | ||||
| 
 | ||||
|         $transformer  = new AccountTransformer(); | ||||
|         $transformer->setParameters($this->parameters); | ||||
| 
 | ||||
|         return response() | ||||
|             ->api($this->jsonApiObject('accounts', $account, $transformer)) | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| } | ||||
| @@ -51,20 +51,6 @@ class ShowController extends Controller | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show a budget. | ||||
|      */ | ||||
|     public function show(Budget $budget): JsonResponse | ||||
|     { | ||||
|         $transformer = new BudgetTransformer(); | ||||
|         $transformer->setParameters($this->parameters); | ||||
| 
 | ||||
|         return response() | ||||
|             ->api($this->jsonApiObject('budgets', $budget, $transformer)) | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 2023-10-29 removed the cerSum reference, not sure where this is used atm | ||||
|      * so removed from api.php. Also applies to "spent" method. | ||||
| @@ -80,6 +66,20 @@ class ShowController extends Controller | ||||
|         return response()->json($result); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show a budget. | ||||
|      */ | ||||
|     public function show(Budget $budget): JsonResponse | ||||
|     { | ||||
|         $transformer = new BudgetTransformer(); | ||||
|         $transformer->setParameters($this->parameters); | ||||
| 
 | ||||
|         return response() | ||||
|             ->api($this->jsonApiObject('budgets', $budget, $transformer)) | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This endpoint is documented at: | ||||
|      * TODO add URL | ||||
|   | ||||
| @@ -114,23 +114,6 @@ class BasicController extends Controller | ||||
|         return response()->json($total); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if date is outside session range. | ||||
|      */ | ||||
|     protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
 | ||||
|     { | ||||
|         $result = false; | ||||
|         if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
|         // start and end in the past? use $end
 | ||||
|         if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
| @@ -315,7 +298,7 @@ class BasicController extends Controller | ||||
|             app('log')->debug(sprintf('Amount left is %s', $left)); | ||||
| 
 | ||||
|             // how much left per day?
 | ||||
|             $days                    = $today->diffInDays($end) + 1; | ||||
|             $days                    = (int) $today->diffInDays($end, true) + 1; | ||||
|             $perDay                  = '0'; | ||||
|             $perDayNative            = '0'; | ||||
|             if (0 !== $days && bccomp($left, '0') > -1) { | ||||
| @@ -411,4 +394,21 @@ class BasicController extends Controller | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if date is outside session range. | ||||
|      */ | ||||
|     protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
 | ||||
|     { | ||||
|         $result = false; | ||||
|         if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
|         // start and end in the past? use $end
 | ||||
|         if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,6 +24,7 @@ declare(strict_types=1); | ||||
| namespace FireflyIII\Api\V2\Controllers\Transaction\List; | ||||
| 
 | ||||
| use FireflyIII\Api\V2\Controllers\Controller; | ||||
| use FireflyIII\Api\V2\Request\Model\Transaction\InfiniteListRequest; | ||||
| use FireflyIII\Api\V2\Request\Model\Transaction\ListRequest; | ||||
| use FireflyIII\Helpers\Collector\GroupCollectorInterface; | ||||
| use FireflyIII\Transformers\V2\TransactionGroupTransformer; | ||||
| @@ -34,6 +35,47 @@ use Illuminate\Http\JsonResponse; | ||||
|  */ | ||||
| class TransactionController extends Controller | ||||
| { | ||||
|     public function infiniteList(InfiniteListRequest $request): JsonResponse | ||||
|     { | ||||
|         // get sort instructions
 | ||||
|         $instructions = $request->getSortInstructions('transactions'); | ||||
| 
 | ||||
|         // collect transactions:
 | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector    = app(GroupCollectorInterface::class); | ||||
|         $collector->setUserGroup(auth()->user()->userGroup) | ||||
|             ->withAPIInformation() | ||||
|             ->setStartRow($request->getStartRow()) | ||||
|             ->setEndRow($request->getEndRow()) | ||||
|             ->setTypes($request->getTransactionTypes()) | ||||
|             ->setSorting($instructions) | ||||
|         ; | ||||
| 
 | ||||
|         $start        = $this->parameters->get('start'); | ||||
|         $end          = $this->parameters->get('end'); | ||||
|         if (null !== $start) { | ||||
|             $collector->setStart($start); | ||||
|         } | ||||
|         if (null !== $end) { | ||||
|             $collector->setEnd($end); | ||||
|         } | ||||
| 
 | ||||
|         $paginator    = $collector->getPaginatedGroups(); | ||||
|         $params       = $request->buildParams(); | ||||
|         $paginator->setPath( | ||||
|             sprintf( | ||||
|                 '%s?%s', | ||||
|                 route('api.v2.infinite.transactions.list'), | ||||
|                 $params | ||||
|             ) | ||||
|         ); | ||||
| 
 | ||||
|         return response() | ||||
|             ->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer())) | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     public function list(ListRequest $request): JsonResponse | ||||
|     { | ||||
|         // collect transactions:
 | ||||
| @@ -59,9 +101,6 @@ class TransactionController extends Controller | ||||
|             $collector->setEnd($end); | ||||
|         } | ||||
| 
 | ||||
|         //        $collector->dumpQuery();
 | ||||
|         //        exit;
 | ||||
| 
 | ||||
|         $paginator = $collector->getPaginatedGroups(); | ||||
|         $params    = $request->buildParams($pageSize); | ||||
|         $paginator->setPath( | ||||
|   | ||||
							
								
								
									
										73
									
								
								app/Api/V2/Controllers/UserGroup/IndexController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								app/Api/V2/Controllers/UserGroup/IndexController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| <?php | ||||
| /* | ||||
|  * IndexController.php | ||||
|  * Copyright (c) 2024 james@firefly-iii.org. | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see https://www.gnu.org/licenses/. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Api\V2\Controllers\UserGroup; | ||||
| 
 | ||||
| use FireflyIII\Api\V2\Controllers\Controller; | ||||
| use FireflyIII\Api\V2\Request\Model\Account\IndexRequest; | ||||
| use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface; | ||||
| use FireflyIII\Transformers\V2\UserGroupTransformer; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| use Illuminate\Pagination\LengthAwarePaginator; | ||||
| 
 | ||||
| class IndexController extends Controller | ||||
| { | ||||
|     public const string RESOURCE_KEY = 'user_groups'; | ||||
| 
 | ||||
|     private UserGroupRepositoryInterface $repository; | ||||
| 
 | ||||
|     /** | ||||
|      * AccountController constructor. | ||||
|      */ | ||||
|     public function __construct() | ||||
|     { | ||||
|         parent::__construct(); | ||||
|         $this->middleware( | ||||
|             function ($request, $next) { | ||||
|                 $this->repository = app(UserGroupRepositoryInterface::class); | ||||
| 
 | ||||
|                 return $next($request); | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO see autocomplete/accountcontroller for list. | ||||
|      */ | ||||
|     public function index(IndexRequest $request): JsonResponse | ||||
|     { | ||||
|         $administrations = $this->repository->get(); | ||||
|         $pageSize        = $this->parameters->get('limit'); | ||||
|         $count           = $administrations->count(); | ||||
|         $administrations = $administrations->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); | ||||
|         $paginator       = new LengthAwarePaginator($administrations, $count, $pageSize, $this->parameters->get('page')); | ||||
|         $transformer     = new UserGroupTransformer(); | ||||
| 
 | ||||
|         $transformer->setParameters($this->parameters); // give params to transformer
 | ||||
| 
 | ||||
|         return response() | ||||
|             ->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer)) | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| } | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										69
									
								
								app/Api/V2/Request/Model/Account/IndexRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/Api/V2/Request/Model/Account/IndexRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| <?php | ||||
| /* | ||||
|  * IndexRequest.php | ||||
|  * Copyright (c) 2024 james@firefly-iii.org. | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see https://www.gnu.org/licenses/. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Api\V2\Request\Model\Account; | ||||
| 
 | ||||
| use Carbon\Carbon; | ||||
| use FireflyIII\Support\Http\Api\AccountFilter; | ||||
| use FireflyIII\Support\Request\ChecksLogin; | ||||
| use FireflyIII\Support\Request\ConvertsDataTypes; | ||||
| use FireflyIII\Support\Request\GetSortInstructions; | ||||
| use Illuminate\Foundation\Http\FormRequest; | ||||
| 
 | ||||
| /** | ||||
|  * Class IndexRequest | ||||
|  * | ||||
|  * Lots of code stolen from the SingleDateRequest. | ||||
|  */ | ||||
| class IndexRequest extends FormRequest | ||||
| { | ||||
|     use AccountFilter; | ||||
|     use ChecksLogin; | ||||
|     use ConvertsDataTypes; | ||||
|     use GetSortInstructions; | ||||
| 
 | ||||
|     public function getAccountTypes(): array | ||||
|     { | ||||
|         $type = (string)$this->get('type', 'default'); | ||||
| 
 | ||||
|         return $this->mapAccountTypes($type); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all data from the request. | ||||
|      */ | ||||
|     public function getDate(): Carbon | ||||
|     { | ||||
|         return $this->getCarbonDate('date'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The rules that the incoming request must be matched against. | ||||
|      */ | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'date' => 'date|after:1900-01-01|before:2099-12-31', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										116
									
								
								app/Api/V2/Request/Model/Account/UpdateRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								app/Api/V2/Request/Model/Account/UpdateRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| <?php | ||||
| /* | ||||
|  * UpdateRequest.php | ||||
|  * Copyright (c) 2024 james@firefly-iii.org. | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see https://www.gnu.org/licenses/. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Api\V2\Request\Model\Account; | ||||
| 
 | ||||
| use FireflyIII\Models\Account; | ||||
| use FireflyIII\Models\Location; | ||||
| use FireflyIII\Rules\IsBoolean; | ||||
| use FireflyIII\Rules\UniqueAccountNumber; | ||||
| use FireflyIII\Rules\UniqueIban; | ||||
| use FireflyIII\Support\Request\AppendsLocationData; | ||||
| use FireflyIII\Support\Request\ChecksLogin; | ||||
| use FireflyIII\Support\Request\ConvertsDataTypes; | ||||
| use Illuminate\Foundation\Http\FormRequest; | ||||
| 
 | ||||
| class UpdateRequest extends FormRequest | ||||
| { | ||||
|     use AppendsLocationData; | ||||
|     use ChecksLogin; | ||||
|     use ConvertsDataTypes; | ||||
| 
 | ||||
|     /** | ||||
|      * TODO is a duplicate of the v1 update thing. | ||||
|      */ | ||||
|     public function getUpdateData(): array | ||||
|     { | ||||
|         $fields = [ | ||||
|             'name'                    => ['name', 'convertString'], | ||||
|             'active'                  => ['active', 'boolean'], | ||||
|             'include_net_worth'       => ['include_net_worth', 'boolean'], | ||||
|             'account_type_name'       => ['type', 'convertString'], | ||||
|             'virtual_balance'         => ['virtual_balance', 'convertString'], | ||||
|             'iban'                    => ['iban', 'convertString'], | ||||
|             'BIC'                     => ['bic', 'convertString'], | ||||
|             'account_number'          => ['account_number', 'convertString'], | ||||
|             'account_role'            => ['account_role', 'convertString'], | ||||
|             'liability_type'          => ['liability_type', 'convertString'], | ||||
|             'opening_balance'         => ['opening_balance', 'convertString'], | ||||
|             'opening_balance_date'    => ['opening_balance_date', 'convertDateTime'], | ||||
|             'cc_type'                 => ['credit_card_type', 'convertString'], | ||||
|             'cc_monthly_payment_date' => ['monthly_payment_date', 'convertDateTime'], | ||||
|             'notes'                   => ['notes', 'stringWithNewlines'], | ||||
|             'interest'                => ['interest', 'convertString'], | ||||
|             'interest_period'         => ['interest_period', 'convertString'], | ||||
|             'order'                   => ['order', 'convertInteger'], | ||||
|             'currency_id'             => ['currency_id', 'convertInteger'], | ||||
|             'currency_code'           => ['currency_code', 'convertString'], | ||||
|             'liability_direction'     => ['liability_direction', 'convertString'], | ||||
|             'liability_amount'        => ['liability_amount', 'convertString'], | ||||
|             'liability_start_date'    => ['liability_start_date', 'date'], | ||||
|         ]; | ||||
|         $data   = $this->getAllData($fields); | ||||
| 
 | ||||
|         return $this->appendLocationData($data, null); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO is a duplicate of the v1 UpdateRequest method. | ||||
|      * | ||||
|      * The rules that the incoming request must be matched against. | ||||
|      */ | ||||
|     public function rules(): array | ||||
|     { | ||||
|         /** @var Account $account */ | ||||
|         $account        = $this->route()->parameter('account'); | ||||
|         $accountRoles   = implode(',', config('firefly.accountRoles')); | ||||
|         $types          = implode(',', array_keys(config('firefly.subTitlesByIdentifier'))); | ||||
|         $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); | ||||
| 
 | ||||
|         $rules          = [ | ||||
|             'name'                 => sprintf('min:1|max:1024|uniqueAccountForUser:%d', $account->id), | ||||
|             'type'                 => sprintf('in:%s', $types), | ||||
|             'iban'                 => ['iban', 'nullable', new UniqueIban($account, $this->convertString('type'))], | ||||
|             'bic'                  => 'bic|nullable', | ||||
|             'account_number'       => ['min:1', 'max:255', 'nullable', new UniqueAccountNumber($account, $this->convertString('type'))], | ||||
|             'opening_balance'      => 'numeric|required_with:opening_balance_date|nullable', | ||||
|             'opening_balance_date' => 'date|required_with:opening_balance|nullable', | ||||
|             'virtual_balance'      => 'numeric|nullable', | ||||
|             'order'                => 'numeric|nullable', | ||||
|             'currency_id'          => 'numeric|exists:transaction_currencies,id', | ||||
|             'currency_code'        => 'min:3|max:51|exists:transaction_currencies,code', | ||||
|             'active'               => [new IsBoolean()], | ||||
|             'include_net_worth'    => [new IsBoolean()], | ||||
|             'account_role'         => sprintf('in:%s|nullable|required_if:type,asset', $accountRoles), | ||||
|             'credit_card_type'     => sprintf('in:%s|nullable|required_if:account_role,ccAsset', $ccPaymentTypes), | ||||
|             'monthly_payment_date' => 'date|nullable|required_if:account_role,ccAsset|required_if:credit_card_type,monthlyFull', | ||||
|             'liability_type'       => 'required_if:type,liability|in:loan,debt,mortgage', | ||||
|             'liability_direction'  => 'required_if:type,liability|in:credit,debit', | ||||
|             'interest'             => 'required_if:type,liability|min:0|max:100|numeric', | ||||
|             'interest_period'      => 'required_if:type,liability|in:daily,monthly,yearly', | ||||
|             'notes'                => 'min:0|max:32768', | ||||
|         ]; | ||||
| 
 | ||||
|         return Location::requestRules($rules); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										118
									
								
								app/Api/V2/Request/Model/Transaction/InfiniteListRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								app/Api/V2/Request/Model/Transaction/InfiniteListRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| <?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\AccountFilter; | ||||
| use FireflyIII\Support\Http\Api\TransactionFilter; | ||||
| use FireflyIII\Support\Request\ChecksLogin; | ||||
| use FireflyIII\Support\Request\ConvertsDataTypes; | ||||
| use FireflyIII\Support\Request\GetSortInstructions; | ||||
| use Illuminate\Foundation\Http\FormRequest; | ||||
| 
 | ||||
| /** | ||||
|  * Class InfiniteListRequest | ||||
|  * Used specifically to list transactions. | ||||
|  */ | ||||
| class InfiniteListRequest extends FormRequest | ||||
| { | ||||
|     use AccountFilter; | ||||
|     use ChecksLogin; | ||||
|     use ConvertsDataTypes; | ||||
|     use GetSortInstructions; | ||||
|     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 getAccountTypes(): array | ||||
|     { | ||||
|         $type = (string)$this->get('type', 'default'); | ||||
| 
 | ||||
|         return $this->mapAccountTypes($type); | ||||
|     } | ||||
| 
 | ||||
|     public function getPage(): int | ||||
|     { | ||||
|         $page = $this->convertInteger('page'); | ||||
| 
 | ||||
|         return 0 === $page || $page > 65536 ? 1 : $page; | ||||
|     } | ||||
| 
 | ||||
|     public function getTransactionTypes(): array | ||||
|     { | ||||
|         $type = (string)$this->get('type', 'default'); | ||||
| 
 | ||||
|         return $this->mapTransactionTypes($type); | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'     => 'date', | ||||
|             'end'       => 'date|after:start', | ||||
|             'start_row' => 'integer|min:0|max:4294967296', | ||||
|             'end_row'   => 'integer|min:0|max:4294967296|gt:start_row', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -78,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()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -248,29 +248,14 @@ class FixAccountTypes extends Command | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function isLiability(string $destinationType): bool | ||||
|     { | ||||
|         return AccountType::LOAN === $destinationType || AccountType::DEBT === $destinationType || AccountType::MORTGAGE === $destinationType; | ||||
|     } | ||||
| 
 | ||||
|     private function shouldBeTransfer(string $transactionType, string $sourceType, string $destinationType): bool | ||||
|     { | ||||
|         return TransactionType::TRANSFER === $transactionType && AccountType::ASSET === $sourceType && $this->isLiability($destinationType); | ||||
|     } | ||||
| 
 | ||||
|     private function shouldBeDeposit(string $transactionType, string $sourceType, string $destinationType): bool | ||||
|     private function isLiability(string $destinationType): bool | ||||
|     { | ||||
|         return TransactionType::TRANSFER === $transactionType && $this->isLiability($sourceType) && AccountType::ASSET === $destinationType; | ||||
|     } | ||||
| 
 | ||||
|     private function shouldGoToExpenseAccount(string $transactionType, string $sourceType, string $destinationType): bool | ||||
|     { | ||||
|         return TransactionType::WITHDRAWAL === $transactionType && AccountType::ASSET === $sourceType && AccountType::REVENUE === $destinationType; | ||||
|     } | ||||
| 
 | ||||
|     private function shouldComeFromRevenueAccount(string $transactionType, string $sourceType, string $destinationType): bool | ||||
|     { | ||||
|         return TransactionType::DEPOSIT === $transactionType && AccountType::EXPENSE === $sourceType && AccountType::ASSET === $destinationType; | ||||
|         return AccountType::LOAN === $destinationType || AccountType::DEBT === $destinationType || AccountType::MORTGAGE === $destinationType; | ||||
|     } | ||||
| 
 | ||||
|     private function makeTransfer(TransactionJournal $journal): void | ||||
| @@ -286,6 +271,11 @@ class FixAccountTypes extends Command | ||||
|         $this->inspectJournal($journal); | ||||
|     } | ||||
| 
 | ||||
|     private function shouldBeDeposit(string $transactionType, string $sourceType, string $destinationType): bool | ||||
|     { | ||||
|         return TransactionType::TRANSFER === $transactionType && $this->isLiability($sourceType) && AccountType::ASSET === $destinationType; | ||||
|     } | ||||
| 
 | ||||
|     private function makeDeposit(TransactionJournal $journal): void | ||||
|     { | ||||
|         // from a liability to an asset should be a deposit.
 | ||||
| @@ -299,6 +289,11 @@ class FixAccountTypes extends Command | ||||
|         $this->inspectJournal($journal); | ||||
|     } | ||||
| 
 | ||||
|     private function shouldGoToExpenseAccount(string $transactionType, string $sourceType, string $destinationType): bool | ||||
|     { | ||||
|         return TransactionType::WITHDRAWAL === $transactionType && AccountType::ASSET === $sourceType && AccountType::REVENUE === $destinationType; | ||||
|     } | ||||
| 
 | ||||
|     private function makeExpenseDestination(TransactionJournal $journal, Transaction $destination): void | ||||
|     { | ||||
|         // withdrawals with a revenue account as destination instead of an expense account.
 | ||||
| @@ -320,6 +315,11 @@ class FixAccountTypes extends Command | ||||
|         $this->inspectJournal($journal); | ||||
|     } | ||||
| 
 | ||||
|     private function shouldComeFromRevenueAccount(string $transactionType, string $sourceType, string $destinationType): bool | ||||
|     { | ||||
|         return TransactionType::DEPOSIT === $transactionType && AccountType::EXPENSE === $sourceType && AccountType::ASSET === $destinationType; | ||||
|     } | ||||
| 
 | ||||
|     private function makeRevenueSource(TransactionJournal $journal, Transaction $source): void | ||||
|     { | ||||
|         // deposits with an expense account as source instead of a revenue account.
 | ||||
| @@ -355,11 +355,6 @@ class FixAccountTypes extends Command | ||||
|         return in_array($accountType, $validTypes, true); | ||||
|     } | ||||
| 
 | ||||
|     private function canCreateDestination(array $validDestinations): bool | ||||
|     { | ||||
|         return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, true); | ||||
|     } | ||||
| 
 | ||||
|     private function giveNewRevenue(TransactionJournal $journal, Transaction $source): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('An account of type "%s" could be a valid source.', AccountTypeEnum::REVENUE->value)); | ||||
| @@ -373,6 +368,11 @@ class FixAccountTypes extends Command | ||||
|         $this->inspectJournal($journal); | ||||
|     } | ||||
| 
 | ||||
|     private function canCreateDestination(array $validDestinations): bool | ||||
|     { | ||||
|         return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, true); | ||||
|     } | ||||
| 
 | ||||
|     private function giveNewExpense(TransactionJournal $journal, Transaction $destination): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('An account of type "%s" could be a valid destination.', AccountTypeEnum::EXPENSE->value)); | ||||
|   | ||||
| @@ -57,6 +57,19 @@ class CreateGroupMemberships extends Command | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function createGroupMemberships(): void | ||||
|     { | ||||
|         $users = User::get(); | ||||
| 
 | ||||
|         /** @var User $user */ | ||||
|         foreach ($users as $user) { | ||||
|             self::createGroupMembership($user); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO move to helper. | ||||
|      * | ||||
| @@ -93,17 +106,4 @@ class CreateGroupMemberships extends Command | ||||
|             $user->save(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function createGroupMemberships(): void | ||||
|     { | ||||
|         $users = User::get(); | ||||
| 
 | ||||
|         /** @var User $user */ | ||||
|         foreach ($users as $user) { | ||||
|             self::createGroupMembership($user); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -53,40 +53,40 @@ class ForceDecimalSize extends Command | ||||
| { | ||||
|     use ShowsFriendlyMessages; | ||||
| 
 | ||||
|     protected $description      = 'This command resizes DECIMAL columns in MySQL or PostgreSQL and correct amounts (only MySQL).'; | ||||
|     protected $signature        = 'firefly-iii:force-decimal-size'; | ||||
|     protected $description = 'This command resizes DECIMAL columns in MySQL or PostgreSQL and correct amounts (only MySQL).'; | ||||
|     protected $signature   = 'firefly-iii:force-decimal-size'; | ||||
|     private string $cast; | ||||
|     private array  $classes | ||||
|                                 = [ | ||||
|                                     'accounts'                 => Account::class, | ||||
|                                     'auto_budgets'             => AutoBudget::class, | ||||
|                                     'available_budgets'        => AvailableBudget::class, | ||||
|                                     'bills'                    => Bill::class, | ||||
|                                     'budget_limits'            => BudgetLimit::class, | ||||
|                                     'piggy_bank_events'        => PiggyBankEvent::class, | ||||
|                                     'piggy_bank_repetitions'   => PiggyBankRepetition::class, | ||||
|                                     'piggy_banks'              => PiggyBank::class, | ||||
|                                     'recurrences_transactions' => RecurrenceTransaction::class, | ||||
|                                     'transactions'             => Transaction::class, | ||||
|                                 ]; | ||||
|                            = [ | ||||
|             'accounts'                 => Account::class, | ||||
|             'auto_budgets'             => AutoBudget::class, | ||||
|             'available_budgets'        => AvailableBudget::class, | ||||
|             'bills'                    => Bill::class, | ||||
|             'budget_limits'            => BudgetLimit::class, | ||||
|             'piggy_bank_events'        => PiggyBankEvent::class, | ||||
|             'piggy_bank_repetitions'   => PiggyBankRepetition::class, | ||||
|             'piggy_banks'              => PiggyBank::class, | ||||
|             'recurrences_transactions' => RecurrenceTransaction::class, | ||||
|             'transactions'             => Transaction::class, | ||||
|         ]; | ||||
| 
 | ||||
|     private string $operator; | ||||
|     private string $regularExpression; | ||||
|     private array  $tables | ||||
|                                 = [ | ||||
|                                     'accounts'                 => ['virtual_balance'], | ||||
|                                     'auto_budgets'             => ['amount'], | ||||
|                                     'available_budgets'        => ['amount'], | ||||
|                                     'bills'                    => ['amount_min', 'amount_max'], | ||||
|                                     'budget_limits'            => ['amount'], | ||||
|                                     'currency_exchange_rates'  => ['rate', 'user_rate'], | ||||
|                                     'limit_repetitions'        => ['amount'], | ||||
|                                     'piggy_bank_events'        => ['amount'], | ||||
|                                     'piggy_bank_repetitions'   => ['currentamount'], | ||||
|                                     'piggy_banks'              => ['targetamount'], | ||||
|                                     'recurrences_transactions' => ['amount', 'foreign_amount'], | ||||
|                                     'transactions'             => ['amount', 'foreign_amount'], | ||||
|                                 ]; | ||||
|                            = [ | ||||
|             'accounts'                 => ['virtual_balance'], | ||||
|             'auto_budgets'             => ['amount'], | ||||
|             'available_budgets'        => ['amount'], | ||||
|             'bills'                    => ['amount_min', 'amount_max'], | ||||
|             'budget_limits'            => ['amount'], | ||||
|             'currency_exchange_rates'  => ['rate', 'user_rate'], | ||||
|             'limit_repetitions'        => ['amount'], | ||||
|             'piggy_bank_events'        => ['amount'], | ||||
|             'piggy_bank_repetitions'   => ['currentamount'], | ||||
|             'piggy_banks'              => ['targetamount'], | ||||
|             'recurrences_transactions' => ['amount', 'foreign_amount'], | ||||
|             'transactions'             => ['amount', 'foreign_amount'], | ||||
|         ]; | ||||
| 
 | ||||
|     /** | ||||
|      * Execute the console command. | ||||
|   | ||||
							
								
								
									
										65
									
								
								app/Console/Commands/System/LaravelPassportKeys.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								app/Console/Commands/System/LaravelPassportKeys.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| /* | ||||
|  * LaravelPassportKeys.php | ||||
|  * Copyright (c) 2024 james@firefly-iii.org. | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see https://www.gnu.org/licenses/. | ||||
|  */ | ||||
| 
 | ||||
| namespace FireflyIII\Console\Commands\System; | ||||
| 
 | ||||
| use FireflyIII\Console\Commands\ShowsFriendlyMessages; | ||||
| use Illuminate\Console\Command; | ||||
| use Illuminate\Support\Facades\Artisan; | ||||
| use Symfony\Component\Console\Command\Command as CommandAlias; | ||||
| 
 | ||||
| class LaravelPassportKeys extends Command | ||||
| { | ||||
|     use ShowsFriendlyMessages; | ||||
| 
 | ||||
|     /** | ||||
|      * The name and signature of the console command. | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $signature   = 'firefly-iii:laravel-passport-keys'; | ||||
| 
 | ||||
|     /** | ||||
|      * The console command description. | ||||
|      * | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $description = 'Calls the Laravel "passport:keys" but doesn\'t exit 1.'; | ||||
| 
 | ||||
|     /** | ||||
|      * Execute the console command. | ||||
|      */ | ||||
|     public function handle() | ||||
|     { | ||||
|         Artisan::call('passport:keys --no-interaction', []); | ||||
|         $result = Artisan::output(); | ||||
|         if (str_contains($result, 'Encryption keys already exist')) { | ||||
|             $this->friendlyInfo('Encryption keys exist already.'); | ||||
| 
 | ||||
|             return CommandAlias::SUCCESS; | ||||
|         } | ||||
|         $this->friendlyPositive('Encryption keys have been created, nice!'); | ||||
| 
 | ||||
|         return CommandAlias::SUCCESS; | ||||
|     } | ||||
| } | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <?php | ||||
| /** | ||||
| /* | ||||
|  * ApplyRules.php | ||||
|  * Copyright (c) 2020 james@firefly-iii.org | ||||
|  * Copyright (c) 2024 james@firefly-iii.org. | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
| @@ -16,7 +16,7 @@ | ||||
|  * 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/>. | ||||
|  * along with this program.  If not, see https://www.gnu.org/licenses/. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| <?php | ||||
| 
 | ||||
| /** | ||||
| /* | ||||
|  * Cron.php | ||||
|  * Copyright (c) 2020 james@firefly-iii.org | ||||
|  * Copyright (c) 2024 james@firefly-iii.org. | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
| @@ -17,7 +17,7 @@ | ||||
|  * 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/>. | ||||
|  * along with this program.  If not, see https://www.gnu.org/licenses/. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
|   | ||||
| @@ -110,7 +110,7 @@ class AppendBudgetLimitPeriods extends Command | ||||
|             return 'daily'; | ||||
|         } | ||||
|         // is weekly
 | ||||
|         if ('1' === $limit->start_date->format('N') && '7' === $limit->end_date->format('N') && 6 === $limit->end_date->diffInDays($limit->start_date)) { | ||||
|         if ('1' === $limit->start_date->format('N') && '7' === $limit->end_date->format('N') && 6 === (int)$limit->end_date->diffInDays($limit->start_date, true)) { | ||||
|             return 'weekly'; | ||||
|         } | ||||
| 
 | ||||
| @@ -129,7 +129,7 @@ class AppendBudgetLimitPeriods extends Command | ||||
|         if ( | ||||
|             in_array($limit->start_date->format('j-n'), $start, true) // start of quarter
 | ||||
|             && in_array($limit->end_date->format('j-n'), $end, true) // end of quarter
 | ||||
|             && 2 === $limit->start_date->diffInMonths($limit->end_date) | ||||
|             && 2 === (int)$limit->start_date->diffInMonths($limit->end_date, true) | ||||
|         ) { | ||||
|             return 'quarterly'; | ||||
|         } | ||||
| @@ -139,7 +139,7 @@ class AppendBudgetLimitPeriods extends Command | ||||
|         if ( | ||||
|             in_array($limit->start_date->format('j-n'), $start, true) // start of quarter
 | ||||
|             && in_array($limit->end_date->format('j-n'), $end, true) // end of quarter
 | ||||
|             && 5 === $limit->start_date->diffInMonths($limit->end_date) | ||||
|             && 5 === (int)$limit->start_date->diffInMonths($limit->end_date, true) | ||||
|         ) { | ||||
|             return 'half_year'; | ||||
|         } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
							
								
								
									
										182
									
								
								app/Console/Commands/Upgrade/MigrateRuleActions.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								app/Console/Commands/Upgrade/MigrateRuleActions.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,182 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| /* | ||||
|  * MigrateRuleActions.php | ||||
|  * Copyright (c) 2024 james@firefly-iii.org. | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see https://www.gnu.org/licenses/. | ||||
|  */ | ||||
| 
 | ||||
| namespace FireflyIII\Console\Commands\Upgrade; | ||||
| 
 | ||||
| use FireflyIII\Console\Commands\ShowsFriendlyMessages; | ||||
| use FireflyIII\Models\RuleAction; | ||||
| use Illuminate\Console\Command; | ||||
| 
 | ||||
| class MigrateRuleActions extends Command | ||||
| { | ||||
|     use ShowsFriendlyMessages; | ||||
| 
 | ||||
|     public const string CONFIG_NAME = '610_migrate_rule_actions'; | ||||
| 
 | ||||
|     protected $description          = 'Migrate rule actions away from expression engine'; | ||||
| 
 | ||||
|     protected $signature            = 'firefly-iii:migrate-rule-actions {--F|force : Force the execution of this command.}'; | ||||
| 
 | ||||
|     /** | ||||
|      * Execute the console command. | ||||
|      */ | ||||
|     public function handle(): int | ||||
|     { | ||||
|         if ($this->isExecuted() && true !== $this->option('force')) { | ||||
|             $this->friendlyInfo('This command has already been executed.'); | ||||
| 
 | ||||
|             return 0; | ||||
|         } | ||||
|         if (false === config('firefly.feature_flags.expression_engine')) { | ||||
|             $this->friendlyInfo('Expression engine is not enabled. Nothing to do.'); | ||||
| 
 | ||||
|             return 0; | ||||
|         } | ||||
|         $this->replaceEqualSign(); | ||||
|         $this->replaceObsoleteActions(); | ||||
| 
 | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     private function isExecuted(): bool | ||||
|     { | ||||
|         $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); | ||||
|         if (null !== $configVar) { | ||||
|             return (bool)$configVar->data; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     private function replaceEqualSign(): void | ||||
|     { | ||||
|         $count   = 0; | ||||
|         $actions = RuleAction::get(); | ||||
| 
 | ||||
|         /** @var RuleAction $action */ | ||||
|         foreach ($actions as $action) { | ||||
|             if (str_starts_with($action->action_value, '=')) { | ||||
|                 $action->action_value = sprintf('%s%s', '\=', substr($action->action_value, 1)); | ||||
|                 $action->save(); | ||||
|                 ++$count; | ||||
|             } | ||||
|         } | ||||
|         if ($count > 0) { | ||||
|             $this->friendlyInfo(sprintf('Upgrading %d rule action(s) for the new expression engine.', $count)); | ||||
|         } | ||||
|         if (0 === $count) { | ||||
|             $this->friendlyInfo('All rule actions are up to date.'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function replaceObsoleteActions(): void | ||||
|     { | ||||
|         $obsolete = [ | ||||
|             'append_description', | ||||
|             'prepend_description', | ||||
|             'append_notes', | ||||
|             'prepend_notes', | ||||
|             'append_descr_to_notes', | ||||
|             'append_notes_to_descr', | ||||
|             'move_descr_to_notes', | ||||
|             'move_notes_to_descr', | ||||
|         ]; | ||||
|         $actions  = RuleAction::whereIn('action_type', $obsolete)->get(); | ||||
| 
 | ||||
|         /** @var RuleAction $action */ | ||||
|         foreach ($actions as $action) { | ||||
|             $oldType = $action->action_type; | ||||
| 
 | ||||
|             switch ($action->action_type) { | ||||
|                 default: | ||||
|                     $this->friendlyError(sprintf('Cannot deal with action type "%s", skip it.', $action->action_type)); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'append_description': | ||||
|                     $action->action_type  = 'set_description'; | ||||
|                     $action->action_value = sprintf('=description~"%s"', str_replace('"', '\"', $action->action_value)); | ||||
|                     $action->save(); | ||||
|                     $this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type)); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'prepend_description': | ||||
|                     $action->action_type  = 'set_description'; | ||||
|                     $action->action_value = sprintf('="%s"~description', str_replace('"', '\"', $action->action_value)); | ||||
|                     $action->save(); | ||||
|                     $this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type)); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'append_notes': | ||||
|                     $action->action_type  = 'set_notes'; | ||||
|                     $action->action_value = sprintf('=notes~"%s"', str_replace('"', '\"', $action->action_value)); | ||||
|                     $action->save(); | ||||
|                     $this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type)); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'prepend_notes': | ||||
|                     $action->action_type  = 'set_notes'; | ||||
|                     $action->action_value = sprintf('="%s"~notes', str_replace('"', '\"', $action->action_value)); | ||||
|                     $action->save(); | ||||
|                     $this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type)); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'append_descr_to_notes': | ||||
|                     $action->action_type  = 'set_notes'; | ||||
|                     $action->action_value = '=notes~" "~description'; | ||||
|                     $action->save(); | ||||
|                     $this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type)); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'append_notes_to_descr': | ||||
|                     $action->action_type  = 'set_description'; | ||||
|                     $action->action_value = '=description~" "~notes'; | ||||
|                     $action->save(); | ||||
|                     $this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type)); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'move_descr_to_notes': | ||||
|                     $action->action_type  = 'set_notes'; | ||||
|                     $action->action_value = '=description'; | ||||
|                     $action->save(); | ||||
|                     $this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type)); | ||||
| 
 | ||||
|                     break; | ||||
| 
 | ||||
|                 case 'move_notes_to_descr': | ||||
|                     $action->action_type  = 'set_description'; | ||||
|                     $action->action_value = '=notes'; | ||||
|                     $action->save(); | ||||
|                     $this->friendlyInfo(sprintf('Upgraded action #%d from "%s" to "%s".', $action->id, $oldType, $action->action_type)); | ||||
| 
 | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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()'); | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user