mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-29 22:47:42 +00:00 
			
		
		
		
	Compare commits
	
		
			35 Commits
		
	
	
		
			develop-20
			...
			develop-20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 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 | 
							
								
								
									
										71
									
								
								.ci/php-cs-fixer/composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										71
									
								
								.ci/php-cs-fixer/composer.lock
									
									
									
										generated
									
									
									
								
							| @@ -226,16 +226,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "friendsofphp/php-cs-fixer", | ||||
|             "version": "v3.49.0", | ||||
|             "version": "v3.51.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", | ||||
|                 "reference": "8742f7aa6f72a399688b65e4f58992c2d4681fc2" | ||||
|                 "reference": "127fa74f010da99053e3f5b62672615b72dd6efd" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/8742f7aa6f72a399688b65e4f58992c2d4681fc2", | ||||
|                 "reference": "8742f7aa6f72a399688b65e4f58992c2d4681fc2", | ||||
|                 "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/127fa74f010da99053e3f5b62672615b72dd6efd", | ||||
|                 "reference": "127fa74f010da99053e3f5b62672615b72dd6efd", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -245,7 +245,7 @@ | ||||
|                 "ext-json": "*", | ||||
|                 "ext-tokenizer": "*", | ||||
|                 "php": "^7.4 || ^8.0", | ||||
|                 "sebastian/diff": "^4.0 || ^5.0", | ||||
|                 "sebastian/diff": "^4.0 || ^5.0 || ^6.0", | ||||
|                 "symfony/console": "^5.4 || ^6.0 || ^7.0", | ||||
|                 "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", | ||||
|                 "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", | ||||
| @@ -266,7 +266,8 @@ | ||||
|                 "php-cs-fixer/accessible-object": "^1.1", | ||||
|                 "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.4", | ||||
|                 "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.4", | ||||
|                 "phpunit/phpunit": "^9.6 || ^10.5.5", | ||||
|                 "phpunit/phpunit": "^9.6 || ^10.5.5 || ^11.0.2", | ||||
|                 "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", | ||||
|                 "symfony/yaml": "^5.4 || ^6.0 || ^7.0" | ||||
|             }, | ||||
|             "suggest": { | ||||
| @@ -305,7 +306,7 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", | ||||
|                 "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.49.0" | ||||
|                 "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.51.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -313,7 +314,7 @@ | ||||
|                     "type": "github" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-02-02T00:41:40+00:00" | ||||
|             "time": "2024-02-28T19:50:06+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "psr/container", | ||||
| @@ -470,29 +471,29 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "sebastian/diff", | ||||
|             "version": "5.1.0", | ||||
|             "version": "6.0.1", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/sebastianbergmann/diff.git", | ||||
|                 "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f" | ||||
|                 "reference": "ab83243ecc233de5655b76f577711de9f842e712" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f", | ||||
|                 "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f", | ||||
|                 "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ab83243ecc233de5655b76f577711de9f842e712", | ||||
|                 "reference": "ab83243ecc233de5655b76f577711de9f842e712", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
|                 "php": ">=8.1" | ||||
|                 "php": ">=8.2" | ||||
|             }, | ||||
|             "require-dev": { | ||||
|                 "phpunit/phpunit": "^10.0", | ||||
|                 "phpunit/phpunit": "^11.0", | ||||
|                 "symfony/process": "^4.2 || ^5" | ||||
|             }, | ||||
|             "type": "library", | ||||
|             "extra": { | ||||
|                 "branch-alias": { | ||||
|                     "dev-main": "5.1-dev" | ||||
|                     "dev-main": "6.0-dev" | ||||
|                 } | ||||
|             }, | ||||
|             "autoload": { | ||||
| @@ -525,7 +526,7 @@ | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/sebastianbergmann/diff/issues", | ||||
|                 "security": "https://github.com/sebastianbergmann/diff/security/policy", | ||||
|                 "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0" | ||||
|                 "source": "https://github.com/sebastianbergmann/diff/tree/6.0.1" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -533,20 +534,20 @@ | ||||
|                     "type": "github" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2023-12-22T10:55:06+00:00" | ||||
|             "time": "2024-03-02T07:30:33+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/console", | ||||
|             "version": "v7.0.3", | ||||
|             "version": "v7.0.4", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/console.git", | ||||
|                 "reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456" | ||||
|                 "reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/symfony/console/zipball/c5010d50f1ee4b25cfa0201d9915cf1b14071456", | ||||
|                 "reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456", | ||||
|                 "url": "https://api.github.com/repos/symfony/console/zipball/6b099f3306f7c9c2d2786ed736d0026b2903205f", | ||||
|                 "reference": "6b099f3306f7c9c2d2786ed736d0026b2903205f", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -610,7 +611,7 @@ | ||||
|                 "terminal" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/console/tree/v7.0.3" | ||||
|                 "source": "https://github.com/symfony/console/tree/v7.0.4" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -626,7 +627,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-01-23T15:02:46+00:00" | ||||
|             "time": "2024-02-22T20:27:20+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/deprecation-contracts", | ||||
| @@ -1521,16 +1522,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "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": { | ||||
| @@ -1562,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": [ | ||||
|                 { | ||||
| @@ -1578,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", | ||||
| @@ -1726,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": { | ||||
| @@ -1792,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": [ | ||||
|                 { | ||||
| @@ -1808,7 +1809,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-01-29T15:41:16+00:00" | ||||
|             "time": "2024-02-01T13:17:36+00:00" | ||||
|         } | ||||
|     ], | ||||
|     "packages-dev": [], | ||||
|   | ||||
							
								
								
									
										36
									
								
								.ci/phpmd/composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										36
									
								
								.ci/phpmd/composer.lock
									
									
									
										generated
									
									
									
								
							| @@ -395,16 +395,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/config", | ||||
|             "version": "v7.0.3", | ||||
|             "version": "v7.0.4", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/config.git", | ||||
|                 "reference": "86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16" | ||||
|                 "reference": "44deeba7233f08f383185ffa37dace3b3bc87364" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/symfony/config/zipball/86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16", | ||||
|                 "reference": "86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16", | ||||
|                 "url": "https://api.github.com/repos/symfony/config/zipball/44deeba7233f08f383185ffa37dace3b3bc87364", | ||||
|                 "reference": "44deeba7233f08f383185ffa37dace3b3bc87364", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -450,7 +450,7 @@ | ||||
|             "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", | ||||
|             "homepage": "https://symfony.com", | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/config/tree/v7.0.3" | ||||
|                 "source": "https://github.com/symfony/config/tree/v7.0.4" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -466,20 +466,20 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-01-30T08:34:29+00:00" | ||||
|             "time": "2024-02-26T07:52:39+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/dependency-injection", | ||||
|             "version": "v7.0.3", | ||||
|             "version": "v7.0.4", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/dependency-injection.git", | ||||
|                 "reference": "e915c6684b8e3ae90a4441f6823ebbb40edf0b92" | ||||
|                 "reference": "47f37af245df8457ea63409fc242b3cc825ce5eb" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e915c6684b8e3ae90a4441f6823ebbb40edf0b92", | ||||
|                 "reference": "e915c6684b8e3ae90a4441f6823ebbb40edf0b92", | ||||
|                 "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/47f37af245df8457ea63409fc242b3cc825ce5eb", | ||||
|                 "reference": "47f37af245df8457ea63409fc242b3cc825ce5eb", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -530,7 +530,7 @@ | ||||
|             "description": "Allows you to standardize and centralize the way objects are constructed in your application", | ||||
|             "homepage": "https://symfony.com", | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/dependency-injection/tree/v7.0.3" | ||||
|                 "source": "https://github.com/symfony/dependency-injection/tree/v7.0.4" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -546,7 +546,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-01-30T08:34:29+00:00" | ||||
|             "time": "2024-02-22T20:27:20+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/deprecation-contracts", | ||||
| @@ -921,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": { | ||||
| @@ -975,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": [ | ||||
|                 { | ||||
| @@ -991,7 +991,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-01-23T15:02:46+00:00" | ||||
|             "time": "2024-02-26T10:35:24+00:00" | ||||
|         } | ||||
|     ], | ||||
|     "aliases": [], | ||||
|   | ||||
							
								
								
									
										16
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -51,7 +51,7 @@ jobs: | ||||
|           CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }} | ||||
|       - name: Cleanup translations | ||||
|         id: cleanup-transactions | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v34 | ||||
|         with: | ||||
|           action: 'ff3:crowdin-warning' | ||||
|           output: '' | ||||
| @@ -60,7 +60,7 @@ jobs: | ||||
|           GH_TOKEN: '' | ||||
|       - name: Cleanup changelog | ||||
|         id: cleanup-changelog | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v34 | ||||
|         with: | ||||
|           action: 'ff3:changelog' | ||||
|           output: '' | ||||
| @@ -69,7 +69,7 @@ jobs: | ||||
|           GH_TOKEN: ${{ secrets.CHANGELOG_TOKEN }} | ||||
|       - name: Extract changelog | ||||
|         id: extract-changelog | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v34 | ||||
|         with: | ||||
|           action: 'ff3:extract-changelog' | ||||
|           output: 'output' | ||||
| @@ -78,7 +78,7 @@ jobs: | ||||
|           GH_TOKEN: "" | ||||
|       - name: Replace version | ||||
|         id: replace-version | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v34 | ||||
|         with: | ||||
|           action: 'ff3:version' | ||||
|           output: '' | ||||
| @@ -88,7 +88,7 @@ jobs: | ||||
|           FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }} | ||||
|       - name: Generate JSON v1 | ||||
|         id: json-v1 | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v34 | ||||
|         with: | ||||
|           action: 'ff3:json-translations v1' | ||||
|           output: '' | ||||
| @@ -97,7 +97,7 @@ jobs: | ||||
|           GH_TOKEN: '' | ||||
|       - name: Generate JSON v2 | ||||
|         id: json-v2 | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v34 | ||||
|         with: | ||||
|           action: 'ff3:json-translations v2' | ||||
|           output: '' | ||||
| @@ -106,7 +106,7 @@ jobs: | ||||
|           GH_TOKEN: '' | ||||
|       - name: Code cleanup | ||||
|         id: code-cleanup | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v34 | ||||
|         with: | ||||
|           action: 'ff3:code' | ||||
|           output: '' | ||||
| @@ -119,7 +119,7 @@ jobs: | ||||
|           npm run build | ||||
|       - name: Build old JS | ||||
|         id: old-js | ||||
|         uses: JC5/firefly-iii-dev@v32 | ||||
|         uses: JC5/firefly-iii-dev@v34 | ||||
|         with: | ||||
|           action: 'ff3:old-js' | ||||
|           output: '' | ||||
|   | ||||
| @@ -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,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.
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -118,23 +118,6 @@ class BasicController extends Controller | ||||
|         return response()->json($return); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if date is outside session range. | ||||
|      */ | ||||
|     protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
 | ||||
|     { | ||||
|         $result = false; | ||||
|         if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
|         // start and end in the past? use $end
 | ||||
|         if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     private function getBalanceInformation(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         // prep some arrays:
 | ||||
| @@ -152,7 +135,7 @@ class BasicController extends Controller | ||||
| 
 | ||||
|         /** @var array $transactionJournal */ | ||||
|         foreach ($set as $transactionJournal) { | ||||
|             $currencyId           = (int) $transactionJournal['currency_id']; | ||||
|             $currencyId           = (int)$transactionJournal['currency_id']; | ||||
|             $incomes[$currencyId] ??= '0'; | ||||
|             $incomes[$currencyId] = bcadd( | ||||
|                 $incomes[$currencyId], | ||||
| @@ -170,7 +153,7 @@ class BasicController extends Controller | ||||
| 
 | ||||
|         /** @var array $transactionJournal */ | ||||
|         foreach ($set as $transactionJournal) { | ||||
|             $currencyId            = (int) $transactionJournal['currency_id']; | ||||
|             $currencyId            = (int)$transactionJournal['currency_id']; | ||||
|             $expenses[$currencyId] ??= '0'; | ||||
|             $expenses[$currencyId] = bcadd($expenses[$currencyId], $transactionJournal['amount']); | ||||
|             $sums[$currencyId]     ??= '0'; | ||||
| @@ -189,7 +172,7 @@ class BasicController extends Controller | ||||
|                 'key'                     => sprintf('balance-in-%s', $currency->code), | ||||
|                 'title'                   => trans('firefly.box_balance_in_currency', ['currency' => $currency->symbol]), | ||||
|                 'monetary_value'          => $sums[$currencyId] ?? '0', | ||||
|                 'currency_id'             => (string) $currency->id, | ||||
|                 'currency_id'             => (string)$currency->id, | ||||
|                 'currency_code'           => $currency->code, | ||||
|                 'currency_symbol'         => $currency->symbol, | ||||
|                 'currency_decimal_places' => $currency->decimal_places, | ||||
| @@ -202,7 +185,7 @@ class BasicController extends Controller | ||||
|                 'key'                     => sprintf('spent-in-%s', $currency->code), | ||||
|                 'title'                   => trans('firefly.box_spent_in_currency', ['currency' => $currency->symbol]), | ||||
|                 'monetary_value'          => $expenses[$currencyId] ?? '0', | ||||
|                 'currency_id'             => (string) $currency->id, | ||||
|                 'currency_id'             => (string)$currency->id, | ||||
|                 'currency_code'           => $currency->code, | ||||
|                 'currency_symbol'         => $currency->symbol, | ||||
|                 'currency_decimal_places' => $currency->decimal_places, | ||||
| @@ -214,7 +197,7 @@ class BasicController extends Controller | ||||
|                 'key'                     => sprintf('earned-in-%s', $currency->code), | ||||
|                 'title'                   => trans('firefly.box_earned_in_currency', ['currency' => $currency->symbol]), | ||||
|                 'monetary_value'          => $incomes[$currencyId] ?? '0', | ||||
|                 'currency_id'             => (string) $currency->id, | ||||
|                 'currency_id'             => (string)$currency->id, | ||||
|                 'currency_code'           => $currency->code, | ||||
|                 'currency_symbol'         => $currency->symbol, | ||||
|                 'currency_decimal_places' => $currency->decimal_places, | ||||
| @@ -248,7 +231,7 @@ class BasicController extends Controller | ||||
|                 'key'                     => sprintf('bills-paid-in-%s', $info['code']), | ||||
|                 'title'                   => trans('firefly.box_bill_paid_in_currency', ['currency' => $info['symbol']]), | ||||
|                 'monetary_value'          => $amount, | ||||
|                 'currency_id'             => (string) $info['id'], | ||||
|                 'currency_id'             => (string)$info['id'], | ||||
|                 'currency_code'           => $info['code'], | ||||
|                 'currency_symbol'         => $info['symbol'], | ||||
|                 'currency_decimal_places' => $info['decimal_places'], | ||||
| @@ -267,7 +250,7 @@ class BasicController extends Controller | ||||
|                 'key'                     => sprintf('bills-unpaid-in-%s', $info['code']), | ||||
|                 'title'                   => trans('firefly.box_bill_unpaid_in_currency', ['currency' => $info['symbol']]), | ||||
|                 'monetary_value'          => $amount, | ||||
|                 'currency_id'             => (string) $info['id'], | ||||
|                 'currency_id'             => (string)$info['id'], | ||||
|                 'currency_code'           => $info['code'], | ||||
|                 'currency_symbol'         => $info['symbol'], | ||||
|                 'currency_decimal_places' => $info['decimal_places'], | ||||
| @@ -294,21 +277,21 @@ class BasicController extends Controller | ||||
| 
 | ||||
|         foreach ($spent as $row) { | ||||
|             // either an amount was budgeted or 0 is available.
 | ||||
|             $amount          = (string) ($available[$row['currency_id']] ?? '0'); | ||||
|             $amount          = (string)($available[$row['currency_id']] ?? '0'); | ||||
|             $spentInCurrency = $row['sum']; | ||||
|             $leftToSpend     = bcadd($amount, $spentInCurrency); | ||||
| 
 | ||||
|             $days            = $today->diffInDays($end) + 1; | ||||
|             $perDay          = '0'; | ||||
|             if (0 !== $days && bccomp($leftToSpend, '0') > -1) { | ||||
|                 $perDay = bcdiv($leftToSpend, (string) $days); | ||||
|                 $perDay = bcdiv($leftToSpend, (string)$days); | ||||
|             } | ||||
| 
 | ||||
|             $return[]        = [ | ||||
|                 'key'                     => sprintf('left-to-spend-in-%s', $row['currency_code']), | ||||
|                 'title'                   => trans('firefly.box_left_to_spend_in_currency', ['currency' => $row['currency_symbol']]), | ||||
|                 'monetary_value'          => $leftToSpend, | ||||
|                 'currency_id'             => (string) $row['currency_id'], | ||||
|                 'currency_id'             => (string)$row['currency_id'], | ||||
|                 'currency_code'           => $row['currency_code'], | ||||
|                 'currency_symbol'         => $row['currency_symbol'], | ||||
|                 'currency_decimal_places' => $row['currency_decimal_places'], | ||||
| @@ -368,7 +351,7 @@ class BasicController extends Controller | ||||
|                 'key'                     => sprintf('net-worth-in-%s', $data['currency_code']), | ||||
|                 'title'                   => trans('firefly.box_net_worth_in_currency', ['currency' => $data['currency_symbol']]), | ||||
|                 'monetary_value'          => $amount, | ||||
|                 'currency_id'             => (string) $data['currency_id'], | ||||
|                 'currency_id'             => (string)$data['currency_id'], | ||||
|                 'currency_code'           => $data['currency_code'], | ||||
|                 'currency_symbol'         => $data['currency_symbol'], | ||||
|                 'currency_decimal_places' => $data['currency_decimal_places'], | ||||
| @@ -380,4 +363,21 @@ class BasicController extends Controller | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if date is outside session range. | ||||
|      */ | ||||
|     protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
 | ||||
|     { | ||||
|         $result = false; | ||||
|         if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
|         // start and end in the past? use $end
 | ||||
|         if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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')); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -83,7 +83,7 @@ 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')); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -73,6 +73,65 @@ class StoreRequest extends FormRequest | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the transaction data as it is found in the submitted data. It's a complex method according to code | ||||
|      * standards but it just has a lot of ??-statements because of the fields that may or may not exist. | ||||
|      */ | ||||
|     private function getTransactionData(): array | ||||
|     { | ||||
|         $return       = []; | ||||
| 
 | ||||
|         // transaction data:
 | ||||
|         /** @var null|array $transactions */ | ||||
|         $transactions = $this->get('transactions'); | ||||
|         if (null === $transactions) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         /** @var array $transaction */ | ||||
|         foreach ($transactions as $transaction) { | ||||
|             $return[] = $this->getSingleTransactionData($transaction); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the repetition data as it is found in the submitted data. | ||||
|      */ | ||||
|     private function getRepetitionData(): array | ||||
|     { | ||||
|         $return      = []; | ||||
| 
 | ||||
|         // repetition data:
 | ||||
|         /** @var null|array $repetitions */ | ||||
|         $repetitions = $this->get('repetitions'); | ||||
|         if (null === $repetitions) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         /** @var array $repetition */ | ||||
|         foreach ($repetitions as $repetition) { | ||||
|             $current  = []; | ||||
|             if (array_key_exists('type', $repetition)) { | ||||
|                 $current['type'] = $repetition['type']; | ||||
|             } | ||||
|             if (array_key_exists('moment', $repetition)) { | ||||
|                 $current['moment'] = $repetition['moment']; | ||||
|             } | ||||
|             if (array_key_exists('skip', $repetition)) { | ||||
|                 $current['skip'] = (int)$repetition['skip']; | ||||
|             } | ||||
|             if (array_key_exists('weekend', $repetition)) { | ||||
|                 $current['weekend'] = (int)$repetition['weekend']; | ||||
|             } | ||||
| 
 | ||||
|             $return[] = $current; | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The rules that the incoming request must be matched against. | ||||
|      */ | ||||
| @@ -136,63 +195,4 @@ class StoreRequest extends FormRequest | ||||
|             Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the transaction data as it is found in the submitted data. It's a complex method according to code | ||||
|      * standards but it just has a lot of ??-statements because of the fields that may or may not exist. | ||||
|      */ | ||||
|     private function getTransactionData(): array | ||||
|     { | ||||
|         $return       = []; | ||||
| 
 | ||||
|         // transaction data:
 | ||||
|         /** @var null|array $transactions */ | ||||
|         $transactions = $this->get('transactions'); | ||||
|         if (null === $transactions) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         /** @var array $transaction */ | ||||
|         foreach ($transactions as $transaction) { | ||||
|             $return[] = $this->getSingleTransactionData($transaction); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the repetition data as it is found in the submitted data. | ||||
|      */ | ||||
|     private function getRepetitionData(): array | ||||
|     { | ||||
|         $return      = []; | ||||
| 
 | ||||
|         // repetition data:
 | ||||
|         /** @var null|array $repetitions */ | ||||
|         $repetitions = $this->get('repetitions'); | ||||
|         if (null === $repetitions) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         /** @var array $repetition */ | ||||
|         foreach ($repetitions as $repetition) { | ||||
|             $current  = []; | ||||
|             if (array_key_exists('type', $repetition)) { | ||||
|                 $current['type'] = $repetition['type']; | ||||
|             } | ||||
|             if (array_key_exists('moment', $repetition)) { | ||||
|                 $current['moment'] = $repetition['moment']; | ||||
|             } | ||||
|             if (array_key_exists('skip', $repetition)) { | ||||
|                 $current['skip'] = (int)$repetition['skip']; | ||||
|             } | ||||
|             if (array_key_exists('weekend', $repetition)) { | ||||
|                 $current['weekend'] = (int)$repetition['weekend']; | ||||
|             } | ||||
| 
 | ||||
|             $return[] = $current; | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -78,6 +78,70 @@ class UpdateRequest extends FormRequest | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the repetition data as it is found in the submitted data. | ||||
|      */ | ||||
|     private function getRepetitionData(): ?array | ||||
|     { | ||||
|         $return      = []; | ||||
| 
 | ||||
|         // repetition data:
 | ||||
|         /** @var null|array $repetitions */ | ||||
|         $repetitions = $this->get('repetitions'); | ||||
|         if (null === $repetitions) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         /** @var array $repetition */ | ||||
|         foreach ($repetitions as $repetition) { | ||||
|             $current  = []; | ||||
|             if (array_key_exists('type', $repetition)) { | ||||
|                 $current['type'] = $repetition['type']; | ||||
|             } | ||||
| 
 | ||||
|             if (array_key_exists('moment', $repetition)) { | ||||
|                 $current['moment'] = (string)$repetition['moment']; | ||||
|             } | ||||
| 
 | ||||
|             if (array_key_exists('skip', $repetition)) { | ||||
|                 $current['skip'] = (int)$repetition['skip']; | ||||
|             } | ||||
| 
 | ||||
|             if (array_key_exists('weekend', $repetition)) { | ||||
|                 $current['weekend'] = (int)$repetition['weekend']; | ||||
|             } | ||||
|             $return[] = $current; | ||||
|         } | ||||
|         if (0 === count($return)) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the transaction data as it is found in the submitted data. It's a complex method according to code | ||||
|      * standards but it just has a lot of ??-statements because of the fields that may or may not exist. | ||||
|      */ | ||||
|     private function getTransactionData(): array | ||||
|     { | ||||
|         $return       = []; | ||||
| 
 | ||||
|         // transaction data:
 | ||||
|         /** @var null|array $transactions */ | ||||
|         $transactions = $this->get('transactions'); | ||||
|         if (null === $transactions) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         /** @var array $transaction */ | ||||
|         foreach ($transactions as $transaction) { | ||||
|             $return[] = $this->getSingleTransactionData($transaction); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The rules that the incoming request must be matched against. | ||||
|      */ | ||||
| @@ -146,68 +210,4 @@ class UpdateRequest extends FormRequest | ||||
|             Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the repetition data as it is found in the submitted data. | ||||
|      */ | ||||
|     private function getRepetitionData(): ?array | ||||
|     { | ||||
|         $return      = []; | ||||
| 
 | ||||
|         // repetition data:
 | ||||
|         /** @var null|array $repetitions */ | ||||
|         $repetitions = $this->get('repetitions'); | ||||
|         if (null === $repetitions) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         /** @var array $repetition */ | ||||
|         foreach ($repetitions as $repetition) { | ||||
|             $current  = []; | ||||
|             if (array_key_exists('type', $repetition)) { | ||||
|                 $current['type'] = $repetition['type']; | ||||
|             } | ||||
| 
 | ||||
|             if (array_key_exists('moment', $repetition)) { | ||||
|                 $current['moment'] = (string) $repetition['moment']; | ||||
|             } | ||||
| 
 | ||||
|             if (array_key_exists('skip', $repetition)) { | ||||
|                 $current['skip'] = (int) $repetition['skip']; | ||||
|             } | ||||
| 
 | ||||
|             if (array_key_exists('weekend', $repetition)) { | ||||
|                 $current['weekend'] = (int) $repetition['weekend']; | ||||
|             } | ||||
|             $return[] = $current; | ||||
|         } | ||||
|         if (0 === count($return)) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the transaction data as it is found in the submitted data. It's a complex method according to code | ||||
|      * standards but it just has a lot of ??-statements because of the fields that may or may not exist. | ||||
|      */ | ||||
|     private function getTransactionData(): array | ||||
|     { | ||||
|         $return       = []; | ||||
| 
 | ||||
|         // transaction data:
 | ||||
|         /** @var null|array $transactions */ | ||||
|         $transactions = $this->get('transactions'); | ||||
|         if (null === $transactions) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         /** @var array $transaction */ | ||||
|         foreach ($transactions as $transaction) { | ||||
|             $return[] = $this->getSingleTransactionData($transaction); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -64,6 +64,42 @@ class StoreRequest extends FormRequest | ||||
|         return $data; | ||||
|     } | ||||
| 
 | ||||
|     private function getRuleTriggers(): array | ||||
|     { | ||||
|         $triggers = $this->get('triggers'); | ||||
|         $return   = []; | ||||
|         if (is_array($triggers)) { | ||||
|             foreach ($triggers as $trigger) { | ||||
|                 $return[] = [ | ||||
|                     'type'            => $trigger['type'], | ||||
|                     'value'           => $trigger['value'], | ||||
|                     'active'          => $this->convertBoolean((string)($trigger['active'] ?? 'true')), | ||||
|                     'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')), | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     private function getRuleActions(): array | ||||
|     { | ||||
|         $actions = $this->get('actions'); | ||||
|         $return  = []; | ||||
|         if (is_array($actions)) { | ||||
|             foreach ($actions as $action) { | ||||
|                 $return[] = [ | ||||
|                     'type'            => $action['type'], | ||||
|                     'value'           => $action['value'], | ||||
|                     'active'          => $this->convertBoolean((string)($action['active'] ?? 'true')), | ||||
|                     'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')), | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The rules that the incoming request must be matched against. | ||||
|      */ | ||||
| @@ -197,40 +233,4 @@ class StoreRequest extends FormRequest | ||||
|             $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action')); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function getRuleTriggers(): array | ||||
|     { | ||||
|         $triggers = $this->get('triggers'); | ||||
|         $return   = []; | ||||
|         if (is_array($triggers)) { | ||||
|             foreach ($triggers as $trigger) { | ||||
|                 $return[] = [ | ||||
|                     'type'            => $trigger['type'], | ||||
|                     'value'           => $trigger['value'], | ||||
|                     'active'          => $this->convertBoolean((string)($trigger['active'] ?? 'true')), | ||||
|                     'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')), | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     private function getRuleActions(): array | ||||
|     { | ||||
|         $actions = $this->get('actions'); | ||||
|         $return  = []; | ||||
|         if (is_array($actions)) { | ||||
|             foreach ($actions as $action) { | ||||
|                 $return[] = [ | ||||
|                     'type'            => $action['type'], | ||||
|                     'value'           => $action['value'], | ||||
|                     'active'          => $this->convertBoolean((string)($action['active'] ?? 'true')), | ||||
|                     'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')), | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -47,16 +47,6 @@ class TestRequest extends FormRequest | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     private function getPage(): int | ||||
|     { | ||||
|         return 0 === (int)$this->query('page') ? 1 : (int)$this->query('page'); | ||||
| @@ -81,4 +71,14 @@ class TestRequest extends FormRequest | ||||
|     { | ||||
|         return $this->get('accounts'); | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -46,16 +46,6 @@ class TriggerRequest extends FormRequest | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     private function getDate(string $field): ?Carbon | ||||
|     { | ||||
|         $value  = $this->query($field); | ||||
| @@ -75,4 +65,14 @@ class TriggerRequest extends FormRequest | ||||
|     { | ||||
|         return $this->get('accounts') ?? []; | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -70,6 +70,50 @@ class UpdateRequest extends FormRequest | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     private function getRuleTriggers(): ?array | ||||
|     { | ||||
|         if (!$this->has('triggers')) { | ||||
|             return null; | ||||
|         } | ||||
|         $triggers = $this->get('triggers'); | ||||
|         $return   = []; | ||||
|         if (is_array($triggers)) { | ||||
|             foreach ($triggers as $trigger) { | ||||
|                 $active         = array_key_exists('active', $trigger) ? $trigger['active'] : true; | ||||
|                 $stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false; | ||||
|                 $return[]       = [ | ||||
|                     'type'            => $trigger['type'], | ||||
|                     'value'           => $trigger['value'], | ||||
|                     'active'          => $active, | ||||
|                     'stop_processing' => $stopProcessing, | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     private function getRuleActions(): ?array | ||||
|     { | ||||
|         if (!$this->has('actions')) { | ||||
|             return null; | ||||
|         } | ||||
|         $actions = $this->get('actions'); | ||||
|         $return  = []; | ||||
|         if (is_array($actions)) { | ||||
|             foreach ($actions as $action) { | ||||
|                 $return[] = [ | ||||
|                     'type'            => $action['type'], | ||||
|                     'value'           => $action['value'], | ||||
|                     'active'          => $this->convertBoolean((string)($action['active'] ?? 'false')), | ||||
|                     'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')), | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * The rules that the incoming request must be matched against. | ||||
|      */ | ||||
| @@ -204,48 +248,4 @@ class UpdateRequest extends FormRequest | ||||
|             $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string)trans('validation.at_least_one_active_action')); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function getRuleTriggers(): ?array | ||||
|     { | ||||
|         if (!$this->has('triggers')) { | ||||
|             return null; | ||||
|         } | ||||
|         $triggers = $this->get('triggers'); | ||||
|         $return   = []; | ||||
|         if (is_array($triggers)) { | ||||
|             foreach ($triggers as $trigger) { | ||||
|                 $active         = array_key_exists('active', $trigger) ? $trigger['active'] : true; | ||||
|                 $stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false; | ||||
|                 $return[]       = [ | ||||
|                     'type'            => $trigger['type'], | ||||
|                     'value'           => $trigger['value'], | ||||
|                     'active'          => $active, | ||||
|                     'stop_processing' => $stopProcessing, | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     private function getRuleActions(): ?array | ||||
|     { | ||||
|         if (!$this->has('actions')) { | ||||
|             return null; | ||||
|         } | ||||
|         $actions = $this->get('actions'); | ||||
|         $return  = []; | ||||
|         if (is_array($actions)) { | ||||
|             foreach ($actions as $action) { | ||||
|                 $return[] = [ | ||||
|                     'type'            => $action['type'], | ||||
|                     'value'           => $action['value'], | ||||
|                     'active'          => $this->convertBoolean((string)($action['active'] ?? 'false')), | ||||
|                     'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')), | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -46,16 +46,6 @@ class TestRequest extends FormRequest | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     private function getDate(string $field): ?Carbon | ||||
|     { | ||||
|         $value  = $this->query($field); | ||||
| @@ -75,4 +65,14 @@ class TestRequest extends FormRequest | ||||
|     { | ||||
|         return $this->get('accounts'); | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start'      => 'date', | ||||
|             'end'        => 'date|after_or_equal:start', | ||||
|             'accounts'   => '', | ||||
|             'accounts.*' => 'exists:accounts,id|belongsToUser:accounts', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -46,14 +46,6 @@ class TriggerRequest extends FormRequest | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start' => 'date', | ||||
|             'end'   => 'date|after_or_equal:start', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     private function getDate(string $field): ?Carbon | ||||
|     { | ||||
|         $value  = $this->query($field); | ||||
| @@ -77,4 +69,12 @@ class TriggerRequest extends FormRequest | ||||
| 
 | ||||
|         return $this->get('accounts'); | ||||
|     } | ||||
| 
 | ||||
|     public function rules(): array | ||||
|     { | ||||
|         return [ | ||||
|             'start' => 'date', | ||||
|             'end'   => 'date|after_or_equal:start', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -69,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()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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,41 @@ class Controller extends BaseController | ||||
| 
 | ||||
|         return $bag; | ||||
|     } | ||||
| 
 | ||||
|     final protected function jsonApiList(string $key, LengthAwarePaginator $paginator, AbstractTransformer $transformer): array | ||||
|     { | ||||
|         $manager  = new Manager(); | ||||
|         $baseUrl  = request()->getSchemeAndHttpHost().'/api/v2'; | ||||
|         $manager->setSerializer(new JsonApiSerializer($baseUrl)); | ||||
| 
 | ||||
|         $objects  = $paginator->getCollection(); | ||||
| 
 | ||||
|         // the transformer, at this point, needs to collect information that ALL items in the collection
 | ||||
|         // require, like meta-data and stuff like that, and save it for later.
 | ||||
|         $transformer->collectMetaData($objects); | ||||
| 
 | ||||
|         $resource = new FractalCollection($objects, $transformer, $key); | ||||
|         $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); | ||||
| 
 | ||||
|         return $manager->createData($resource)->toArray(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns a JSON API object and returns it. | ||||
|      * | ||||
|      * @param array<int, mixed>|Model $object | ||||
|      */ | ||||
|     final protected function jsonApiObject(string $key, array|Model $object, AbstractTransformer $transformer): array | ||||
|     { | ||||
|         // create some objects:
 | ||||
|         $manager  = new Manager(); | ||||
|         $baseUrl  = request()->getSchemeAndHttpHost().'/api/v2'; | ||||
|         $manager->setSerializer(new JsonApiSerializer($baseUrl)); | ||||
| 
 | ||||
|         $transformer->collectMetaData(new Collection([$object])); | ||||
| 
 | ||||
|         $resource = new Item($object, $transformer, $key); | ||||
| 
 | ||||
|         return $manager->createData($resource)->toArray(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -51,20 +51,6 @@ class ShowController extends Controller | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show a budget. | ||||
|      */ | ||||
|     public function show(Budget $budget): JsonResponse | ||||
|     { | ||||
|         $transformer = new BudgetTransformer(); | ||||
|         $transformer->setParameters($this->parameters); | ||||
| 
 | ||||
|         return response() | ||||
|             ->api($this->jsonApiObject('budgets', $budget, $transformer)) | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 2023-10-29 removed the cerSum reference, not sure where this is used atm | ||||
|      * so removed from api.php. Also applies to "spent" method. | ||||
| @@ -80,6 +66,20 @@ class ShowController extends Controller | ||||
|         return response()->json($result); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show a budget. | ||||
|      */ | ||||
|     public function show(Budget $budget): JsonResponse | ||||
|     { | ||||
|         $transformer = new BudgetTransformer(); | ||||
|         $transformer->setParameters($this->parameters); | ||||
| 
 | ||||
|         return response() | ||||
|             ->api($this->jsonApiObject('budgets', $budget, $transformer)) | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This endpoint is documented at: | ||||
|      * TODO add URL | ||||
|   | ||||
| @@ -114,23 +114,6 @@ class BasicController extends Controller | ||||
|         return response()->json($total); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if date is outside session range. | ||||
|      */ | ||||
|     protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
 | ||||
|     { | ||||
|         $result = false; | ||||
|         if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
|         // start and end in the past? use $end
 | ||||
|         if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
| @@ -411,4 +394,21 @@ class BasicController extends Controller | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if date is outside session range. | ||||
|      */ | ||||
|     protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
 | ||||
|     { | ||||
|         $result = false; | ||||
|         if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
|         // start and end in the past? use $end
 | ||||
|         if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) { | ||||
|             $result = true; | ||||
|         } | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,7 +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\ListByCountRequest; | ||||
| 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; | ||||
| @@ -35,43 +35,6 @@ use Illuminate\Http\JsonResponse; | ||||
|  */ | ||||
| class TransactionController extends Controller | ||||
| { | ||||
|     public function listByCount(ListByCountRequest $request): JsonResponse | ||||
|     { | ||||
|         // collect transactions:
 | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector = app(GroupCollectorInterface::class); | ||||
|         $collector->setUserGroup(auth()->user()->userGroup) | ||||
|             ->withAPIInformation() | ||||
|             ->setStartRow($request->getStartRow()) | ||||
|             ->setEndRow($request->getEndRow()) | ||||
|             ->setTypes($request->getTransactionTypes()) | ||||
|         ; | ||||
| 
 | ||||
|         $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.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:
 | ||||
| @@ -97,9 +60,6 @@ class TransactionController extends Controller | ||||
|             $collector->setEnd($end); | ||||
|         } | ||||
| 
 | ||||
|         //        $collector->dumpQuery();
 | ||||
|         //        exit;
 | ||||
| 
 | ||||
|         $paginator = $collector->getPaginatedGroups(); | ||||
|         $params    = $request->buildParams($pageSize); | ||||
|         $paginator->setPath( | ||||
| @@ -115,4 +75,45 @@ class TransactionController extends Controller | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     public function infiniteList(InfiniteListRequest $request): JsonResponse | ||||
|     { | ||||
|         // get sort instructions
 | ||||
|         $instructions = $request->getSortInstructions(); | ||||
| 
 | ||||
|         // collect transactions:
 | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector    = app(GroupCollectorInterface::class); | ||||
|         $collector->setUserGroup(auth()->user()->userGroup) | ||||
|             ->withAPIInformation() | ||||
|             ->setStartRow($request->getStartRow()) | ||||
|             ->setEndRow($request->getEndRow()) | ||||
|             ->setTypes($request->getTransactionTypes()) | ||||
|             ->setSorting($instructions) | ||||
|         ; | ||||
| 
 | ||||
|         $start        = $this->parameters->get('start'); | ||||
|         $end          = $this->parameters->get('end'); | ||||
|         if (null !== $start) { | ||||
|             $collector->setStart($start); | ||||
|         } | ||||
|         if (null !== $end) { | ||||
|             $collector->setEnd($end); | ||||
|         } | ||||
| 
 | ||||
|         $paginator    = $collector->getPaginatedGroups(); | ||||
|         $params       = $request->buildParams(); | ||||
|         $paginator->setPath( | ||||
|             sprintf( | ||||
|                 '%s?%s', | ||||
|                 route('api.v2.infinite.transactions.list'), | ||||
|                 $params | ||||
|             ) | ||||
|         ); | ||||
| 
 | ||||
|         return response() | ||||
|             ->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer())) | ||||
|             ->header('Content-Type', self::CONTENT_TYPE) | ||||
|         ; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -31,10 +31,10 @@ use FireflyIII\Support\Request\ConvertsDataTypes; | ||||
| use Illuminate\Foundation\Http\FormRequest; | ||||
| 
 | ||||
| /** | ||||
|  * Class ListRequest | ||||
|  * Class InfiniteListRequest | ||||
|  * Used specifically to list transactions. | ||||
|  */ | ||||
| class ListByCountRequest extends FormRequest | ||||
| class InfiniteListRequest extends FormRequest | ||||
| { | ||||
|     use ChecksLogin; | ||||
|     use ConvertsDataTypes; | ||||
| @@ -57,13 +57,6 @@ class ListByCountRequest extends FormRequest | ||||
|         return http_build_query($array); | ||||
|     } | ||||
| 
 | ||||
|     public function getPage(): int | ||||
|     { | ||||
|         $page = $this->convertInteger('page'); | ||||
| 
 | ||||
|         return 0 === $page || $page > 65536 ? 1 : $page; | ||||
|     } | ||||
| 
 | ||||
|     public function getStartRow(): int | ||||
|     { | ||||
|         $startRow = $this->convertInteger('start_row'); | ||||
| @@ -88,6 +81,38 @@ class ListByCountRequest extends FormRequest | ||||
|         return $this->getCarbonDate('end'); | ||||
|     } | ||||
| 
 | ||||
|     public function getPage(): int | ||||
|     { | ||||
|         $page = $this->convertInteger('page'); | ||||
| 
 | ||||
|         return 0 === $page || $page > 65536 ? 1 : $page; | ||||
|     } | ||||
| 
 | ||||
|     public function getSortInstructions(): array | ||||
|     { | ||||
|         $allowed = config('firefly.sorting.allowed.transactions'); | ||||
|         $set     = $this->get('sorting', []); | ||||
|         $result  = []; | ||||
|         if (0 === count($set)) { | ||||
|             return []; | ||||
|         } | ||||
|         foreach ($set as $info) { | ||||
|             $column          = $info['column'] ?? 'NOPE'; | ||||
|             $direction       = $info['direction'] ?? 'NOPE'; | ||||
|             if ('asc' !== $direction && 'desc' !== $direction) { | ||||
|                 // skip invalid direction
 | ||||
|                 continue; | ||||
|             } | ||||
|             if (false === in_array($column, $allowed, true)) { | ||||
|                 // skip invalid column
 | ||||
|                 continue; | ||||
|             } | ||||
|             $result[$column] = $direction; | ||||
|         } | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     public function getTransactionTypes(): array | ||||
|     { | ||||
|         $type = (string)$this->get('type', 'default'); | ||||
| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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()'); | ||||
|   | ||||
| @@ -117,6 +117,7 @@ class GracefulNotFoundHandler extends ExceptionHandler | ||||
|                 return redirect(route('tags.index')); | ||||
| 
 | ||||
|             case 'categories.show': | ||||
|             case 'categories.edit': | ||||
|             case 'categories.show.all': | ||||
|                 $request->session()->reflash(); | ||||
| 
 | ||||
|   | ||||
| @@ -128,7 +128,7 @@ class Handler extends ExceptionHandler | ||||
|             $errorCode = 500; | ||||
|             $errorCode = $e instanceof MethodNotAllowedHttpException ? 405 : $errorCode; | ||||
| 
 | ||||
|             $isDebug   = (bool) config('app.debug', false); | ||||
|             $isDebug   = (bool)config('app.debug', false); | ||||
|             if ($isDebug) { | ||||
|                 app('log')->debug(sprintf('Return JSON %s with debug.', get_class($e))); | ||||
| 
 | ||||
| @@ -185,7 +185,7 @@ class Handler extends ExceptionHandler | ||||
|      */ | ||||
|     public function report(\Throwable $e): void | ||||
|     { | ||||
|         $doMailError = (bool) config('firefly.send_error_message'); | ||||
|         $doMailError = (bool)config('firefly.send_error_message'); | ||||
|         if ($this->shouldntReportLocal($e) || !$doMailError) { | ||||
|             parent::report($e); | ||||
| 
 | ||||
| @@ -221,12 +221,22 @@ class Handler extends ExceptionHandler | ||||
| 
 | ||||
|         // create job that will mail.
 | ||||
|         $ipAddress   = request()->ip() ?? '0.0.0.0'; | ||||
|         $job         = new MailError($userData, (string) config('firefly.site_owner'), $ipAddress, $data); | ||||
|         $job         = new MailError($userData, (string)config('firefly.site_owner'), $ipAddress, $data); | ||||
|         dispatch($job); | ||||
| 
 | ||||
|         parent::report($e); | ||||
|     } | ||||
| 
 | ||||
|     private function shouldntReportLocal(\Throwable $e): bool | ||||
|     { | ||||
|         return null !== Arr::first( | ||||
|             $this->dontReport, | ||||
|             static function ($type) use ($e) { | ||||
|                 return $e instanceof $type; | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convert a validation exception into a response. | ||||
|      * | ||||
| @@ -244,16 +254,6 @@ class Handler extends ExceptionHandler | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     private function shouldntReportLocal(\Throwable $e): bool | ||||
|     { | ||||
|         return null !== Arr::first( | ||||
|             $this->dontReport, | ||||
|             static function ($type) use ($e) { | ||||
|                 return $e instanceof $type; | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Only return the redirectTo property from the exception if it is a valid URL. Return NULL otherwise. | ||||
|      */ | ||||
|   | ||||
| @@ -48,7 +48,7 @@ final class IntervalException extends \Exception | ||||
|         Periodicity $periodicity, | ||||
|         array       $intervals, | ||||
|         int         $code = 0, | ||||
|         ?\Throwable  $previous = null | ||||
|         ?\Throwable $previous = null | ||||
|     ): self { | ||||
|         $message                       = sprintf( | ||||
|             'The periodicity %s is unknown. Choose one of available periodicity: %s', | ||||
|   | ||||
| @@ -121,21 +121,6 @@ class AccountFactory | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     public function find(string $accountName, string $accountType): ?Account | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now in AccountFactory::find("%s", "%s")', $accountName, $accountType)); | ||||
|         $type = AccountType::whereType($accountType)->first(); | ||||
| 
 | ||||
|         // @var Account|null
 | ||||
|         return $this->user->accounts()->where('account_type_id', $type->id)->where('name', $accountName)->first(); | ||||
|     } | ||||
| 
 | ||||
|     public function setUser(User $user): void | ||||
|     { | ||||
|         $this->user = $user; | ||||
|         $this->accountRepository->setUser($user); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
| @@ -169,6 +154,15 @@ class AccountFactory | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     public function find(string $accountName, string $accountType): ?Account | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now in AccountFactory::find("%s", "%s")', $accountName, $accountType)); | ||||
|         $type = AccountType::whereType($accountType)->first(); | ||||
| 
 | ||||
|         // @var Account|null
 | ||||
|         return $this->user->accounts()->where('account_type_id', $type->id)->where('name', $accountName)->first(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
| @@ -365,4 +359,10 @@ class AccountFactory | ||||
|         $updateService->setUser($account->user); | ||||
|         $updateService->update($account, ['order' => $order]); | ||||
|     } | ||||
| 
 | ||||
|     public function setUser(User $user): void | ||||
|     { | ||||
|         $this->user = $user; | ||||
|         $this->accountRepository->setUser($user); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -79,7 +79,7 @@ class RecurrenceFactory | ||||
|             $firstDate = $data['recurrence']['first_date']; | ||||
|         } | ||||
|         if (array_key_exists('nr_of_repetitions', $data['recurrence'])) { | ||||
|             $repetitions = (int) $data['recurrence']['nr_of_repetitions']; | ||||
|             $repetitions = (int)$data['recurrence']['nr_of_repetitions']; | ||||
|         } | ||||
|         if (array_key_exists('repeat_until', $data['recurrence'])) { | ||||
|             $repeatUntil = $data['recurrence']['repeat_until']; | ||||
| @@ -116,7 +116,7 @@ class RecurrenceFactory | ||||
|         $recurrence->save(); | ||||
| 
 | ||||
|         if (array_key_exists('notes', $data['recurrence'])) { | ||||
|             $this->updateNote($recurrence, (string) $data['recurrence']['notes']); | ||||
|             $this->updateNote($recurrence, (string)$data['recurrence']['notes']); | ||||
|         } | ||||
| 
 | ||||
|         $this->createRepetitions($recurrence, $data['repetitions'] ?? []); | ||||
|   | ||||
| @@ -72,64 +72,6 @@ class TransactionFactory | ||||
|         return $this->create(app('steam')->negative($amount), $foreignAmount); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create transaction with positive amount (for destination accounts). | ||||
|      * | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     public function createPositive(string $amount, ?string $foreignAmount): Transaction | ||||
|     { | ||||
|         if ('' === $foreignAmount) { | ||||
|             $foreignAmount = null; | ||||
|         } | ||||
|         if (null !== $foreignAmount) { | ||||
|             $foreignAmount = app('steam')->positive($foreignAmount); | ||||
|         } | ||||
| 
 | ||||
|         return $this->create(app('steam')->positive($amount), $foreignAmount); | ||||
|     } | ||||
| 
 | ||||
|     public function setAccount(Account $account): void | ||||
|     { | ||||
|         $this->account = $account; | ||||
|     } | ||||
| 
 | ||||
|     public function setAccountInformation(array $accountInformation): void | ||||
|     { | ||||
|         $this->accountInformation = $accountInformation; | ||||
|     } | ||||
| 
 | ||||
|     public function setCurrency(TransactionCurrency $currency): void | ||||
|     { | ||||
|         $this->currency = $currency; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param null|TransactionCurrency $foreignCurrency |null | ||||
|      */ | ||||
|     public function setForeignCurrency(?TransactionCurrency $foreignCurrency): void | ||||
|     { | ||||
|         $this->foreignCurrency = $foreignCurrency; | ||||
|     } | ||||
| 
 | ||||
|     public function setJournal(TransactionJournal $journal): void | ||||
|     { | ||||
|         $this->journal = $journal; | ||||
|     } | ||||
| 
 | ||||
|     public function setReconciled(bool $reconciled): void | ||||
|     { | ||||
|         $this->reconciled = $reconciled; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function setUser(User $user): void | ||||
|     { | ||||
|         // empty function.
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
| @@ -224,4 +166,62 @@ class TransactionFactory | ||||
|         $service   = app(AccountUpdateService::class); | ||||
|         $service->update($this->account, ['iban' => $this->accountInformation['iban']]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create transaction with positive amount (for destination accounts). | ||||
|      * | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     public function createPositive(string $amount, ?string $foreignAmount): Transaction | ||||
|     { | ||||
|         if ('' === $foreignAmount) { | ||||
|             $foreignAmount = null; | ||||
|         } | ||||
|         if (null !== $foreignAmount) { | ||||
|             $foreignAmount = app('steam')->positive($foreignAmount); | ||||
|         } | ||||
| 
 | ||||
|         return $this->create(app('steam')->positive($amount), $foreignAmount); | ||||
|     } | ||||
| 
 | ||||
|     public function setAccount(Account $account): void | ||||
|     { | ||||
|         $this->account = $account; | ||||
|     } | ||||
| 
 | ||||
|     public function setAccountInformation(array $accountInformation): void | ||||
|     { | ||||
|         $this->accountInformation = $accountInformation; | ||||
|     } | ||||
| 
 | ||||
|     public function setCurrency(TransactionCurrency $currency): void | ||||
|     { | ||||
|         $this->currency = $currency; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param null|TransactionCurrency $foreignCurrency |null | ||||
|      */ | ||||
|     public function setForeignCurrency(?TransactionCurrency $foreignCurrency): void | ||||
|     { | ||||
|         $this->foreignCurrency = $foreignCurrency; | ||||
|     } | ||||
| 
 | ||||
|     public function setJournal(TransactionJournal $journal): void | ||||
|     { | ||||
|         $this->journal = $journal; | ||||
|     } | ||||
| 
 | ||||
|     public function setReconciled(bool $reconciled): void | ||||
|     { | ||||
|         $this->reconciled = $reconciled; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      */ | ||||
|     public function setUser(User $user): void | ||||
|     { | ||||
|         // empty function.
 | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -141,49 +141,6 @@ class TransactionJournalFactory | ||||
|         return $collection; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the user. | ||||
|      */ | ||||
|     public function setUser(User $user): void | ||||
|     { | ||||
|         $this->user = $user; | ||||
|         $this->currencyRepository->setUser($this->user); | ||||
|         $this->tagFactory->setUser($user); | ||||
|         $this->billRepository->setUser($this->user); | ||||
|         $this->budgetRepository->setUser($this->user); | ||||
|         $this->categoryRepository->setUser($this->user); | ||||
|         $this->piggyRepository->setUser($this->user); | ||||
|         $this->accountRepository->setUser($this->user); | ||||
|     } | ||||
| 
 | ||||
|     public function setErrorOnHash(bool $errorOnHash): void | ||||
|     { | ||||
|         $this->errorOnHash = $errorOnHash; | ||||
|         if (true === $errorOnHash) { | ||||
|             app('log')->info('Will trigger duplication alert for this journal.'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void | ||||
|     { | ||||
|         $set     = [ | ||||
|             'journal' => $journal, | ||||
|             'name'    => $field, | ||||
|             'data'    => (string) ($data[$field] ?? ''), | ||||
|         ]; | ||||
|         if ($data[$field] instanceof Carbon) { | ||||
|             $data[$field]->setTimezone(config('app.timezone')); | ||||
|             app('log')->debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName())); | ||||
|             $set['data'] = $data[$field]->format('Y-m-d H:i:s'); | ||||
|         } | ||||
| 
 | ||||
|         app('log')->debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data'])); | ||||
| 
 | ||||
|         /** @var TransactionJournalMetaFactory $factory */ | ||||
|         $factory = app(TransactionJournalMetaFactory::class); | ||||
|         $factory->updateOrCreate($set); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO typeOverrule: the account validator may have another opinion on the transaction type. not sure what to do | ||||
|      * with this. | ||||
| @@ -203,11 +160,11 @@ class TransactionJournalFactory | ||||
|         $type                  = $this->typeRepository->findTransactionType(null, $row['type']); | ||||
|         $carbon                = $row['date'] ?? today(config('app.timezone')); | ||||
|         $order                 = $row['order'] ?? 0; | ||||
|         $currency              = $this->currencyRepository->findCurrency((int) $row['currency_id'], $row['currency_code']); | ||||
|         $currency              = $this->currencyRepository->findCurrency((int)$row['currency_id'], $row['currency_code']); | ||||
|         $foreignCurrency       = $this->currencyRepository->findCurrencyNull($row['foreign_currency_id'], $row['foreign_currency_code']); | ||||
|         $bill                  = $this->billRepository->findBill((int) $row['bill_id'], $row['bill_name']); | ||||
|         $bill                  = $this->billRepository->findBill((int)$row['bill_id'], $row['bill_name']); | ||||
|         $billId                = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null; | ||||
|         $description           = (string) $row['description']; | ||||
|         $description           = (string)$row['description']; | ||||
| 
 | ||||
|         // Manipulate basic fields
 | ||||
|         $carbon->setTimezone(config('app.timezone')); | ||||
| @@ -286,7 +243,7 @@ class TransactionJournalFactory | ||||
|         $transactionFactory->setReconciled($row['reconciled'] ?? false); | ||||
| 
 | ||||
|         try { | ||||
|             $negative = $transactionFactory->createNegative((string) $row['amount'], (string) $row['foreign_amount']); | ||||
|             $negative = $transactionFactory->createNegative((string)$row['amount'], (string)$row['foreign_amount']); | ||||
|         } catch (FireflyException $e) { | ||||
|             app('log')->error(sprintf('Exception creating negative transaction: %s', $e->getMessage())); | ||||
|             $this->forceDeleteOnError(new Collection([$journal])); | ||||
| @@ -305,7 +262,7 @@ class TransactionJournalFactory | ||||
|         $transactionFactory->setReconciled($row['reconciled'] ?? false); | ||||
| 
 | ||||
|         try { | ||||
|             $transactionFactory->createPositive((string) $row['amount'], (string) $row['foreign_amount']); | ||||
|             $transactionFactory->createPositive((string)$row['amount'], (string)$row['foreign_amount']); | ||||
|         } catch (FireflyException $e) { | ||||
|             app('log')->error(sprintf('Exception creating positive transaction: %s', $e->getMessage())); | ||||
|             $this->forceTrDelete($negative); | ||||
| @@ -326,18 +283,6 @@ class TransactionJournalFactory | ||||
|         return $journal; | ||||
|     } | ||||
| 
 | ||||
|     private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void | ||||
|     { | ||||
|         if (true === $data['store_location']) { | ||||
|             $location             = new Location(); | ||||
|             $location->longitude  = $data['longitude']; | ||||
|             $location->latitude   = $data['latitude']; | ||||
|             $location->zoom_level = $data['zoom_level']; | ||||
|             $location->locatable()->associate($journal); | ||||
|             $location->save(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function hashArray(NullArrayObject $row): string | ||||
|     { | ||||
|         $dataRow = $row->getArrayCopy(); | ||||
| @@ -382,7 +327,7 @@ class TransactionJournalFactory | ||||
|             app('log')->warning(sprintf('Found a duplicate in errorIfDuplicate because hash %s is not unique!', $hash)); | ||||
|             $journal = $result->transactionJournal()->withTrashed()->first(); | ||||
|             $group   = $journal?->transactionGroup()->withTrashed()->first(); | ||||
|             $groupId = (int) $group?->id; | ||||
|             $groupId = (int)$group?->id; | ||||
| 
 | ||||
|             throw new DuplicateTransactionException(sprintf('Duplicate of transaction #%d.', $groupId)); | ||||
|         } | ||||
| @@ -400,10 +345,10 @@ class TransactionJournalFactory | ||||
| 
 | ||||
|         // validate source account.
 | ||||
|         $array            = [ | ||||
|             'id'     => null !== $data['source_id'] ? (int) $data['source_id'] : null, | ||||
|             'name'   => null !== $data['source_name'] ? (string) $data['source_name'] : null, | ||||
|             'iban'   => null !== $data['source_iban'] ? (string) $data['source_iban'] : null, | ||||
|             'number' => null !== $data['source_number'] ? (string) $data['source_number'] : null, | ||||
|             'id'     => null !== $data['source_id'] ? (int)$data['source_id'] : null, | ||||
|             'name'   => null !== $data['source_name'] ? (string)$data['source_name'] : null, | ||||
|             'iban'   => null !== $data['source_iban'] ? (string)$data['source_iban'] : null, | ||||
|             'number' => null !== $data['source_number'] ? (string)$data['source_number'] : null, | ||||
|         ]; | ||||
|         $validSource      = $this->accountValidator->validateSource($array); | ||||
| 
 | ||||
| @@ -415,10 +360,10 @@ class TransactionJournalFactory | ||||
| 
 | ||||
|         // validate destination account
 | ||||
|         $array            = [ | ||||
|             'id'     => null !== $data['destination_id'] ? (int) $data['destination_id'] : null, | ||||
|             'name'   => null !== $data['destination_name'] ? (string) $data['destination_name'] : null, | ||||
|             'iban'   => null !== $data['destination_iban'] ? (string) $data['destination_iban'] : null, | ||||
|             'number' => null !== $data['destination_number'] ? (string) $data['destination_number'] : null, | ||||
|             'id'     => null !== $data['destination_id'] ? (int)$data['destination_id'] : null, | ||||
|             'name'   => null !== $data['destination_name'] ? (string)$data['destination_name'] : null, | ||||
|             'iban'   => null !== $data['destination_iban'] ? (string)$data['destination_iban'] : null, | ||||
|             'number' => null !== $data['destination_number'] ? (string)$data['destination_number'] : null, | ||||
|         ]; | ||||
| 
 | ||||
|         $validDestination = $this->accountValidator->validateDestination($array); | ||||
| @@ -428,6 +373,21 @@ class TransactionJournalFactory | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the user. | ||||
|      */ | ||||
|     public function setUser(User $user): void | ||||
|     { | ||||
|         $this->user = $user; | ||||
|         $this->currencyRepository->setUser($this->user); | ||||
|         $this->tagFactory->setUser($user); | ||||
|         $this->billRepository->setUser($this->user); | ||||
|         $this->budgetRepository->setUser($this->user); | ||||
|         $this->categoryRepository->setUser($this->user); | ||||
|         $this->piggyRepository->setUser($this->user); | ||||
|         $this->accountRepository->setUser($this->user); | ||||
|     } | ||||
| 
 | ||||
|     private function reconciliationSanityCheck(?Account $sourceAccount, ?Account $destinationAccount): array | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now in %s', __METHOD__)); | ||||
| @@ -550,7 +510,7 @@ class TransactionJournalFactory | ||||
|     { | ||||
|         app('log')->debug('Will now store piggy event.'); | ||||
| 
 | ||||
|         $piggyBank = $this->piggyRepository->findPiggyBank((int) $data['piggy_bank_id'], $data['piggy_bank_name']); | ||||
|         $piggyBank = $this->piggyRepository->findPiggyBank((int)$data['piggy_bank_id'], $data['piggy_bank_name']); | ||||
| 
 | ||||
|         if (null !== $piggyBank) { | ||||
|             $this->piggyEventFactory->create($journal, $piggyBank); | ||||
| @@ -567,4 +527,44 @@ class TransactionJournalFactory | ||||
|             $this->storeMeta($journal, $transaction, $field); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void | ||||
|     { | ||||
|         $set     = [ | ||||
|             'journal' => $journal, | ||||
|             'name'    => $field, | ||||
|             'data'    => (string)($data[$field] ?? ''), | ||||
|         ]; | ||||
|         if ($data[$field] instanceof Carbon) { | ||||
|             $data[$field]->setTimezone(config('app.timezone')); | ||||
|             app('log')->debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName())); | ||||
|             $set['data'] = $data[$field]->format('Y-m-d H:i:s'); | ||||
|         } | ||||
| 
 | ||||
|         app('log')->debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data'])); | ||||
| 
 | ||||
|         /** @var TransactionJournalMetaFactory $factory */ | ||||
|         $factory = app(TransactionJournalMetaFactory::class); | ||||
|         $factory->updateOrCreate($set); | ||||
|     } | ||||
| 
 | ||||
|     private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void | ||||
|     { | ||||
|         if (true === $data['store_location']) { | ||||
|             $location             = new Location(); | ||||
|             $location->longitude  = $data['longitude']; | ||||
|             $location->latitude   = $data['latitude']; | ||||
|             $location->zoom_level = $data['zoom_level']; | ||||
|             $location->locatable()->associate($journal); | ||||
|             $location->save(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function setErrorOnHash(bool $errorOnHash): void | ||||
|     { | ||||
|         $this->errorOnHash = $errorOnHash; | ||||
|         if (true === $errorOnHash) { | ||||
|             app('log')->info('Will trigger duplication alert for this journal.'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -67,6 +67,14 @@ class MonthReportGenerator implements ReportGeneratorInterface | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Return the preferred period. | ||||
|      */ | ||||
|     protected function preferredPeriod(): string | ||||
|     { | ||||
|         return 'day'; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set accounts. | ||||
|      */ | ||||
| @@ -130,12 +138,4 @@ class MonthReportGenerator implements ReportGeneratorInterface | ||||
|     { | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Return the preferred period. | ||||
|      */ | ||||
|     protected function preferredPeriod(): string | ||||
|     { | ||||
|         return 'day'; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -125,26 +125,6 @@ class MonthReportGenerator implements ReportGeneratorInterface | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the involved budgets. | ||||
|      */ | ||||
|     public function setBudgets(Collection $budgets): ReportGeneratorInterface | ||||
|     { | ||||
|         $this->budgets = $budgets; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the involved accounts. | ||||
|      */ | ||||
|     public function setAccounts(Collection $accounts): ReportGeneratorInterface | ||||
|     { | ||||
|         $this->accounts = $accounts; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the expenses. | ||||
|      */ | ||||
| @@ -170,4 +150,24 @@ class MonthReportGenerator implements ReportGeneratorInterface | ||||
| 
 | ||||
|         return $journals; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the involved budgets. | ||||
|      */ | ||||
|     public function setBudgets(Collection $budgets): ReportGeneratorInterface | ||||
|     { | ||||
|         $this->budgets = $budgets; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the involved accounts. | ||||
|      */ | ||||
|     public function setAccounts(Collection $accounts): ReportGeneratorInterface | ||||
|     { | ||||
|         $this->accounts = $accounts; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -124,26 +124,6 @@ class MonthReportGenerator implements ReportGeneratorInterface | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the categories involved in this report. | ||||
|      */ | ||||
|     public function setCategories(Collection $categories): ReportGeneratorInterface | ||||
|     { | ||||
|         $this->categories = $categories; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the involved accounts. | ||||
|      */ | ||||
|     public function setAccounts(Collection $accounts): ReportGeneratorInterface | ||||
|     { | ||||
|         $this->accounts = $accounts; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the expenses for this report. | ||||
|      */ | ||||
| @@ -168,6 +148,26 @@ class MonthReportGenerator implements ReportGeneratorInterface | ||||
|         return $transactions; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the categories involved in this report. | ||||
|      */ | ||||
|     public function setCategories(Collection $categories): ReportGeneratorInterface | ||||
|     { | ||||
|         $this->categories = $categories; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the involved accounts. | ||||
|      */ | ||||
|     public function setAccounts(Collection $accounts): ReportGeneratorInterface | ||||
|     { | ||||
|         $this->accounts = $accounts; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the income for this report. | ||||
|      */ | ||||
|   | ||||
| @@ -49,20 +49,6 @@ class BudgetLimitHandler | ||||
|         $this->updateAvailableBudget($event->budgetLimit); | ||||
|     } | ||||
| 
 | ||||
|     public function deleted(Deleted $event): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('BudgetLimitHandler::deleted(#%s)', $event->budgetLimit->id)); | ||||
|         $budgetLimit     = $event->budgetLimit; | ||||
|         $budgetLimit->id = 0; | ||||
|         $this->updateAvailableBudget($event->budgetLimit); | ||||
|     } | ||||
| 
 | ||||
|     public function updated(Updated $event): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id)); | ||||
|         $this->updateAvailableBudget($event->budgetLimit); | ||||
|     } | ||||
| 
 | ||||
|     private function updateAvailableBudget(BudgetLimit $budgetLimit): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now in updateAvailableBudget(#%d)', $budgetLimit->id)); | ||||
| @@ -248,4 +234,18 @@ class BudgetLimitHandler | ||||
| 
 | ||||
|         return $amount; | ||||
|     } | ||||
| 
 | ||||
|     public function deleted(Deleted $event): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('BudgetLimitHandler::deleted(#%s)', $event->budgetLimit->id)); | ||||
|         $budgetLimit     = $event->budgetLimit; | ||||
|         $budgetLimit->id = 0; | ||||
|         $this->updateAvailableBudget($event->budgetLimit); | ||||
|     } | ||||
| 
 | ||||
|     public function updated(Updated $event): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id)); | ||||
|         $this->updateAvailableBudget($event->budgetLimit); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -220,7 +220,7 @@ class UserEventHandler | ||||
| 
 | ||||
|     public function sendAdminRegistrationNotification(RegisteredUser $event): void | ||||
|     { | ||||
|         $sendMail = (bool) app('fireflyconfig')->get('notification_admin_new_reg', true)->data; | ||||
|         $sendMail = (bool)app('fireflyconfig')->get('notification_admin_new_reg', true)->data; | ||||
|         if ($sendMail) { | ||||
|             /** @var UserRepositoryInterface $repository */ | ||||
|             $repository = app(UserRepositoryInterface::class); | ||||
| @@ -285,7 +285,7 @@ class UserEventHandler | ||||
|         $oldEmail = $event->oldEmail; | ||||
|         $user     = $event->user; | ||||
|         $token    = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid'); | ||||
|         $hashed   = hash('sha256', sprintf('%s%s', (string) config('app.key'), $oldEmail)); | ||||
|         $hashed   = hash('sha256', sprintf('%s%s', (string)config('app.key'), $oldEmail)); | ||||
|         $url      = route('profile.undo-email-change', [$token->data, $hashed]); | ||||
| 
 | ||||
|         try { | ||||
| @@ -347,7 +347,7 @@ class UserEventHandler | ||||
|      */ | ||||
|     public function sendRegistrationMail(RegisteredUser $event): void | ||||
|     { | ||||
|         $sendMail = (bool) app('fireflyconfig')->get('notification_user_new_reg', true)->data; | ||||
|         $sendMail = (bool)app('fireflyconfig')->get('notification_user_new_reg', true)->data; | ||||
|         if ($sendMail) { | ||||
|             try { | ||||
|                 Notification::send($event->user, new UserRegistrationNotification()); | ||||
|   | ||||
| @@ -81,6 +81,27 @@ trait AttachmentCollection | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Join table to get attachment information. | ||||
|      */ | ||||
|     private function joinAttachmentTables(): void | ||||
|     { | ||||
|         if (false === $this->hasJoinedAttTables) { | ||||
|             // join some extra tables:
 | ||||
|             $this->hasJoinedAttTables = true; | ||||
|             $this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id') | ||||
|                 ->where( | ||||
|                     static function (EloquentBuilder $q1): void { // @phpstan-ignore-line
 | ||||
|                         $q1->where('attachments.attachable_type', TransactionJournal::class); | ||||
|                         $q1->where('attachments.uploaded', true); | ||||
|                         $q1->whereNull('attachments.deleted_at'); | ||||
|                         $q1->orWhereNull('attachments.attachable_type'); | ||||
|                     } | ||||
|                 ) | ||||
|             ; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function withAttachmentInformation(): GroupCollectorInterface | ||||
|     { | ||||
|         $this->fields[] = 'attachments.id as attachment_id'; | ||||
| @@ -511,25 +532,4 @@ trait AttachmentCollection | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Join table to get attachment information. | ||||
|      */ | ||||
|     private function joinAttachmentTables(): void | ||||
|     { | ||||
|         if (false === $this->hasJoinedAttTables) { | ||||
|             // join some extra tables:
 | ||||
|             $this->hasJoinedAttTables = true; | ||||
|             $this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id') | ||||
|                 ->where( | ||||
|                     static function (EloquentBuilder $q1): void { // @phpstan-ignore-line
 | ||||
|                         $q1->where('attachments.attachable_type', TransactionJournal::class); | ||||
|                         $q1->where('attachments.uploaded', true); | ||||
|                         $q1->whereNull('attachments.deleted_at'); | ||||
|                         $q1->orWhereNull('attachments.attachable_type'); | ||||
|                     } | ||||
|                 ) | ||||
|             ; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -33,7 +33,10 @@ use Illuminate\Database\Eloquent\Relations\HasMany; | ||||
|  */ | ||||
| trait CollectorProperties | ||||
| { | ||||
|     /** @var array<int, string> */ | ||||
|     public array $sorting; | ||||
|     public const string TEST = 'Test'; | ||||
|     private ?int $endRow; | ||||
|     private bool    $expandGroupSearch; | ||||
|     private array   $fields; | ||||
|     private bool    $hasAccountInfo; | ||||
| @@ -49,10 +52,8 @@ trait CollectorProperties | ||||
|     private ?int    $page; | ||||
|     private array   $postFilters; | ||||
|     private HasMany $query; | ||||
|     private array   $stringFields; | ||||
| 
 | ||||
|     private ?int $startRow; | ||||
|     private ?int $endRow; | ||||
|     private array   $stringFields; | ||||
|     /* | ||||
|      * This array is used to collect ALL tags the user may search for (using 'setTags'). | ||||
|      * This way the user can call 'setTags' multiple times and get a joined result. | ||||
|   | ||||
| @@ -171,6 +171,19 @@ trait MetaCollection | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Join table to get tag information. | ||||
|      */ | ||||
|     protected function joinMetaDataTables(): void | ||||
|     { | ||||
|         if (false === $this->hasJoinedMetaTables) { | ||||
|             $this->hasJoinedMetaTables = true; | ||||
|             $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); | ||||
|             $this->fields[]            = 'journal_meta.name as meta_name'; | ||||
|             $this->fields[]            = 'journal_meta.data as meta_data'; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function excludeExternalUrl(string $url): GroupCollectorInterface | ||||
|     { | ||||
|         $this->joinMetaDataTables(); | ||||
| @@ -369,6 +382,19 @@ trait MetaCollection | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Join table to get tag information. | ||||
|      */ | ||||
|     protected function joinTagTables(): void | ||||
|     { | ||||
|         if (false === $this->hasJoinedTagTables) { | ||||
|             // join some extra tables:
 | ||||
|             $this->hasJoinedTagTables = true; | ||||
|             $this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); | ||||
|             $this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function internalReferenceContains(string $internalReference): GroupCollectorInterface | ||||
|     { | ||||
|         $internalReference = (string)json_encode($internalReference); | ||||
| @@ -539,6 +565,63 @@ trait MetaCollection | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit results to a SPECIFIC set of tags. | ||||
|      */ | ||||
|     public function setAllTags(Collection $tags): GroupCollectorInterface | ||||
|     { | ||||
|         Log::debug(sprintf('Now in setAllTags(%d tag(s))', $tags->count())); | ||||
|         $this->withTagInformation(); | ||||
|         $this->query->whereNotNull('tag_transaction_journal.tag_id'); | ||||
| 
 | ||||
|         // this method adds a "postFilter" to the collector.
 | ||||
|         $list                = $tags->pluck('tag')->toArray(); | ||||
|         $list                = array_map('strtolower', $list); | ||||
|         $filter              = static function (array $object) use ($list): bool|array { | ||||
|             $includedJournals       = []; | ||||
|             $return                 = $object; | ||||
|             unset($return['transactions']); | ||||
|             $return['transactions'] = []; | ||||
|             Log::debug(sprintf('Now in setAllTags(%s) filter', implode(', ', $list))); | ||||
|             $expectedTagCount       = count($list); | ||||
|             $foundTagCount          = 0; | ||||
|             foreach ($object['transactions'] as $transaction) { | ||||
|                 $transactionTagCount = count($transaction['tags']); | ||||
|                 app('log')->debug(sprintf('Transaction #%d has %d tag(s)', $transaction['transaction_journal_id'], $transactionTagCount)); | ||||
|                 if ($transactionTagCount < $expectedTagCount) { | ||||
|                     app('log')->debug(sprintf('Transaction has %d tag(s), we expect %d tag(s), return false.', $transactionTagCount, $expectedTagCount)); | ||||
| 
 | ||||
|                     return false; | ||||
|                 } | ||||
|                 foreach ($transaction['tags'] as $tag) { | ||||
|                     Log::debug(sprintf('"%s" versus', strtolower($tag['name'])), $list); | ||||
|                     if (in_array(strtolower($tag['name']), $list, true)) { | ||||
|                         app('log')->debug(sprintf('Transaction has tag "%s" so count++.', $tag['name'])); | ||||
|                         ++$foundTagCount; | ||||
|                         $journalId = $transaction['transaction_journal_id']; | ||||
|                         // #8377 prevent adding a transaction twice when multiple tag searches find this transaction
 | ||||
|                         if (!in_array($journalId, $includedJournals, true)) { | ||||
|                             $includedJournals[]       = $journalId; | ||||
|                             $return['transactions'][] = $transaction; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Log::debug(sprintf('Found %d tags, need at least %d.', $foundTagCount, $expectedTagCount)); | ||||
| 
 | ||||
|             // found at least the expected tags.
 | ||||
|             $result                 = $foundTagCount >= $expectedTagCount; | ||||
|             if (true === $result) { | ||||
|                 return $return; | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         }; | ||||
|         $this->postFilters[] = $filter; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the search to a specific bill. | ||||
|      */ | ||||
| @@ -669,63 +752,6 @@ trait MetaCollection | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit results to a SPECIFIC set of tags. | ||||
|      */ | ||||
|     public function setAllTags(Collection $tags): GroupCollectorInterface | ||||
|     { | ||||
|         Log::debug(sprintf('Now in setAllTags(%d tag(s))', $tags->count())); | ||||
|         $this->withTagInformation(); | ||||
|         $this->query->whereNotNull('tag_transaction_journal.tag_id'); | ||||
| 
 | ||||
|         // this method adds a "postFilter" to the collector.
 | ||||
|         $list                = $tags->pluck('tag')->toArray(); | ||||
|         $list                = array_map('strtolower', $list); | ||||
|         $filter              = static function (array $object) use ($list): bool|array { | ||||
|             $includedJournals       = []; | ||||
|             $return                 = $object; | ||||
|             unset($return['transactions']); | ||||
|             $return['transactions'] = []; | ||||
|             Log::debug(sprintf('Now in setAllTags(%s) filter', implode(', ', $list))); | ||||
|             $expectedTagCount       = count($list); | ||||
|             $foundTagCount          = 0; | ||||
|             foreach ($object['transactions'] as $transaction) { | ||||
|                 $transactionTagCount = count($transaction['tags']); | ||||
|                 app('log')->debug(sprintf('Transaction #%d has %d tag(s)', $transaction['transaction_journal_id'], $transactionTagCount)); | ||||
|                 if ($transactionTagCount < $expectedTagCount) { | ||||
|                     app('log')->debug(sprintf('Transaction has %d tag(s), we expect %d tag(s), return false.', $transactionTagCount, $expectedTagCount)); | ||||
| 
 | ||||
|                     return false; | ||||
|                 } | ||||
|                 foreach ($transaction['tags'] as $tag) { | ||||
|                     Log::debug(sprintf('"%s" versus', strtolower($tag['name'])), $list); | ||||
|                     if (in_array(strtolower($tag['name']), $list, true)) { | ||||
|                         app('log')->debug(sprintf('Transaction has tag "%s" so count++.', $tag['name'])); | ||||
|                         ++$foundTagCount; | ||||
|                         $journalId = $transaction['transaction_journal_id']; | ||||
|                         // #8377 prevent adding a transaction twice when multiple tag searches find this transaction
 | ||||
|                         if (!in_array($journalId, $includedJournals, true)) { | ||||
|                             $includedJournals[]       = $journalId; | ||||
|                             $return['transactions'][] = $transaction; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Log::debug(sprintf('Found %d tags, need at least %d.', $foundTagCount, $expectedTagCount)); | ||||
| 
 | ||||
|             // found at least the expected tags.
 | ||||
|             $result                 = $foundTagCount >= $expectedTagCount; | ||||
|             if (true === $result) { | ||||
|                 return $return; | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         }; | ||||
|         $this->postFilters[] = $filter; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit results to any of the tags in the list. | ||||
|      */ | ||||
| @@ -938,30 +964,4 @@ trait MetaCollection | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Join table to get tag information. | ||||
|      */ | ||||
|     protected function joinMetaDataTables(): void | ||||
|     { | ||||
|         if (false === $this->hasJoinedMetaTables) { | ||||
|             $this->hasJoinedMetaTables = true; | ||||
|             $this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id'); | ||||
|             $this->fields[]            = 'journal_meta.name as meta_name'; | ||||
|             $this->fields[]            = 'journal_meta.data as meta_data'; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Join table to get tag information. | ||||
|      */ | ||||
|     protected function joinTagTables(): void | ||||
|     { | ||||
|         if (false === $this->hasJoinedTagTables) { | ||||
|             // join some extra tables:
 | ||||
|             $this->hasJoinedTagTables = true; | ||||
|             $this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); | ||||
|             $this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -61,6 +61,7 @@ class GroupCollector implements GroupCollectorInterface | ||||
|      */ | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->sorting              = []; | ||||
|         $this->postFilters          = []; | ||||
|         $this->tags                 = []; | ||||
|         $this->user                 = null; | ||||
| @@ -466,6 +467,9 @@ class GroupCollector implements GroupCollectorInterface | ||||
|         // filter the array using all available post filters:
 | ||||
|         $collection  = $this->postFilterCollection($collection); | ||||
| 
 | ||||
|         // sort the collection, if sort instructions are present.
 | ||||
|         $collection  = $this->sortCollection($collection); | ||||
| 
 | ||||
|         // count it and continue:
 | ||||
|         $this->total = $collection->count(); | ||||
| 
 | ||||
| @@ -483,226 +487,6 @@ class GroupCollector implements GroupCollectorInterface | ||||
|         return $collection; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Same as getGroups but everything is in a paginator. | ||||
|      */ | ||||
|     public function getPaginatedGroups(): LengthAwarePaginator | ||||
|     { | ||||
|         $set = $this->getGroups(); | ||||
|         if (0 === $this->limit) { | ||||
|             $this->setLimit(50); | ||||
|         } | ||||
|         if (null !== $this->startRow && null !== $this->endRow) { | ||||
|             $total = $this->endRow - $this->startRow; | ||||
| 
 | ||||
|             return new LengthAwarePaginator($set, $this->total, $total, 1); | ||||
|         } | ||||
| 
 | ||||
|         return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the number of returned entries. | ||||
|      */ | ||||
|     public function setLimit(int $limit): GroupCollectorInterface | ||||
|     { | ||||
|         $this->limit = $limit; | ||||
|         // app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
 | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function isNotReconciled(): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where('source.reconciled', 0)->where('destination.reconciled', 0); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function isReconciled(): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where('source.reconciled', 1)->where('destination.reconciled', 1); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit results to a specific currency, either foreign or normal one. | ||||
|      */ | ||||
|     public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where( | ||||
|             static function (EloquentBuilder $q) use ($currency): void { // @phpstan-ignore-line
 | ||||
|                 $q->where('source.transaction_currency_id', $currency->id); | ||||
|                 $q->orWhere('source.foreign_currency_id', $currency->id); | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setExpandGroupSearch(bool $expandGroupSearch): GroupCollectorInterface | ||||
|     { | ||||
|         $this->expandGroupSearch = $expandGroupSearch; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where('source.foreign_currency_id', $currency->id); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the result to a set of specific transaction groups. | ||||
|      */ | ||||
|     public function setIds(array $groupIds): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->whereIn('transaction_groups.id', $groupIds); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the result to a set of specific journals. | ||||
|      */ | ||||
|     public function setJournalIds(array $journalIds): GroupCollectorInterface | ||||
|     { | ||||
|         if (0 !== count($journalIds)) { | ||||
|             // make all integers.
 | ||||
|             $integerIDs = array_map('intval', $journalIds); | ||||
|             Log::debug(sprintf('GroupCollector: setJournalIds: %s', implode(', ', $integerIDs))); | ||||
| 
 | ||||
|             $this->query->whereIn('transaction_journals.id', $integerIDs); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the page to get. | ||||
|      */ | ||||
|     public function setPage(int $page): GroupCollectorInterface | ||||
|     { | ||||
|         $page       = 0 === $page ? 1 : $page; | ||||
|         $this->page = $page; | ||||
|         // app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
 | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Search for words in descriptions. | ||||
|      */ | ||||
|     public function setSearchWords(array $array): GroupCollectorInterface | ||||
|     { | ||||
|         if (0 === count($array)) { | ||||
|             return $this; | ||||
|         } | ||||
|         $this->query->where( | ||||
|             static function (EloquentBuilder $q) use ($array): void { // @phpstan-ignore-line
 | ||||
|                 $q->where( | ||||
|                     static function (EloquentBuilder $q1) use ($array): void { | ||||
|                         foreach ($array as $word) { | ||||
|                             $keyword = sprintf('%%%s%%', $word); | ||||
|                             $q1->where('transaction_journals.description', 'LIKE', $keyword); | ||||
|                         } | ||||
|                     } | ||||
|                 ); | ||||
|                 $q->orWhere( | ||||
|                     static function (EloquentBuilder $q2) use ($array): void { | ||||
|                         foreach ($array as $word) { | ||||
|                             $keyword = sprintf('%%%s%%', $word); | ||||
|                             $q2->where('transaction_groups.title', 'LIKE', $keyword); | ||||
|                         } | ||||
|                     } | ||||
|                 ); | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the search to one specific transaction group. | ||||
|      */ | ||||
|     public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where('transaction_groups.id', $transactionGroup->id); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the included transaction types. | ||||
|      */ | ||||
|     public function setTypes(array $types): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->whereIn('transaction_types.type', $types); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the user object and start the query. | ||||
|      */ | ||||
|     public function setUser(User $user): GroupCollectorInterface | ||||
|     { | ||||
|         if (null === $this->user) { | ||||
|             $this->user = $user; | ||||
|             $this->startQuery(); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the user object and start the query. | ||||
|      */ | ||||
|     public function setUserGroup(UserGroup $userGroup): GroupCollectorInterface | ||||
|     { | ||||
|         if (null === $this->userGroup) { | ||||
|             $this->userGroup = $userGroup; | ||||
|             $this->startQueryForGroup(); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Automatically include all stuff required to make API calls work. | ||||
|      */ | ||||
|     public function withAPIInformation(): GroupCollectorInterface | ||||
|     { | ||||
|         // include source + destination account name and type.
 | ||||
|         $this->withAccountInformation() | ||||
|             // include category ID + name (if any)
 | ||||
|             ->withCategoryInformation() | ||||
|             // include budget ID + name (if any)
 | ||||
|             ->withBudgetInformation() | ||||
|             // include bill ID + name (if any)
 | ||||
|             ->withBillInformation() | ||||
|         ; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setEndRow(int $endRow): self | ||||
|     { | ||||
|         $this->endRow = $endRow; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setStartRow(int $startRow): self | ||||
|     { | ||||
|         $this->startRow = $startRow; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     private function getCollectedGroupIds(): array | ||||
|     { | ||||
|         return $this->query->get(['transaction_journals.transaction_group_id'])->pluck('transaction_group_id')->toArray(); | ||||
| @@ -998,6 +782,195 @@ class GroupCollector implements GroupCollectorInterface | ||||
|         return $currentCollection; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Same as getGroups but everything is in a paginator. | ||||
|      */ | ||||
|     public function getPaginatedGroups(): LengthAwarePaginator | ||||
|     { | ||||
|         $set = $this->getGroups(); | ||||
|         if (0 === $this->limit) { | ||||
|             $this->setLimit(50); | ||||
|         } | ||||
|         if (null !== $this->startRow && null !== $this->endRow) { | ||||
|             $total = $this->endRow - $this->startRow; | ||||
| 
 | ||||
|             return new LengthAwarePaginator($set, $this->total, $total, 1); | ||||
|         } | ||||
| 
 | ||||
|         return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the number of returned entries. | ||||
|      */ | ||||
|     public function setLimit(int $limit): GroupCollectorInterface | ||||
|     { | ||||
|         $this->limit = $limit; | ||||
|         // app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
 | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function isNotReconciled(): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where('source.reconciled', 0)->where('destination.reconciled', 0); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function isReconciled(): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where('source.reconciled', 1)->where('destination.reconciled', 1); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit results to a specific currency, either foreign or normal one. | ||||
|      */ | ||||
|     public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where( | ||||
|             static function (EloquentBuilder $q) use ($currency): void { // @phpstan-ignore-line
 | ||||
|                 $q->where('source.transaction_currency_id', $currency->id); | ||||
|                 $q->orWhere('source.foreign_currency_id', $currency->id); | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setEndRow(int $endRow): self | ||||
|     { | ||||
|         $this->endRow = $endRow; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setExpandGroupSearch(bool $expandGroupSearch): GroupCollectorInterface | ||||
|     { | ||||
|         $this->expandGroupSearch = $expandGroupSearch; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setForeignCurrency(TransactionCurrency $currency): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where('source.foreign_currency_id', $currency->id); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the result to a set of specific transaction groups. | ||||
|      */ | ||||
|     public function setIds(array $groupIds): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->whereIn('transaction_groups.id', $groupIds); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the result to a set of specific journals. | ||||
|      */ | ||||
|     public function setJournalIds(array $journalIds): GroupCollectorInterface | ||||
|     { | ||||
|         if (0 !== count($journalIds)) { | ||||
|             // make all integers.
 | ||||
|             $integerIDs = array_map('intval', $journalIds); | ||||
|             Log::debug(sprintf('GroupCollector: setJournalIds: %s', implode(', ', $integerIDs))); | ||||
| 
 | ||||
|             $this->query->whereIn('transaction_journals.id', $integerIDs); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the page to get. | ||||
|      */ | ||||
|     public function setPage(int $page): GroupCollectorInterface | ||||
|     { | ||||
|         $page       = 0 === $page ? 1 : $page; | ||||
|         $this->page = $page; | ||||
|         // app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
 | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Search for words in descriptions. | ||||
|      */ | ||||
|     public function setSearchWords(array $array): GroupCollectorInterface | ||||
|     { | ||||
|         if (0 === count($array)) { | ||||
|             return $this; | ||||
|         } | ||||
|         $this->query->where( | ||||
|             static function (EloquentBuilder $q) use ($array): void { // @phpstan-ignore-line
 | ||||
|                 $q->where( | ||||
|                     static function (EloquentBuilder $q1) use ($array): void { | ||||
|                         foreach ($array as $word) { | ||||
|                             $keyword = sprintf('%%%s%%', $word); | ||||
|                             $q1->where('transaction_journals.description', 'LIKE', $keyword); | ||||
|                         } | ||||
|                     } | ||||
|                 ); | ||||
|                 $q->orWhere( | ||||
|                     static function (EloquentBuilder $q2) use ($array): void { | ||||
|                         foreach ($array as $word) { | ||||
|                             $keyword = sprintf('%%%s%%', $word); | ||||
|                             $q2->where('transaction_groups.title', 'LIKE', $keyword); | ||||
|                         } | ||||
|                     } | ||||
|                 ); | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setStartRow(int $startRow): self | ||||
|     { | ||||
|         $this->startRow = $startRow; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the search to one specific transaction group. | ||||
|      */ | ||||
|     public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where('transaction_groups.id', $transactionGroup->id); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the included transaction types. | ||||
|      */ | ||||
|     public function setTypes(array $types): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->whereIn('transaction_types.type', $types); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the user object and start the query. | ||||
|      */ | ||||
|     public function setUser(User $user): GroupCollectorInterface | ||||
|     { | ||||
|         if (null === $this->user) { | ||||
|             $this->user = $user; | ||||
|             $this->startQuery(); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Build the query. | ||||
|      */ | ||||
| @@ -1044,6 +1017,19 @@ class GroupCollector implements GroupCollectorInterface | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the user object and start the query. | ||||
|      */ | ||||
|     public function setUserGroup(UserGroup $userGroup): GroupCollectorInterface | ||||
|     { | ||||
|         if (null === $this->userGroup) { | ||||
|             $this->userGroup = $userGroup; | ||||
|             $this->startQueryForGroup(); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Build the query. | ||||
|      */ | ||||
| @@ -1087,4 +1073,59 @@ class GroupCollector implements GroupCollectorInterface | ||||
|             ->orderBy('source.amount', 'DESC') | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Automatically include all stuff required to make API calls work. | ||||
|      */ | ||||
|     public function withAPIInformation(): GroupCollectorInterface | ||||
|     { | ||||
|         // include source + destination account name and type.
 | ||||
|         $this->withAccountInformation() | ||||
|             // include category ID + name (if any)
 | ||||
|             ->withCategoryInformation() | ||||
|             // include budget ID + name (if any)
 | ||||
|             ->withBudgetInformation() | ||||
|             // include bill ID + name (if any)
 | ||||
|             ->withBillInformation() | ||||
|         ; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     #[\Override]
 | ||||
|     public function sortCollection(Collection $collection): Collection | ||||
|     { | ||||
|         /** | ||||
|          * @var string $field | ||||
|          * @var string $direction | ||||
|          */ | ||||
|         foreach ($this->sorting as $field => $direction) { | ||||
|             $func       = 'ASC' === $direction ? 'sortBy' : 'sortByDesc'; | ||||
|             $collection = $collection->{$func}(function (array $product, int $key) use ($field) { // @phpstan-ignore-line
 | ||||
|                 // depends on $field:
 | ||||
|                 if ('description' === $field) { | ||||
|                     if (1 === count($product['transactions'])) { | ||||
|                         return array_values($product['transactions'])[0][$field]; | ||||
|                     } | ||||
|                     if (count($product['transactions']) > 1) { | ||||
|                         return $product['title']; | ||||
|                     } | ||||
| 
 | ||||
|                     return 'zzz'; | ||||
|                 } | ||||
| 
 | ||||
|                 exit('here we are'); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         return $collection; | ||||
|     } | ||||
| 
 | ||||
|     #[\Override]
 | ||||
|     public function setSorting(array $instructions): GroupCollectorInterface | ||||
|     { | ||||
|         $this->sorting = $instructions; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -285,6 +285,13 @@ interface GroupCollectorInterface | ||||
|      */ | ||||
|     public function getPaginatedGroups(): LengthAwarePaginator; | ||||
| 
 | ||||
|     public function setSorting(array $instructions): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Sort the collection on a column. | ||||
|      */ | ||||
|     public function sortCollection(Collection $collection): Collection; | ||||
| 
 | ||||
|     public function hasAnyTag(): self; | ||||
| 
 | ||||
|     /** | ||||
| @@ -401,6 +408,11 @@ interface GroupCollectorInterface | ||||
|      */ | ||||
|     public function setAfter(Carbon $date): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Limit results to a SPECIFIC set of tags. | ||||
|      */ | ||||
|     public function setAllTags(Collection $tags): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Collect transactions before a specific date. | ||||
|      */ | ||||
| @@ -461,6 +473,11 @@ interface GroupCollectorInterface | ||||
|      */ | ||||
|     public function setEnd(Carbon $end): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Set the page to get. | ||||
|      */ | ||||
|     public function setEndRow(int $endRow): self; | ||||
| 
 | ||||
|     public function setExpandGroupSearch(bool $expandGroupSearch): self; | ||||
| 
 | ||||
|     /** | ||||
| @@ -526,16 +543,6 @@ interface GroupCollectorInterface | ||||
|      */ | ||||
|     public function setPage(int $page): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Set the page to get. | ||||
|      */ | ||||
|     public function setStartRow(int $startRow): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Set the page to get. | ||||
|      */ | ||||
|     public function setEndRow(int $endRow): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Set the start and end time of the results to return. | ||||
|      */ | ||||
| @@ -563,6 +570,11 @@ interface GroupCollectorInterface | ||||
|      */ | ||||
|     public function setStart(Carbon $start): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Set the page to get. | ||||
|      */ | ||||
|     public function setStartRow(int $startRow): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Limit results to a specific tag. | ||||
|      */ | ||||
| @@ -573,11 +585,6 @@ interface GroupCollectorInterface | ||||
|      */ | ||||
|     public function setTags(Collection $tags): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Limit results to a SPECIFIC set of tags. | ||||
|      */ | ||||
|     public function setAllTags(Collection $tags): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Limit the search to one specific transaction group. | ||||
|      */ | ||||
|   | ||||
| @@ -51,7 +51,7 @@ class NetWorth implements NetWorthInterface | ||||
| 
 | ||||
|     private CurrencyRepositoryInterface $currencyRepos; | ||||
|     private User                        $user; | ||||
|     private null|UserGroup            $userGroup; | ||||
|     private ?UserGroup            $userGroup; | ||||
| 
 | ||||
|     /** | ||||
|      * This method collects the user's net worth in ALL the user's currencies | ||||
| @@ -121,12 +121,12 @@ class NetWorth implements NetWorthInterface | ||||
|             $netWorth[$currencyCode] ??= [ | ||||
|                 'balance'                        => '0', | ||||
|                 'native_balance'                 => '0', | ||||
|                 'currency_id'                    => (string) $currency->id, | ||||
|                 '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_id'             => (string)$default->id, | ||||
|                 'native_currency_code'           => $default->code, | ||||
|                 'native_currency_name'           => $default->name, | ||||
|                 'native_currency_symbol'         => $default->symbol, | ||||
| @@ -144,6 +144,15 @@ class NetWorth implements NetWorthInterface | ||||
|         return $netWorth; | ||||
|     } | ||||
| 
 | ||||
|     private function getRepository(): AccountRepositoryInterface|AdminAccountRepositoryInterface | ||||
|     { | ||||
|         if (null === $this->userGroup) { | ||||
|             return $this->accountRepository; | ||||
|         } | ||||
| 
 | ||||
|         return $this->adminAccountRepository; | ||||
|     } | ||||
| 
 | ||||
|     public function setUser(null|Authenticatable|User $user): void | ||||
|     { | ||||
|         if (!$user instanceof User) { | ||||
| @@ -189,7 +198,7 @@ class NetWorth implements NetWorthInterface | ||||
|             } | ||||
| 
 | ||||
|             $return[$currency->id] ??= [ | ||||
|                 'id'             => (string) $currency->id, | ||||
|                 'id'             => (string)$currency->id, | ||||
|                 'name'           => $currency->name, | ||||
|                 'symbol'         => $currency->symbol, | ||||
|                 'code'           => $currency->code, | ||||
| @@ -202,15 +211,6 @@ class NetWorth implements NetWorthInterface | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     private function getRepository(): AccountRepositoryInterface|AdminAccountRepositoryInterface | ||||
|     { | ||||
|         if (null === $this->userGroup) { | ||||
|             return $this->accountRepository; | ||||
|         } | ||||
| 
 | ||||
|         return $this->adminAccountRepository; | ||||
|     } | ||||
| 
 | ||||
|     private function getAccounts(): Collection | ||||
|     { | ||||
|         $accounts = $this->getRepository()->getAccountsByType( | ||||
| @@ -220,7 +220,7 @@ class NetWorth implements NetWorthInterface | ||||
| 
 | ||||
|         /** @var Account $account */ | ||||
|         foreach ($accounts as $account) { | ||||
|             if (1 === (int) $this->getRepository()->getMetaValue($account, 'include_net_worth')) { | ||||
|             if (1 === (int)$this->getRepository()->getMetaValue($account, 'include_net_worth')) { | ||||
|                 $filtered->push($account); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -58,7 +58,7 @@ class CreateController extends Controller | ||||
|         $this->middleware( | ||||
|             function ($request, $next) { | ||||
|                 app('view')->share('mainTitleIcon', 'fa-credit-card'); | ||||
|                 app('view')->share('title', (string) trans('firefly.accounts')); | ||||
|                 app('view')->share('title', (string)trans('firefly.accounts')); | ||||
| 
 | ||||
|                 $this->repository  = app(AccountRepositoryInterface::class); | ||||
|                 $this->attachments = app(AttachmentHelperInterface::class); | ||||
| @@ -77,7 +77,7 @@ class CreateController extends Controller | ||||
|     { | ||||
|         $defaultCurrency     = app('amount')->getDefaultCurrency(); | ||||
|         $subTitleIcon        = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); | ||||
|         $subTitle            = (string) trans(sprintf('firefly.make_new_%s_account', $objectType)); | ||||
|         $subTitle            = (string)trans(sprintf('firefly.make_new_%s_account', $objectType)); | ||||
|         $roles               = $this->getRoles(); | ||||
|         $liabilityTypes      = $this->getLiabilityTypes(); | ||||
|         $hasOldInput         = null !== $request->old('_token'); | ||||
| @@ -96,9 +96,9 @@ class CreateController extends Controller | ||||
| 
 | ||||
|         // interest calculation periods:
 | ||||
|         $interestPeriods     = [ | ||||
|             'daily'   => (string) trans('firefly.interest_calc_daily'), | ||||
|             'monthly' => (string) trans('firefly.interest_calc_monthly'), | ||||
|             'yearly'  => (string) trans('firefly.interest_calc_yearly'), | ||||
|             'daily'   => (string)trans('firefly.interest_calc_daily'), | ||||
|             'monthly' => (string)trans('firefly.interest_calc_monthly'), | ||||
|             'yearly'  => (string)trans('firefly.interest_calc_yearly'), | ||||
|         ]; | ||||
| 
 | ||||
|         // pre fill some data
 | ||||
| @@ -106,7 +106,7 @@ class CreateController extends Controller | ||||
|             'preFilled', | ||||
|             [ | ||||
|                 'currency_id'       => $defaultCurrency->id, | ||||
|                 'include_net_worth' => $hasOldInput ? (bool) $request->old('include_net_worth') : true, | ||||
|                 'include_net_worth' => $hasOldInput ? (bool)$request->old('include_net_worth') : true, | ||||
|             ] | ||||
|         ); | ||||
|         // issue #8321
 | ||||
| @@ -139,7 +139,7 @@ class CreateController extends Controller | ||||
|     { | ||||
|         $data      = $request->getAccountData(); | ||||
|         $account   = $this->repository->store($data); | ||||
|         $request->session()->flash('success', (string) trans('firefly.stored_new_account', ['name' => $account->name])); | ||||
|         $request->session()->flash('success', (string)trans('firefly.stored_new_account', ['name' => $account->name])); | ||||
|         app('preferences')->mark(); | ||||
| 
 | ||||
|         Log::channel('audit')->info('Stored new account.', $data); | ||||
| @@ -162,7 +162,7 @@ class CreateController extends Controller | ||||
|         } | ||||
|         if (null !== $files && auth()->user()->hasRole('demo')) { | ||||
|             Log::channel('audit')->warning(sprintf('The demo user is trying to upload attachments in %s.', __METHOD__)); | ||||
|             session()->flash('info', (string) trans('firefly.no_att_demo_user')); | ||||
|             session()->flash('info', (string)trans('firefly.no_att_demo_user')); | ||||
|         } | ||||
| 
 | ||||
|         if (count($this->attachments->getMessages()->get('attachments')) > 0) { | ||||
| @@ -171,7 +171,7 @@ class CreateController extends Controller | ||||
| 
 | ||||
|         // redirect to previous URL.
 | ||||
|         $redirect  = redirect($this->getPreviousUrl('accounts.create.url')); | ||||
|         if (1 === (int) $request->get('create_another')) { | ||||
|         if (1 === (int)$request->get('create_another')) { | ||||
|             // set value so create routine will not overwrite URL:
 | ||||
|             $request->session()->put('accounts.create.fromStore', true); | ||||
| 
 | ||||
|   | ||||
| @@ -57,7 +57,7 @@ class EditController extends Controller | ||||
|         $this->middleware( | ||||
|             function ($request, $next) { | ||||
|                 app('view')->share('mainTitleIcon', 'fa-credit-card'); | ||||
|                 app('view')->share('title', (string) trans('firefly.accounts')); | ||||
|                 app('view')->share('title', (string)trans('firefly.accounts')); | ||||
| 
 | ||||
|                 $this->repository  = app(AccountRepositoryInterface::class); | ||||
|                 $this->attachments = app(AttachmentHelperInterface::class); | ||||
| @@ -81,7 +81,7 @@ class EditController extends Controller | ||||
|         } | ||||
| 
 | ||||
|         $objectType           = config('firefly.shortNamesByFullName')[$account->accountType->type]; | ||||
|         $subTitle             = (string) trans(sprintf('firefly.edit_%s_account', $objectType), ['name' => $account->name]); | ||||
|         $subTitle             = (string)trans(sprintf('firefly.edit_%s_account', $objectType), ['name' => $account->name]); | ||||
|         $subTitleIcon         = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); | ||||
|         $roles                = $this->getRoles(); | ||||
|         $liabilityTypes       = $this->getLiabilityTypes(); | ||||
| @@ -106,9 +106,9 @@ class EditController extends Controller | ||||
| 
 | ||||
|         // interest calculation periods:
 | ||||
|         $interestPeriods      = [ | ||||
|             'daily'   => (string) trans('firefly.interest_calc_daily'), | ||||
|             'monthly' => (string) trans('firefly.interest_calc_monthly'), | ||||
|             'yearly'  => (string) trans('firefly.interest_calc_yearly'), | ||||
|             'daily'   => (string)trans('firefly.interest_calc_daily'), | ||||
|             'monthly' => (string)trans('firefly.interest_calc_monthly'), | ||||
|             'yearly'  => (string)trans('firefly.interest_calc_yearly'), | ||||
|         ]; | ||||
| 
 | ||||
|         // put previous url in session if not redirect from store (not "return_to_edit").
 | ||||
| @@ -117,7 +117,7 @@ class EditController extends Controller | ||||
|         } | ||||
|         $request->session()->forget('accounts.edit.fromUpdate'); | ||||
| 
 | ||||
|         $openingBalanceAmount = (string) $repository->getOpeningBalanceAmount($account); | ||||
|         $openingBalanceAmount = (string)$repository->getOpeningBalanceAmount($account); | ||||
|         if ('0' === $openingBalanceAmount) { | ||||
|             $openingBalanceAmount = ''; | ||||
|         } | ||||
| @@ -143,17 +143,17 @@ class EditController extends Controller | ||||
|             'cc_type'                 => $repository->getMetaValue($account, 'cc_type'), | ||||
|             'cc_monthly_payment_date' => $repository->getMetaValue($account, 'cc_monthly_payment_date'), | ||||
|             'BIC'                     => $repository->getMetaValue($account, 'BIC'), | ||||
|             'opening_balance_date'    => substr((string) $openingBalanceDate, 0, 10), | ||||
|             'opening_balance_date'    => substr((string)$openingBalanceDate, 0, 10), | ||||
|             'liability_type_id'       => $account->account_type_id, | ||||
|             'opening_balance'         => app('steam')->bcround($openingBalanceAmount, $currency->decimal_places), | ||||
|             'liability_direction'     => $this->repository->getMetaValue($account, 'liability_direction'), | ||||
|             'virtual_balance'         => app('steam')->bcround($virtualBalance, $currency->decimal_places), | ||||
|             'currency_id'             => $currency->id, | ||||
|             'include_net_worth'       => $hasOldInput ? (bool) $request->old('include_net_worth') : $includeNetWorth, | ||||
|             'include_net_worth'       => $hasOldInput ? (bool)$request->old('include_net_worth') : $includeNetWorth, | ||||
|             'interest'                => $repository->getMetaValue($account, 'interest'), | ||||
|             'interest_period'         => $repository->getMetaValue($account, 'interest_period'), | ||||
|             'notes'                   => $this->repository->getNoteText($account), | ||||
|             'active'                  => $hasOldInput ? (bool) $request->old('active') : $account->active, | ||||
|             'active'                  => $hasOldInput ? (bool)$request->old('active') : $account->active, | ||||
|         ]; | ||||
|         if ('' === $openingBalanceAmount) { | ||||
|             $preFilled['opening_balance'] = ''; | ||||
| @@ -178,7 +178,7 @@ class EditController extends Controller | ||||
|         $data     = $request->getAccountData(); | ||||
|         $this->repository->update($account, $data); | ||||
|         Log::channel('audit')->info(sprintf('Updated account #%d.', $account->id), $data); | ||||
|         $request->session()->flash('success', (string) trans('firefly.updated_account', ['name' => $account->name])); | ||||
|         $request->session()->flash('success', (string)trans('firefly.updated_account', ['name' => $account->name])); | ||||
| 
 | ||||
|         // store new attachment(s):
 | ||||
|         /** @var null|array $files */ | ||||
| @@ -188,7 +188,7 @@ class EditController extends Controller | ||||
|         } | ||||
|         if (null !== $files && auth()->user()->hasRole('demo')) { | ||||
|             Log::channel('audit')->warning(sprintf('The demo user is trying to upload attachments in %s.', __METHOD__)); | ||||
|             session()->flash('info', (string) trans('firefly.no_att_demo_user')); | ||||
|             session()->flash('info', (string)trans('firefly.no_att_demo_user')); | ||||
|         } | ||||
| 
 | ||||
|         if (count($this->attachments->getMessages()->get('attachments')) > 0) { | ||||
| @@ -197,7 +197,7 @@ class EditController extends Controller | ||||
| 
 | ||||
|         // redirect
 | ||||
|         $redirect = redirect($this->getPreviousUrl('accounts.edit.url')); | ||||
|         if (1 === (int) $request->get('return_to_edit')) { | ||||
|         if (1 === (int)$request->get('return_to_edit')) { | ||||
|             // set value so edit routine will not overwrite URL:
 | ||||
|             $request->session()->put('accounts.edit.fromUpdate', true); | ||||
| 
 | ||||
|   | ||||
| @@ -95,6 +95,21 @@ class ForgotPasswordController extends Controller | ||||
|         return back()->with('status', trans($response)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function validateHost(): void | ||||
|     { | ||||
|         $configuredHost = parse_url((string)config('app.url'), PHP_URL_HOST); | ||||
|         if (false === $configuredHost || null === $configuredHost) { | ||||
|             throw new FireflyException('Please set a valid and correct Firefly III URL in the APP_URL environment variable.'); | ||||
|         } | ||||
|         $host           = request()->host(); | ||||
|         if ($configuredHost !== $host) { | ||||
|             throw new FireflyException('The Host-header does not match the host in the APP_URL environment variable. Please make sure these match. See also: https://bit.ly/FF3-host-header'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show form for email recovery. | ||||
|      * | ||||
| @@ -121,19 +136,4 @@ class ForgotPasswordController extends Controller | ||||
| 
 | ||||
|         return view('auth.passwords.email')->with(compact('allowRegistration', 'pageTitle')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function validateHost(): void | ||||
|     { | ||||
|         $configuredHost = parse_url((string)config('app.url'), PHP_URL_HOST); | ||||
|         if (false === $configuredHost || null === $configuredHost) { | ||||
|             throw new FireflyException('Please set a valid and correct Firefly III URL in the APP_URL environment variable.'); | ||||
|         } | ||||
|         $host           = request()->host(); | ||||
|         if ($configuredHost !== $host) { | ||||
|             throw new FireflyException('The Host-header does not match the host in the APP_URL environment variable. Please make sure these match. See also: https://bit.ly/FF3-host-header'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -132,6 +132,25 @@ class LoginController extends Controller | ||||
|         return $this->username; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the failed login response instance. | ||||
|      * | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @throws ValidationException | ||||
|      */ | ||||
|     protected function sendFailedLoginResponse(Request $request): void | ||||
|     { | ||||
|         $exception             = ValidationException::withMessages( | ||||
|             [ | ||||
|                 $this->username() => [trans('auth.failed')], | ||||
|             ] | ||||
|         ); | ||||
|         $exception->redirectTo = route('login'); | ||||
| 
 | ||||
|         throw $exception; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log the user out of the application. | ||||
|      * | ||||
| @@ -210,23 +229,4 @@ class LoginController extends Controller | ||||
| 
 | ||||
|         return view('auth.login', compact('allowRegistration', 'email', 'remember', 'allowReset', 'title', 'usernameField')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the failed login response instance. | ||||
|      * | ||||
|      * @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||||
|      * | ||||
|      * @throws ValidationException | ||||
|      */ | ||||
|     protected function sendFailedLoginResponse(Request $request): void | ||||
|     { | ||||
|         $exception             = ValidationException::withMessages( | ||||
|             [ | ||||
|                 $this->username() => [trans('auth.failed')], | ||||
|             ] | ||||
|         ); | ||||
|         $exception->redirectTo = route('login'); | ||||
| 
 | ||||
|         throw $exception; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -109,6 +109,31 @@ class RegisterController extends Controller | ||||
|         return redirect($this->redirectPath()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     protected function allowedToRegister(): bool | ||||
|     { | ||||
|         // is allowed to register?
 | ||||
|         $allowRegistration = true; | ||||
| 
 | ||||
|         try { | ||||
|             $singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; | ||||
|         } catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) { | ||||
|             $singleUserMode = true; | ||||
|         } | ||||
|         $userCount         = User::count(); | ||||
|         $guard             = config('auth.defaults.guard'); | ||||
|         if (true === $singleUserMode && $userCount > 0 && 'web' === $guard) { | ||||
|             $allowRegistration = false; | ||||
|         } | ||||
|         if ('web' !== $guard) { | ||||
|             $allowRegistration = false; | ||||
|         } | ||||
| 
 | ||||
|         return $allowRegistration; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show the application registration form if the invitation code is valid. | ||||
|      * | ||||
| @@ -164,29 +189,4 @@ class RegisterController extends Controller | ||||
| 
 | ||||
|         return view('auth.register', compact('isDemoSite', 'email', 'pageTitle')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     protected function allowedToRegister(): bool | ||||
|     { | ||||
|         // is allowed to register?
 | ||||
|         $allowRegistration = true; | ||||
| 
 | ||||
|         try { | ||||
|             $singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; | ||||
|         } catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) { | ||||
|             $singleUserMode = true; | ||||
|         } | ||||
|         $userCount         = User::count(); | ||||
|         $guard             = config('auth.defaults.guard'); | ||||
|         if (true === $singleUserMode && $userCount > 0 && 'web' === $guard) { | ||||
|             $allowRegistration = false; | ||||
|         } | ||||
|         if ('web' !== $guard) { | ||||
|             $allowRegistration = false; | ||||
|         } | ||||
| 
 | ||||
|         return $allowRegistration; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -31,7 +31,6 @@ use Illuminate\Http\RedirectResponse; | ||||
| use Illuminate\Http\Request; | ||||
| use Illuminate\Routing\Redirector; | ||||
| use PragmaRX\Google2FALaravel\Support\Authenticator; | ||||
| use Preferences; | ||||
| 
 | ||||
| /** | ||||
|  * Class TwoFactorController. | ||||
|   | ||||
| @@ -52,7 +52,7 @@ class EditController extends Controller | ||||
| 
 | ||||
|         $this->middleware( | ||||
|             function ($request, $next) { | ||||
|                 app('view')->share('title', (string) trans('firefly.bills')); | ||||
|                 app('view')->share('title', (string)trans('firefly.bills')); | ||||
|                 app('view')->share('mainTitleIcon', 'fa-calendar-o'); | ||||
|                 $this->attachments = app(AttachmentHelperInterface::class); | ||||
|                 $this->repository  = app(BillRepositoryInterface::class); | ||||
| @@ -75,10 +75,10 @@ class EditController extends Controller | ||||
|         $billPeriods      = config('firefly.bill_periods'); | ||||
| 
 | ||||
|         foreach ($billPeriods as $current) { | ||||
|             $periods[$current] = (string) trans('firefly.'.$current); | ||||
|             $periods[$current] = (string)trans('firefly.'.$current); | ||||
|         } | ||||
| 
 | ||||
|         $subTitle         = (string) trans('firefly.edit_bill', ['name' => $bill->name]); | ||||
|         $subTitle         = (string)trans('firefly.edit_bill', ['name' => $bill->name]); | ||||
| 
 | ||||
|         // put previous url in session if not redirect from store (not "return_to_edit").
 | ||||
|         if (true !== session('bills.edit.fromUpdate')) { | ||||
| @@ -99,7 +99,7 @@ class EditController extends Controller | ||||
|             'extension_date'          => $bill->extension_date, | ||||
|             'notes'                   => $this->repository->getNoteText($bill), | ||||
|             'transaction_currency_id' => $bill->transaction_currency_id, | ||||
|             'active'                  => $hasOldInput ? (bool) $request->old('active') : $bill->active, | ||||
|             'active'                  => $hasOldInput ? (bool)$request->old('active') : $bill->active, | ||||
|             'object_group'            => null !== $bill->objectGroups->first() ? $bill->objectGroups->first()->title : '', | ||||
|         ]; | ||||
| 
 | ||||
| @@ -119,7 +119,7 @@ class EditController extends Controller | ||||
| 
 | ||||
|         Log::channel('audit')->info(sprintf('Updated bill #%d.', $bill->id), $billData); | ||||
| 
 | ||||
|         $request->session()->flash('success', (string) trans('firefly.updated_bill', ['name' => $bill->name])); | ||||
|         $request->session()->flash('success', (string)trans('firefly.updated_bill', ['name' => $bill->name])); | ||||
|         app('preferences')->mark(); | ||||
| 
 | ||||
|         /** @var null|array $files */ | ||||
| @@ -129,7 +129,7 @@ class EditController extends Controller | ||||
|         } | ||||
|         if (null !== $files && auth()->user()->hasRole('demo')) { | ||||
|             Log::channel('audit')->warning(sprintf('The demo user is trying to upload attachments in %s.', __METHOD__)); | ||||
|             session()->flash('info', (string) trans('firefly.no_att_demo_user')); | ||||
|             session()->flash('info', (string)trans('firefly.no_att_demo_user')); | ||||
|         } | ||||
| 
 | ||||
|         // flash messages
 | ||||
| @@ -138,7 +138,7 @@ class EditController extends Controller | ||||
|         } | ||||
|         $redirect = redirect($this->getPreviousUrl('bills.edit.url')); | ||||
| 
 | ||||
|         if (1 === (int) $request->get('return_to_edit')) { | ||||
|         if (1 === (int)$request->get('return_to_edit')) { | ||||
|             $request->session()->put('bills.edit.fromUpdate', true); | ||||
| 
 | ||||
|             $redirect = redirect(route('bills.edit', [$bill->id]))->withInput(['return_to_edit' => 1]); | ||||
|   | ||||
| @@ -134,24 +134,6 @@ class IndexController extends Controller | ||||
|         return view('bills.index', compact('bills', 'sums', 'total', 'totals', 'today')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the order of a bill. | ||||
|      */ | ||||
|     public function setOrder(Request $request, Bill $bill): JsonResponse | ||||
|     { | ||||
|         $objectGroupTitle = (string)$request->get('objectGroupTitle'); | ||||
|         $newOrder         = (int)$request->get('order'); | ||||
|         $this->repository->setOrder($bill, $newOrder); | ||||
|         if ('' !== $objectGroupTitle) { | ||||
|             $this->repository->setObjectGroup($bill, $objectGroupTitle); | ||||
|         } | ||||
|         if ('' === $objectGroupTitle) { | ||||
|             $this->repository->removeObjectGroup($bill); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json(['data' => 'OK']); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
| @@ -268,4 +250,22 @@ class IndexController extends Controller | ||||
| 
 | ||||
|         return $totals; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the order of a bill. | ||||
|      */ | ||||
|     public function setOrder(Request $request, Bill $bill): JsonResponse | ||||
|     { | ||||
|         $objectGroupTitle = (string)$request->get('objectGroupTitle'); | ||||
|         $newOrder         = (int)$request->get('order'); | ||||
|         $this->repository->setOrder($bill, $newOrder); | ||||
|         if ('' !== $objectGroupTitle) { | ||||
|             $this->repository->setObjectGroup($bill, $objectGroupTitle); | ||||
|         } | ||||
|         if ('' === $objectGroupTitle) { | ||||
|             $this->repository->removeObjectGroup($bill); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json(['data' => 'OK']); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -116,7 +116,6 @@ class IndexController extends Controller | ||||
| 
 | ||||
|         // get all available budgets:
 | ||||
|         $availableBudgets = $this->getAllAvailableBudgets($start, $end); | ||||
| 
 | ||||
|         // get all active budgets:
 | ||||
|         $budgets          = $this->getAllBudgets($start, $end, $currencies, $defaultCurrency); | ||||
|         $sums             = $this->getSums($budgets); | ||||
| @@ -159,24 +158,6 @@ class IndexController extends Controller | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse | ||||
|     { | ||||
|         $this->abRepository->cleanup(); | ||||
|         $budgetIds = $request->get('budgetIds'); | ||||
| 
 | ||||
|         foreach ($budgetIds as $index => $budgetId) { | ||||
|             $budgetId = (int)$budgetId; | ||||
|             $budget   = $repository->find($budgetId); | ||||
|             if (null !== $budget) { | ||||
|                 app('log')->debug(sprintf('Set budget #%d ("%s") to position %d', $budget->id, $budget->name, $index + 1)); | ||||
|                 $repository->setBudgetOrder($budget, $index + 1); | ||||
|             } | ||||
|         } | ||||
|         app('preferences')->mark(); | ||||
| 
 | ||||
|         return response()->json(['OK']); | ||||
|     } | ||||
| 
 | ||||
|     private function getAllAvailableBudgets(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         // get all available budgets.
 | ||||
| @@ -316,4 +297,22 @@ class IndexController extends Controller | ||||
| 
 | ||||
|         return $sums; | ||||
|     } | ||||
| 
 | ||||
|     public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse | ||||
|     { | ||||
|         $this->abRepository->cleanup(); | ||||
|         $budgetIds = $request->get('budgetIds'); | ||||
| 
 | ||||
|         foreach ($budgetIds as $index => $budgetId) { | ||||
|             $budgetId = (int)$budgetId; | ||||
|             $budget   = $repository->find($budgetId); | ||||
|             if (null !== $budget) { | ||||
|                 app('log')->debug(sprintf('Set budget #%d ("%s") to position %d', $budget->id, $budget->name, $index + 1)); | ||||
|                 $repository->setBudgetOrder($budget, $index + 1); | ||||
|             } | ||||
|         } | ||||
|         app('preferences')->mark(); | ||||
| 
 | ||||
|         return response()->json(['OK']); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -409,6 +409,58 @@ class AccountController extends Controller | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code)); | ||||
|         $locale            = app('steam')->getLocale(); | ||||
|         $step              = $this->calculateStep($start, $end); | ||||
|         $result            = [ | ||||
|             'label'           => sprintf('%s (%s)', $account->name, $currency->symbol), | ||||
|             'currency_symbol' => $currency->symbol, | ||||
|             'currency_code'   => $currency->code, | ||||
|         ]; | ||||
|         $entries           = []; | ||||
|         $current           = clone $start; | ||||
|         app('log')->debug(sprintf('Step is %s', $step)); | ||||
| 
 | ||||
|         // fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
 | ||||
|         // have to make sure this chart is always based on the balance at the END of the period.
 | ||||
|         // This period depends on the size of the chart
 | ||||
|         $current           = app('navigation')->endOfX($current, $step, null); | ||||
|         app('log')->debug(sprintf('$current date is %s', $current->format('Y-m-d'))); | ||||
|         if ('1D' === $step) { | ||||
|             // per day the entire period, balance for every day.
 | ||||
|             $format   = (string)trans('config.month_and_day_js', [], $locale); | ||||
|             $range    = app('steam')->balanceInRange($account, $start, $end, $currency); | ||||
|             $previous = array_values($range)[0]; | ||||
|             while ($end >= $current) { | ||||
|                 $theDate         = $current->format('Y-m-d'); | ||||
|                 $balance         = $range[$theDate] ?? $previous; | ||||
|                 $label           = $current->isoFormat($format); | ||||
|                 $entries[$label] = (float)$balance; | ||||
|                 $previous        = $balance; | ||||
|                 $current->addDay(); | ||||
|             } | ||||
|         } | ||||
|         if ('1W' === $step || '1M' === $step || '1Y' === $step) { | ||||
|             while ($end >= $current) { | ||||
|                 app('log')->debug(sprintf('Current is: %s', $current->format('Y-m-d'))); | ||||
|                 $balance         = (float)app('steam')->balance($account, $current, $currency); | ||||
|                 $label           = app('navigation')->periodShow($current, $step); | ||||
|                 $entries[$label] = $balance; | ||||
|                 $current         = app('navigation')->addPeriod($current, $step, 0); | ||||
|                 // here too, to fix #8041, the data is corrected to the end of the period.
 | ||||
|                 $current         = app('navigation')->endOfX($current, $step, null); | ||||
|             } | ||||
|         } | ||||
|         $result['entries'] = $entries; | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Shows the balances for a given set of dates and accounts. | ||||
|      * | ||||
| @@ -512,56 +564,4 @@ class AccountController extends Controller | ||||
| 
 | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function periodByCurrency(Carbon $start, Carbon $end, Account $account, TransactionCurrency $currency): array | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now in periodByCurrency("%s", "%s", %s, "%s")', $start->format('Y-m-d'), $end->format('Y-m-d'), $account->id, $currency->code)); | ||||
|         $locale            = app('steam')->getLocale(); | ||||
|         $step              = $this->calculateStep($start, $end); | ||||
|         $result            = [ | ||||
|             'label'           => sprintf('%s (%s)', $account->name, $currency->symbol), | ||||
|             'currency_symbol' => $currency->symbol, | ||||
|             'currency_code'   => $currency->code, | ||||
|         ]; | ||||
|         $entries           = []; | ||||
|         $current           = clone $start; | ||||
|         app('log')->debug(sprintf('Step is %s', $step)); | ||||
| 
 | ||||
|         // fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
 | ||||
|         // have to make sure this chart is always based on the balance at the END of the period.
 | ||||
|         // This period depends on the size of the chart
 | ||||
|         $current           = app('navigation')->endOfX($current, $step, null); | ||||
|         app('log')->debug(sprintf('$current date is %s', $current->format('Y-m-d'))); | ||||
|         if ('1D' === $step) { | ||||
|             // per day the entire period, balance for every day.
 | ||||
|             $format   = (string)trans('config.month_and_day_js', [], $locale); | ||||
|             $range    = app('steam')->balanceInRange($account, $start, $end, $currency); | ||||
|             $previous = array_values($range)[0]; | ||||
|             while ($end >= $current) { | ||||
|                 $theDate         = $current->format('Y-m-d'); | ||||
|                 $balance         = $range[$theDate] ?? $previous; | ||||
|                 $label           = $current->isoFormat($format); | ||||
|                 $entries[$label] = (float)$balance; | ||||
|                 $previous        = $balance; | ||||
|                 $current->addDay(); | ||||
|             } | ||||
|         } | ||||
|         if ('1W' === $step || '1M' === $step || '1Y' === $step) { | ||||
|             while ($end >= $current) { | ||||
|                 app('log')->debug(sprintf('Current is: %s', $current->format('Y-m-d'))); | ||||
|                 $balance         = (float)app('steam')->balance($account, $current, $currency); | ||||
|                 $label           = app('navigation')->periodShow($current, $step); | ||||
|                 $entries[$label] = $balance; | ||||
|                 $current         = app('navigation')->addPeriod($current, $step, 0); | ||||
|                 // here too, to fix #8041, the data is corrected to the end of the period.
 | ||||
|                 $current         = app('navigation')->endOfX($current, $step, null); | ||||
|             } | ||||
|         } | ||||
|         $result['entries'] = $entries; | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -195,6 +195,23 @@ class BudgetReportController extends Controller | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     private function makeEntries(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         $return         = []; | ||||
|         $format         = app('navigation')->preferredCarbonLocalizedFormat($start, $end); | ||||
|         $preferredRange = app('navigation')->preferredRangeFormat($start, $end); | ||||
|         $currentStart   = clone $start; | ||||
|         while ($currentStart <= $end) { | ||||
|             $currentEnd   = app('navigation')->endOfPeriod($currentStart, $preferredRange); | ||||
|             $key          = $currentStart->isoFormat($format); | ||||
|             $return[$key] = '0'; | ||||
|             $currentStart = clone $currentEnd; | ||||
|             $currentStart->addDay()->startOfDay(); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Chart that groups expenses by the account. | ||||
|      */ | ||||
| @@ -224,21 +241,4 @@ class BudgetReportController extends Controller | ||||
| 
 | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     private function makeEntries(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         $return         = []; | ||||
|         $format         = app('navigation')->preferredCarbonLocalizedFormat($start, $end); | ||||
|         $preferredRange = app('navigation')->preferredRangeFormat($start, $end); | ||||
|         $currentStart   = clone $start; | ||||
|         while ($currentStart <= $end) { | ||||
|             $currentEnd   = app('navigation')->endOfPeriod($currentStart, $preferredRange); | ||||
|             $key          = $currentStart->isoFormat($format); | ||||
|             $return[$key] = '0'; | ||||
|             $currentStart = clone $currentEnd; | ||||
|             $currentStart->addDay()->startOfDay(); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -94,6 +94,11 @@ class CategoryController extends Controller | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     private function getDate(): Carbon | ||||
|     { | ||||
|         return today(config('app.timezone')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Shows the category chart on the front page. | ||||
|      * TODO test method for category refactor. | ||||
| @@ -141,66 +146,6 @@ class CategoryController extends Controller | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Chart for period for transactions without a category. | ||||
|      * TODO test me. | ||||
|      */ | ||||
|     public function reportPeriodNoCategory(Collection $accounts, Carbon $start, Carbon $end): JsonResponse | ||||
|     { | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache->addProperty($start); | ||||
|         $cache->addProperty($end); | ||||
|         $cache->addProperty('chart.category.period.no-cat'); | ||||
|         $cache->addProperty($accounts->pluck('id')->toArray()); | ||||
|         if ($cache->has()) { | ||||
|             return response()->json($cache->get()); | ||||
|         } | ||||
|         $data  = $this->reportPeriodChart($accounts, $start, $end, null); | ||||
| 
 | ||||
|         $cache->store($data); | ||||
| 
 | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Chart for a specific period. | ||||
|      * TODO test me, for category refactor. | ||||
|      * | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     public function specificPeriod(Category $category, Carbon $date): JsonResponse | ||||
|     { | ||||
|         $range          = app('navigation')->getViewRange(false); | ||||
|         $start          = app('navigation')->startOfPeriod($date, $range); | ||||
|         $end            = session()->get('end'); | ||||
|         if ($end < $start) { | ||||
|             [$end, $start] = [$start, $end]; | ||||
|         } | ||||
| 
 | ||||
|         $cache          = new CacheProperties(); | ||||
|         $cache->addProperty($start); | ||||
|         $cache->addProperty($end); | ||||
|         $cache->addProperty($category->id); | ||||
|         $cache->addProperty('chart.category.period-chart'); | ||||
|         if ($cache->has()) { | ||||
|             return response()->json($cache->get()); | ||||
|         } | ||||
| 
 | ||||
|         /** @var WholePeriodChartGenerator $chartGenerator */ | ||||
|         $chartGenerator = app(WholePeriodChartGenerator::class); | ||||
|         $chartData      = $chartGenerator->generate($category, $start, $end); | ||||
|         $data           = $this->generator->multiSet($chartData); | ||||
| 
 | ||||
|         $cache->store($data); | ||||
| 
 | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     private function getDate(): Carbon | ||||
|     { | ||||
|         return today(config('app.timezone')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Generate report chart for either with or without category. | ||||
|      */ | ||||
| @@ -279,4 +224,59 @@ class CategoryController extends Controller | ||||
| 
 | ||||
|         return $this->generator->multiSet($chartData); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Chart for period for transactions without a category. | ||||
|      * TODO test me. | ||||
|      */ | ||||
|     public function reportPeriodNoCategory(Collection $accounts, Carbon $start, Carbon $end): JsonResponse | ||||
|     { | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache->addProperty($start); | ||||
|         $cache->addProperty($end); | ||||
|         $cache->addProperty('chart.category.period.no-cat'); | ||||
|         $cache->addProperty($accounts->pluck('id')->toArray()); | ||||
|         if ($cache->has()) { | ||||
|             return response()->json($cache->get()); | ||||
|         } | ||||
|         $data  = $this->reportPeriodChart($accounts, $start, $end, null); | ||||
| 
 | ||||
|         $cache->store($data); | ||||
| 
 | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Chart for a specific period. | ||||
|      * TODO test me, for category refactor. | ||||
|      * | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     public function specificPeriod(Category $category, Carbon $date): JsonResponse | ||||
|     { | ||||
|         $range          = app('navigation')->getViewRange(false); | ||||
|         $start          = app('navigation')->startOfPeriod($date, $range); | ||||
|         $end            = session()->get('end'); | ||||
|         if ($end < $start) { | ||||
|             [$end, $start] = [$start, $end]; | ||||
|         } | ||||
| 
 | ||||
|         $cache          = new CacheProperties(); | ||||
|         $cache->addProperty($start); | ||||
|         $cache->addProperty($end); | ||||
|         $cache->addProperty($category->id); | ||||
|         $cache->addProperty('chart.category.period-chart'); | ||||
|         if ($cache->has()) { | ||||
|             return response()->json($cache->get()); | ||||
|         } | ||||
| 
 | ||||
|         /** @var WholePeriodChartGenerator $chartGenerator */ | ||||
|         $chartGenerator = app(WholePeriodChartGenerator::class); | ||||
|         $chartData      = $chartGenerator->generate($category, $start, $end); | ||||
|         $data           = $this->generator->multiSet($chartData); | ||||
| 
 | ||||
|         $cache->store($data); | ||||
| 
 | ||||
|         return response()->json($data); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -269,6 +269,26 @@ class CategoryReportController extends Controller | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO duplicate function
 | ||||
|      */ | ||||
|     private function makeEntries(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         $return         = []; | ||||
|         $format         = app('navigation')->preferredCarbonLocalizedFormat($start, $end); | ||||
|         $preferredRange = app('navigation')->preferredRangeFormat($start, $end); | ||||
|         $currentStart   = clone $start; | ||||
|         while ($currentStart <= $end) { | ||||
|             $currentEnd   = app('navigation')->endOfPeriod($currentStart, $preferredRange); | ||||
|             $key          = $currentStart->isoFormat($format); | ||||
|             $return[$key] = '0'; | ||||
|             $currentStart = clone $currentEnd; | ||||
|             $currentStart->addDay()->startOfDay(); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     public function sourceExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): JsonResponse | ||||
|     { | ||||
|         $result = []; | ||||
| @@ -324,24 +344,4 @@ class CategoryReportController extends Controller | ||||
| 
 | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO duplicate function
 | ||||
|      */ | ||||
|     private function makeEntries(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         $return         = []; | ||||
|         $format         = app('navigation')->preferredCarbonLocalizedFormat($start, $end); | ||||
|         $preferredRange = app('navigation')->preferredRangeFormat($start, $end); | ||||
|         $currentStart   = clone $start; | ||||
|         while ($currentStart <= $end) { | ||||
|             $currentEnd   = app('navigation')->endOfPeriod($currentStart, $preferredRange); | ||||
|             $key          = $currentStart->isoFormat($format); | ||||
|             $return[$key] = '0'; | ||||
|             $currentStart = clone $currentEnd; | ||||
|             $currentStart->addDay()->startOfDay(); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -212,6 +212,44 @@ class DoubleReportController extends Controller | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO duplicate function
 | ||||
|      */ | ||||
|     private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string | ||||
|     { | ||||
|         /** @var Account $account */ | ||||
|         foreach ($accounts as $account) { | ||||
|             if ($account->name === $name && $account->id !== $id) { | ||||
|                 return $account->name; | ||||
|             } | ||||
|             if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) { | ||||
|                 return $account->iban; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $name; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO duplicate function
 | ||||
|      */ | ||||
|     private function makeEntries(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         $return         = []; | ||||
|         $format         = app('navigation')->preferredCarbonLocalizedFormat($start, $end); | ||||
|         $preferredRange = app('navigation')->preferredRangeFormat($start, $end); | ||||
|         $currentStart   = clone $start; | ||||
|         while ($currentStart <= $end) { | ||||
|             $currentEnd   = app('navigation')->endOfPeriod($currentStart, $preferredRange); | ||||
|             $key          = $currentStart->isoFormat($format); | ||||
|             $return[$key] = '0'; | ||||
|             $currentStart = clone $currentEnd; | ||||
|             $currentStart->addDay()->startOfDay(); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     public function tagExpense(Collection $accounts, Collection $others, Carbon $start, Carbon $end): JsonResponse | ||||
|     { | ||||
|         $result           = []; | ||||
| @@ -317,42 +355,4 @@ class DoubleReportController extends Controller | ||||
| 
 | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO duplicate function
 | ||||
|      */ | ||||
|     private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string | ||||
|     { | ||||
|         /** @var Account $account */ | ||||
|         foreach ($accounts as $account) { | ||||
|             if ($account->name === $name && $account->id !== $id) { | ||||
|                 return $account->name; | ||||
|             } | ||||
|             if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) { | ||||
|                 return $account->iban; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $name; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO duplicate function
 | ||||
|      */ | ||||
|     private function makeEntries(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         $return         = []; | ||||
|         $format         = app('navigation')->preferredCarbonLocalizedFormat($start, $end); | ||||
|         $preferredRange = app('navigation')->preferredRangeFormat($start, $end); | ||||
|         $currentStart   = clone $start; | ||||
|         while ($currentStart <= $end) { | ||||
|             $currentEnd   = app('navigation')->endOfPeriod($currentStart, $preferredRange); | ||||
|             $key          = $currentStart->isoFormat($format); | ||||
|             $return[$key] = '0'; | ||||
|             $currentStart = clone $currentEnd; | ||||
|             $currentStart->addDay()->startOfDay(); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -102,27 +102,27 @@ class ExpenseReportController extends Controller | ||||
|             /** @var Account $exp */ | ||||
|             $exp                              = $combination->first(); | ||||
|             $chartData[$exp->id.'-in']        = [ | ||||
|                 'label'   => sprintf('%s (%s)', $name, (string) trans('firefly.income')), | ||||
|                 'label'   => sprintf('%s (%s)', $name, (string)trans('firefly.income')), | ||||
|                 'type'    => 'bar', | ||||
|                 'yAxisID' => 'y-axis-0', | ||||
|                 'entries' => [], | ||||
|             ]; | ||||
|             $chartData[$exp->id.'-out']       = [ | ||||
|                 'label'   => sprintf('%s (%s)', $name, (string) trans('firefly.expenses')), | ||||
|                 'label'   => sprintf('%s (%s)', $name, (string)trans('firefly.expenses')), | ||||
|                 'type'    => 'bar', | ||||
|                 'yAxisID' => 'y-axis-0', | ||||
|                 'entries' => [], | ||||
|             ]; | ||||
|             // total in, total out:
 | ||||
|             $chartData[$exp->id.'-total-in']  = [ | ||||
|                 'label'   => sprintf('%s (%s)', $name, (string) trans('firefly.sum_of_income')), | ||||
|                 'label'   => sprintf('%s (%s)', $name, (string)trans('firefly.sum_of_income')), | ||||
|                 'type'    => 'line', | ||||
|                 'fill'    => false, | ||||
|                 'yAxisID' => 'y-axis-1', | ||||
|                 'entries' => [], | ||||
|             ]; | ||||
|             $chartData[$exp->id.'-total-out'] = [ | ||||
|                 'label'   => sprintf('%s (%s)', $name, (string) trans('firefly.sum_of_expenses')), | ||||
|                 'label'   => sprintf('%s (%s)', $name, (string)trans('firefly.sum_of_expenses')), | ||||
|                 'type'    => 'line', | ||||
|                 'fill'    => false, | ||||
|                 'yAxisID' => 'y-axis-1', | ||||
|   | ||||
| @@ -274,6 +274,26 @@ class TagReportController extends Controller | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO duplicate function
 | ||||
|      */ | ||||
|     private function makeEntries(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         $return         = []; | ||||
|         $format         = app('navigation')->preferredCarbonLocalizedFormat($start, $end); | ||||
|         $preferredRange = app('navigation')->preferredRangeFormat($start, $end); | ||||
|         $currentStart   = clone $start; | ||||
|         while ($currentStart <= $end) { | ||||
|             $currentEnd   = app('navigation')->endOfPeriod($currentStart, $preferredRange); | ||||
|             $key          = $currentStart->isoFormat($format); | ||||
|             $return[$key] = '0'; | ||||
|             $currentStart = clone $currentEnd; | ||||
|             $currentStart->addDay()->startOfDay(); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
|     public function sourceExpense(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): JsonResponse | ||||
|     { | ||||
|         $result = []; | ||||
| @@ -381,24 +401,4 @@ class TagReportController extends Controller | ||||
| 
 | ||||
|         return response()->json($data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO duplicate function
 | ||||
|      */ | ||||
|     private function makeEntries(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         $return         = []; | ||||
|         $format         = app('navigation')->preferredCarbonLocalizedFormat($start, $end); | ||||
|         $preferredRange = app('navigation')->preferredRangeFormat($start, $end); | ||||
|         $currentStart   = clone $start; | ||||
|         while ($currentStart <= $end) { | ||||
|             $currentEnd   = app('navigation')->endOfPeriod($currentStart, $preferredRange); | ||||
|             $key          = $currentStart->isoFormat($format); | ||||
|             $return[$key] = '0'; | ||||
|             $currentStart = clone $currentEnd; | ||||
|             $currentStart->addDay()->startOfDay(); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -140,21 +140,6 @@ class DebugController extends Controller | ||||
|         return view('debug', compact('table', 'now', 'logContent')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Flash all types of messages. | ||||
|      * | ||||
|      * @return Redirector|RedirectResponse | ||||
|      */ | ||||
|     public function testFlash(Request $request) | ||||
|     { | ||||
|         $request->session()->flash('success', 'This is a success message.'); | ||||
|         $request->session()->flash('info', 'This is an info message.'); | ||||
|         $request->session()->flash('warning', 'This is a warning.'); | ||||
|         $request->session()->flash('error', 'This is an error!'); | ||||
| 
 | ||||
|         return redirect(route('home')); | ||||
|     } | ||||
| 
 | ||||
|     private function generateTable(): string | ||||
|     { | ||||
|         // system information:
 | ||||
| @@ -202,6 +187,7 @@ class DebugController extends Controller | ||||
|         try { | ||||
|             if (file_exists('/var/www/counter-main.txt')) { | ||||
|                 $return['build'] = trim((string)file_get_contents('/var/www/counter-main.txt')); | ||||
|                 app('log')->debug(sprintf('build is now "%s"', $return['build'])); | ||||
|             } | ||||
|         } catch (\Exception $e) { // @phpstan-ignore-line
 | ||||
|             app('log')->debug('Could not check build counter, but thats ok.'); | ||||
| @@ -336,4 +322,19 @@ class DebugController extends Controller | ||||
| 
 | ||||
|         return implode(' ', $flags); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Flash all types of messages. | ||||
|      * | ||||
|      * @return Redirector|RedirectResponse | ||||
|      */ | ||||
|     public function testFlash(Request $request) | ||||
|     { | ||||
|         $request->session()->flash('success', 'This is a success message.'); | ||||
|         $request->session()->flash('info', 'This is an info message.'); | ||||
|         $request->session()->flash('warning', 'This is a warning.'); | ||||
|         $request->session()->flash('error', 'This is an error!'); | ||||
| 
 | ||||
|         return redirect(route('home')); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -67,7 +67,7 @@ class IndexController extends Controller | ||||
|     public function export(): LaravelResponse|RedirectResponse | ||||
|     { | ||||
|         if (auth()->user()->hasRole('demo')) { | ||||
|             session()->flash('info', (string) trans('firefly.demo_user_export')); | ||||
|             session()->flash('info', (string)trans('firefly.demo_user_export')); | ||||
| 
 | ||||
|             return redirect(route('export.index')); | ||||
|         } | ||||
|   | ||||
| @@ -129,7 +129,7 @@ class BoxController extends Controller | ||||
|                 app('log')->debug('Left to spend is positive or zero!'); | ||||
|                 $boxTitle         = (string)trans('firefly.left_to_spend'); | ||||
|                 $activeDaysLeft   = $this->activeDaysLeft($start, $end);   // see method description.
 | ||||
|                 $display          = 1; // not overspent
 | ||||
|                 $display          = 1;                                     // not overspent
 | ||||
|                 $leftPerDayAmount = bcdiv($leftToSpendAmount, (string)$activeDaysLeft); | ||||
|                 app('log')->debug(sprintf('Left to spend per day is %s', $leftPerDayAmount)); | ||||
|             } | ||||
|   | ||||
| @@ -176,6 +176,7 @@ class ReconcileController extends Controller | ||||
|         } | ||||
|         $startDate      = clone $start; | ||||
|         $startDate->subDay(); | ||||
|         $end->endOfDay(); | ||||
| 
 | ||||
|         $currency       = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); | ||||
|         $startBalance   = app('steam')->bcround(app('steam')->balance($account, $startDate), $currency->decimal_places); | ||||
|   | ||||
| @@ -71,13 +71,13 @@ class RecurrenceController extends Controller | ||||
|         $start                         = Carbon::createFromFormat('Y-m-d', $request->get('start')); | ||||
|         $end                           = Carbon::createFromFormat('Y-m-d', $request->get('end')); | ||||
|         $firstDate                     = Carbon::createFromFormat('Y-m-d', $request->get('first_date')); | ||||
|         $endDate                       = '' !== (string) $request->get('end_date') ? Carbon::createFromFormat('Y-m-d', $request->get('end_date')) : null; | ||||
|         $endsAt                        = (string) $request->get('ends'); | ||||
|         $endDate                       = '' !== (string)$request->get('end_date') ? Carbon::createFromFormat('Y-m-d', $request->get('end_date')) : null; | ||||
|         $endsAt                        = (string)$request->get('ends'); | ||||
|         $repetitionType                = explode(',', $request->get('type'))[0]; | ||||
|         $repetitions                   = (int) $request->get('reps'); | ||||
|         $weekend                       = (int) $request->get('weekend'); | ||||
|         $repetitions                   = (int)$request->get('reps'); | ||||
|         $weekend                       = (int)$request->get('weekend'); | ||||
|         $repetitionMoment              = ''; | ||||
|         $skip                          = (int) $request->get('skip'); | ||||
|         $skip                          = (int)$request->get('skip'); | ||||
|         $skip                          = $skip < 0 || $skip > 31 ? 0 : $skip; | ||||
|         $weekend                       = $weekend < 1 || $weekend > 4 ? 1 : $weekend; | ||||
| 
 | ||||
| @@ -147,7 +147,7 @@ class RecurrenceController extends Controller | ||||
|      */ | ||||
|     public function suggest(Request $request): JsonResponse | ||||
|     { | ||||
|         $string      = '' === (string) $request->get('date') ? date('Y-m-d') : (string) $request->get('date'); | ||||
|         $string      = '' === (string)$request->get('date') ? date('Y-m-d') : (string)$request->get('date'); | ||||
|         $today       = today(config('app.timezone'))->startOfDay(); | ||||
| 
 | ||||
|         try { | ||||
| @@ -159,37 +159,37 @@ class RecurrenceController extends Controller | ||||
|             return response()->json(); | ||||
|         } | ||||
|         $date->startOfDay(); | ||||
|         $preSelected = (string) $request->get('pre_select'); | ||||
|         $preSelected = (string)$request->get('pre_select'); | ||||
|         $locale      = app('steam')->getLocale(); | ||||
| 
 | ||||
|         app('log')->debug(sprintf('date = %s, today = %s. date > today? %s', $date->toAtomString(), $today->toAtomString(), var_export($date > $today, true))); | ||||
|         app('log')->debug(sprintf('past = true? %s', var_export('true' === (string) $request->get('past'), true))); | ||||
|         app('log')->debug(sprintf('past = true? %s', var_export('true' === (string)$request->get('past'), true))); | ||||
| 
 | ||||
|         $result      = []; | ||||
|         if ($date > $today || 'true' === (string) $request->get('past')) { | ||||
|         if ($date > $today || 'true' === (string)$request->get('past')) { | ||||
|             app('log')->debug('Will fill dropdown.'); | ||||
|             $weekly     = sprintf('weekly,%s', $date->dayOfWeekIso); | ||||
|             $monthly    = sprintf('monthly,%s', $date->day); | ||||
|             $dayOfWeek  = (string) trans(sprintf('config.dow_%s', $date->dayOfWeekIso)); | ||||
|             $dayOfWeek  = (string)trans(sprintf('config.dow_%s', $date->dayOfWeekIso)); | ||||
|             $ndom       = sprintf('ndom,%s,%s', $date->weekOfMonth, $date->dayOfWeekIso); | ||||
|             $yearly     = sprintf('yearly,%s', $date->format('Y-m-d')); | ||||
|             $yearlyDate = $date->isoFormat((string) trans('config.month_and_day_no_year_js', [], $locale)); | ||||
|             $yearlyDate = $date->isoFormat((string)trans('config.month_and_day_no_year_js', [], $locale)); | ||||
|             $result     = [ | ||||
|                 'daily'  => ['label' => (string) trans('firefly.recurring_daily'), 'selected' => str_starts_with($preSelected, 'daily')], | ||||
|                 'daily'  => ['label' => (string)trans('firefly.recurring_daily'), 'selected' => str_starts_with($preSelected, 'daily')], | ||||
|                 $weekly  => [ | ||||
|                     'label'    => (string) trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek]), | ||||
|                     'label'    => (string)trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek]), | ||||
|                     'selected' => str_starts_with($preSelected, 'weekly'), | ||||
|                 ], | ||||
|                 $monthly => [ | ||||
|                     'label'    => (string) trans('firefly.recurring_monthly', ['dayOfMonth' => $date->day]), | ||||
|                     'label'    => (string)trans('firefly.recurring_monthly', ['dayOfMonth' => $date->day]), | ||||
|                     'selected' => str_starts_with($preSelected, 'monthly'), | ||||
|                 ], | ||||
|                 $ndom    => [ | ||||
|                     'label'    => (string) trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $date->weekOfMonth]), | ||||
|                     'label'    => (string)trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $date->weekOfMonth]), | ||||
|                     'selected' => str_starts_with($preSelected, 'ndom'), | ||||
|                 ], | ||||
|                 $yearly  => [ | ||||
|                     'label'    => (string) trans('firefly.recurring_yearly', ['date' => $yearlyDate]), | ||||
|                     'label'    => (string)trans('firefly.recurring_yearly', ['date' => $yearlyDate]), | ||||
|                     'selected' => str_starts_with($preSelected, 'yearly'), | ||||
|                 ], | ||||
|             ]; | ||||
|   | ||||
| @@ -141,24 +141,6 @@ class IndexController extends Controller | ||||
|         return view('piggy-banks.index', compact('piggyBanks', 'accounts')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the order of a piggy bank. | ||||
|      */ | ||||
|     public function setOrder(Request $request, PiggyBank $piggyBank): JsonResponse | ||||
|     { | ||||
|         $objectGroupTitle = (string)$request->get('objectGroupTitle'); | ||||
|         $newOrder         = (int)$request->get('order'); | ||||
|         $this->piggyRepos->setOrder($piggyBank, $newOrder); | ||||
|         if ('' !== $objectGroupTitle) { | ||||
|             $this->piggyRepos->setObjectGroup($piggyBank, $objectGroupTitle); | ||||
|         } | ||||
|         if ('' === $objectGroupTitle) { | ||||
|             $this->piggyRepos->removeObjectGroup($piggyBank); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json(['data' => 'OK']); | ||||
|     } | ||||
| 
 | ||||
|     private function makeSums(array $piggyBanks): array | ||||
|     { | ||||
|         $sums = []; | ||||
| @@ -193,4 +175,22 @@ class IndexController extends Controller | ||||
| 
 | ||||
|         return $piggyBanks; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the order of a piggy bank. | ||||
|      */ | ||||
|     public function setOrder(Request $request, PiggyBank $piggyBank): JsonResponse | ||||
|     { | ||||
|         $objectGroupTitle = (string)$request->get('objectGroupTitle'); | ||||
|         $newOrder         = (int)$request->get('order'); | ||||
|         $this->piggyRepos->setOrder($piggyBank, $newOrder); | ||||
|         if ('' !== $objectGroupTitle) { | ||||
|             $this->piggyRepos->setObjectGroup($piggyBank, $objectGroupTitle); | ||||
|         } | ||||
|         if ('' === $objectGroupTitle) { | ||||
|             $this->piggyRepos->removeObjectGroup($piggyBank); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json(['data' => 'OK']); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -49,7 +49,7 @@ class PreferencesController extends Controller | ||||
| 
 | ||||
|         $this->middleware( | ||||
|             static function ($request, $next) { | ||||
|                 app('view')->share('title', (string) trans('firefly.preferences')); | ||||
|                 app('view')->share('title', (string)trans('firefly.preferences')); | ||||
|                 app('view')->share('mainTitleIcon', 'fa-gear'); | ||||
| 
 | ||||
|                 return $next($request); | ||||
| @@ -72,8 +72,8 @@ class PreferencesController extends Controller | ||||
| 
 | ||||
|         /** @var Account $account */ | ||||
|         foreach ($accounts as $account) { | ||||
|             $type                                                                        = $account->accountType->type; | ||||
|             $role                                                                        = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role')); | ||||
|             $type                                                                       = $account->accountType->type; | ||||
|             $role                                                                       = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role')); | ||||
| 
 | ||||
|             if (in_array($type, [AccountType::MORTGAGE, AccountType::DEBT, AccountType::LOAN], true)) { | ||||
|                 $role = sprintf('opt_group_l_%s', $type); | ||||
| @@ -82,7 +82,7 @@ class PreferencesController extends Controller | ||||
|             if ('opt_group_' === $role) { | ||||
|                 $role = 'opt_group_defaultAsset'; | ||||
|             } | ||||
|             $groupedAccounts[(string) trans(sprintf('firefly.%s', $role))][$account->id] = $account->name; | ||||
|             $groupedAccounts[(string)trans(sprintf('firefly.%s', $role))][$account->id] = $account->name; | ||||
|         } | ||||
|         ksort($groupedAccounts); | ||||
| 
 | ||||
| @@ -105,7 +105,7 @@ class PreferencesController extends Controller | ||||
|         if (is_array($fiscalYearStartStr)) { | ||||
|             $fiscalYearStartStr = '01-01'; | ||||
|         } | ||||
|         $fiscalYearStart       = sprintf('%s-%s', date('Y'), (string) $fiscalYearStartStr); | ||||
|         $fiscalYearStart       = sprintf('%s-%s', date('Y'), (string)$fiscalYearStartStr); | ||||
|         $tjOptionalFields      = app('preferences')->get('transaction_journal_optional_fields', [])->data; | ||||
|         $availableDarkModes    = config('firefly.available_dark_modes'); | ||||
| 
 | ||||
| @@ -120,12 +120,12 @@ class PreferencesController extends Controller | ||||
|         // list of locales also has "equal" which makes it equal to whatever the language is.
 | ||||
| 
 | ||||
|         try { | ||||
|             $locales = json_decode((string) file_get_contents(resource_path(sprintf('lang/%s/locales.json', $language))), true, 512, JSON_THROW_ON_ERROR); | ||||
|             $locales = json_decode((string)file_get_contents(resource_path(sprintf('lang/%s/locales.json', $language))), true, 512, JSON_THROW_ON_ERROR); | ||||
|         } catch (\JsonException $e) { | ||||
|             app('log')->error($e->getMessage()); | ||||
|             $locales = []; | ||||
|         } | ||||
|         $locales               = ['equal' => (string) trans('firefly.equal_to_language')] + $locales; | ||||
|         $locales               = ['equal' => (string)trans('firefly.equal_to_language')] + $locales; | ||||
|         // an important fallback is that the frontPageAccount array gets refilled automatically
 | ||||
|         // when it turns up empty.
 | ||||
|         if (0 === count($frontPageAccounts)) { | ||||
| @@ -158,7 +158,7 @@ class PreferencesController extends Controller | ||||
|         $frontPageAccounts = []; | ||||
|         if (is_array($request->get('frontPageAccounts')) && count($request->get('frontPageAccounts')) > 0) { | ||||
|             foreach ($request->get('frontPageAccounts') as $id) { | ||||
|                 $frontPageAccounts[] = (int) $id; | ||||
|                 $frontPageAccounts[] = (int)$id; | ||||
|             } | ||||
|             app('preferences')->set('frontPageAccounts', $frontPageAccounts); | ||||
|         } | ||||
| @@ -184,7 +184,7 @@ class PreferencesController extends Controller | ||||
| 
 | ||||
|         // slack URL:
 | ||||
|         if (!auth()->user()->hasRole('demo')) { | ||||
|             $url = (string) $request->get('slackUrl'); | ||||
|             $url = (string)$request->get('slackUrl'); | ||||
|             if (UrlValidator::isValidWebhookURL($url)) { | ||||
|                 app('preferences')->set('slack_webhook_url', $url); | ||||
|             } | ||||
| @@ -194,8 +194,8 @@ class PreferencesController extends Controller | ||||
|         } | ||||
| 
 | ||||
|         // custom fiscal year
 | ||||
|         $customFiscalYear  = 1 === (int) $request->get('customFiscalYear'); | ||||
|         $string            = strtotime((string) $request->get('fiscalYearStart')); | ||||
|         $customFiscalYear  = 1 === (int)$request->get('customFiscalYear'); | ||||
|         $string            = strtotime((string)$request->get('fiscalYearStart')); | ||||
|         if (false !== $string) { | ||||
|             $fiscalYearStart = date('m-d', $string); | ||||
|             app('preferences')->set('customFiscalYear', $customFiscalYear); | ||||
| @@ -204,7 +204,7 @@ class PreferencesController extends Controller | ||||
| 
 | ||||
|         // save page size:
 | ||||
|         app('preferences')->set('listPageSize', 50); | ||||
|         $listPageSize      = (int) $request->get('listPageSize'); | ||||
|         $listPageSize      = (int)$request->get('listPageSize'); | ||||
|         if ($listPageSize > 0 && $listPageSize < 1337) { | ||||
|             app('preferences')->set('listPageSize', $listPageSize); | ||||
|         } | ||||
| @@ -252,7 +252,7 @@ class PreferencesController extends Controller | ||||
|             app('preferences')->set('darkMode', $darkMode); | ||||
|         } | ||||
| 
 | ||||
|         session()->flash('success', (string) trans('firefly.saved_preferences')); | ||||
|         session()->flash('success', (string)trans('firefly.saved_preferences')); | ||||
|         app('preferences')->mark(); | ||||
| 
 | ||||
|         return redirect(route('preferences.index')); | ||||
|   | ||||
| @@ -497,6 +497,47 @@ class ProfileController extends Controller | ||||
|         return redirect(route('profile.index')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO duplicate code. | ||||
|      * | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function addToMFAHistory(string $mfaCode): void | ||||
|     { | ||||
|         /** @var array $mfaHistory */ | ||||
|         $mfaHistory   = app('preferences')->get('mfa_history', [])->data; | ||||
|         $entry        = [ | ||||
|             'time' => time(), | ||||
|             'code' => $mfaCode, | ||||
|         ]; | ||||
|         $mfaHistory[] = $entry; | ||||
| 
 | ||||
|         app('preferences')->set('mfa_history', $mfaHistory); | ||||
|         $this->filterMFAHistory(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Remove old entries from the preferences array. | ||||
|      */ | ||||
|     private function filterMFAHistory(): void | ||||
|     { | ||||
|         /** @var array $mfaHistory */ | ||||
|         $mfaHistory = app('preferences')->get('mfa_history', [])->data; | ||||
|         $newHistory = []; | ||||
|         $now        = time(); | ||||
|         foreach ($mfaHistory as $entry) { | ||||
|             $time = $entry['time']; | ||||
|             $code = $entry['code']; | ||||
|             if ($now - $time <= 300) { | ||||
|                 $newHistory[] = [ | ||||
|                     'time' => $time, | ||||
|                     'code' => $code, | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
|         app('preferences')->set('mfa_history', $newHistory); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Submit delete account. | ||||
|      * | ||||
| @@ -631,45 +672,4 @@ class ProfileController extends Controller | ||||
| 
 | ||||
|         return redirect(route('login')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO duplicate code. | ||||
|      * | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
|     private function addToMFAHistory(string $mfaCode): void | ||||
|     { | ||||
|         /** @var array $mfaHistory */ | ||||
|         $mfaHistory   = app('preferences')->get('mfa_history', [])->data; | ||||
|         $entry        = [ | ||||
|             'time' => time(), | ||||
|             'code' => $mfaCode, | ||||
|         ]; | ||||
|         $mfaHistory[] = $entry; | ||||
| 
 | ||||
|         app('preferences')->set('mfa_history', $mfaHistory); | ||||
|         $this->filterMFAHistory(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Remove old entries from the preferences array. | ||||
|      */ | ||||
|     private function filterMFAHistory(): void | ||||
|     { | ||||
|         /** @var array $mfaHistory */ | ||||
|         $mfaHistory = app('preferences')->get('mfa_history', [])->data; | ||||
|         $newHistory = []; | ||||
|         $now        = time(); | ||||
|         foreach ($mfaHistory as $entry) { | ||||
|             $time = $entry['time']; | ||||
|             $code = $entry['code']; | ||||
|             if ($now - $time <= 300) { | ||||
|                 $newHistory[] = [ | ||||
|                     'time' => $time, | ||||
|                     'code' => $code, | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
|         app('preferences')->set('mfa_history', $newHistory); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -283,8 +283,8 @@ class CategoryController extends Controller | ||||
|                     ]; | ||||
|                     ++$result[$key]['transactions']; | ||||
|                     $result[$key]['sum']       = bcadd($journal['amount'], $result[$key]['sum']); | ||||
|                     $result[$key]['avg']       = bcdiv($result[$key]['sum'], (string) $result[$key]['transactions']); | ||||
|                     $result[$key]['avg_float'] = (float) $result[$key]['avg']; // intentional float
 | ||||
|                     $result[$key]['avg']       = bcdiv($result[$key]['sum'], (string)$result[$key]['transactions']); | ||||
|                     $result[$key]['avg_float'] = (float)$result[$key]['avg']; // intentional float
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -333,8 +333,8 @@ class CategoryController extends Controller | ||||
|                     ]; | ||||
|                     ++$result[$key]['transactions']; | ||||
|                     $result[$key]['sum']       = bcadd($journal['amount'], $result[$key]['sum']); | ||||
|                     $result[$key]['avg']       = bcdiv($result[$key]['sum'], (string) $result[$key]['transactions']); | ||||
|                     $result[$key]['avg_float'] = (float) $result[$key]['avg']; | ||||
|                     $result[$key]['avg']       = bcdiv($result[$key]['sum'], (string)$result[$key]['transactions']); | ||||
|                     $result[$key]['avg_float'] = (float)$result[$key]['avg']; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -664,7 +664,7 @@ class CategoryController extends Controller | ||||
|                     $result[] = [ | ||||
|                         'description'              => $journal['description'], | ||||
|                         'transaction_group_id'     => $journal['transaction_group_id'], | ||||
|                         'amount_float'             => (float) $journal['amount'], | ||||
|                         'amount_float'             => (float)$journal['amount'], | ||||
|                         'amount'                   => $journal['amount'], | ||||
|                         'date'                     => $journal['date']->isoFormat($this->monthAndDayFormat), | ||||
|                         'date_sort'                => $journal['date']->format('Y-m-d'), | ||||
| @@ -712,7 +712,7 @@ class CategoryController extends Controller | ||||
|                     $result[] = [ | ||||
|                         'description'             => $journal['description'], | ||||
|                         'transaction_group_id'    => $journal['transaction_group_id'], | ||||
|                         'amount_float'            => (float) $journal['amount'], | ||||
|                         'amount_float'            => (float)$journal['amount'], | ||||
|                         'amount'                  => $journal['amount'], | ||||
|                         'date'                    => $journal['date']->isoFormat($this->monthAndDayFormat), | ||||
|                         'date_sort'               => $journal['date']->format('Y-m-d'), | ||||
|   | ||||
| @@ -41,7 +41,7 @@ class DoubleController extends Controller | ||||
| { | ||||
|     use AugumentData; | ||||
| 
 | ||||
|     protected AccountRepositoryInterface $accountRepository; | ||||
|     protected AccountRepositoryInterface  $accountRepository; | ||||
|     private OperationsRepositoryInterface $opsRepository; | ||||
| 
 | ||||
|     /** | ||||
| @@ -278,6 +278,24 @@ class DoubleController extends Controller | ||||
|         return view('reports.double.partials.accounts', compact('sums', 'report')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO this method is duplicated. | ||||
|      */ | ||||
|     private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string | ||||
|     { | ||||
|         /** @var Account $account */ | ||||
|         foreach ($accounts as $account) { | ||||
|             if ($account->name === $name && $account->id !== $id) { | ||||
|                 return $account->name; | ||||
|             } | ||||
|             if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) { | ||||
|                 return $account->iban; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $name; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return Factory|View | ||||
|      */ | ||||
| @@ -468,22 +486,4 @@ class DoubleController extends Controller | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * TODO this method is duplicated. | ||||
|      */ | ||||
|     private function getCounterpartName(Collection $accounts, int $id, string $name, ?string $iban): string | ||||
|     { | ||||
|         /** @var Account $account */ | ||||
|         foreach ($accounts as $account) { | ||||
|             if ($account->name === $name && $account->id !== $id) { | ||||
|                 return $account->name; | ||||
|             } | ||||
|             if (null !== $account->iban && $account->iban === $iban && $account->id !== $id) { | ||||
|                 return $account->iban; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $name; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -279,8 +279,8 @@ class TagController extends Controller | ||||
|                     ]; | ||||
|                     ++$result[$key]['transactions']; | ||||
|                     $result[$key]['sum']       = bcadd($journal['amount'], $result[$key]['sum']); | ||||
|                     $result[$key]['avg']       = bcdiv($result[$key]['sum'], (string) $result[$key]['transactions']); | ||||
|                     $result[$key]['avg_float'] = (float) $result[$key]['avg']; | ||||
|                     $result[$key]['avg']       = bcdiv($result[$key]['sum'], (string)$result[$key]['transactions']); | ||||
|                     $result[$key]['avg_float'] = (float)$result[$key]['avg']; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -329,8 +329,8 @@ class TagController extends Controller | ||||
|                     ]; | ||||
|                     ++$result[$key]['transactions']; | ||||
|                     $result[$key]['sum']       = bcadd($journal['amount'], $result[$key]['sum']); | ||||
|                     $result[$key]['avg']       = bcdiv($result[$key]['sum'], (string) $result[$key]['transactions']); | ||||
|                     $result[$key]['avg_float'] = (float) $result[$key]['avg']; | ||||
|                     $result[$key]['avg']       = bcdiv($result[$key]['sum'], (string)$result[$key]['transactions']); | ||||
|                     $result[$key]['avg_float'] = (float)$result[$key]['avg']; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -466,7 +466,7 @@ class TagController extends Controller | ||||
|                     $result[] = [ | ||||
|                         'description'              => $journal['description'], | ||||
|                         'transaction_group_id'     => $journal['transaction_group_id'], | ||||
|                         'amount_float'             => (float) $journal['amount'], | ||||
|                         'amount_float'             => (float)$journal['amount'], | ||||
|                         'amount'                   => $journal['amount'], | ||||
|                         'date'                     => $journal['date']->isoFormat($this->monthAndDayFormat), | ||||
|                         'date_sort'                => $journal['date']->format('Y-m-d'), | ||||
| @@ -514,7 +514,7 @@ class TagController extends Controller | ||||
|                     $result[] = [ | ||||
|                         'description'             => $journal['description'], | ||||
|                         'transaction_group_id'    => $journal['transaction_group_id'], | ||||
|                         'amount_float'            => (float) $journal['amount'], // intentional float.
 | ||||
|                         'amount_float'            => (float)$journal['amount'], // intentional float.
 | ||||
|                         'amount'                  => $journal['amount'], | ||||
|                         'date'                    => $journal['date']->isoFormat($this->monthAndDayFormat), | ||||
|                         'date_sort'               => $journal['date']->format('Y-m-d'), | ||||
|   | ||||
| @@ -159,7 +159,7 @@ class CreateController extends Controller | ||||
|         $oldActions   = $this->getActionsForBill($bill); | ||||
| 
 | ||||
|         // restore actions and triggers from old input:
 | ||||
|         if (null !== $request->old()) { | ||||
|         if (null !== $request->old() && is_array($request->old()) && count($request->old()) > 0) { | ||||
|             $oldTriggers = $this->getPreviousTriggers($request); | ||||
|             $oldActions  = $this->getPreviousActions($request); | ||||
|         } | ||||
|   | ||||
| @@ -132,29 +132,6 @@ class EditController extends Controller | ||||
|         return view('rules.rule.edit', compact('rule', 'subTitle', 'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update the rule. | ||||
|      * | ||||
|      * @return Redirector|RedirectResponse | ||||
|      */ | ||||
|     public function update(RuleFormRequest $request, Rule $rule) | ||||
|     { | ||||
|         $data     = $request->getRuleData(); | ||||
| 
 | ||||
|         $this->ruleRepos->update($rule, $data); | ||||
| 
 | ||||
|         session()->flash('success', (string)trans('firefly.updated_rule', ['title' => $rule->title])); | ||||
|         app('preferences')->mark(); | ||||
|         $redirect = redirect($this->getPreviousUrl('rules.edit.url')); | ||||
|         if (1 === (int)$request->get('return_to_edit')) { | ||||
|             session()->put('rules.edit.fromUpdate', true); | ||||
| 
 | ||||
|             $redirect = redirect(route('rules.edit', [$rule->id]))->withInput(['return_to_edit' => 1]); | ||||
|         } | ||||
| 
 | ||||
|         return $redirect; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
| @@ -196,4 +173,27 @@ class EditController extends Controller | ||||
| 
 | ||||
|         return $renderedEntries; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update the rule. | ||||
|      * | ||||
|      * @return Redirector|RedirectResponse | ||||
|      */ | ||||
|     public function update(RuleFormRequest $request, Rule $rule) | ||||
|     { | ||||
|         $data     = $request->getRuleData(); | ||||
| 
 | ||||
|         $this->ruleRepos->update($rule, $data); | ||||
| 
 | ||||
|         session()->flash('success', (string)trans('firefly.updated_rule', ['title' => $rule->title])); | ||||
|         app('preferences')->mark(); | ||||
|         $redirect = redirect($this->getPreviousUrl('rules.edit.url')); | ||||
|         if (1 === (int)$request->get('return_to_edit')) { | ||||
|             session()->put('rules.edit.fromUpdate', true); | ||||
| 
 | ||||
|             $redirect = redirect(route('rules.edit', [$rule->id]))->withInput(['return_to_edit' => 1]); | ||||
|         } | ||||
| 
 | ||||
|         return $redirect; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -129,26 +129,6 @@ class InstallController extends Controller | ||||
|         return response()->json($response); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create specific RSA keys. | ||||
|      */ | ||||
|     public function keys(): void | ||||
|     { | ||||
|         $key                      = RSA::createKey(4096); | ||||
| 
 | ||||
|         [$publicKey, $privateKey] = [ | ||||
|             Passport::keyPath('oauth-public.key'), | ||||
|             Passport::keyPath('oauth-private.key'), | ||||
|         ]; | ||||
| 
 | ||||
|         if (file_exists($publicKey) || file_exists($privateKey)) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         file_put_contents($publicKey, (string)$key->getPublicKey()); | ||||
|         file_put_contents($privateKey, $key->toString('PKCS1')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
| @@ -173,4 +153,24 @@ class InstallController extends Controller | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create specific RSA keys. | ||||
|      */ | ||||
|     public function keys(): void | ||||
|     { | ||||
|         $key                      = RSA::createKey(4096); | ||||
| 
 | ||||
|         [$publicKey, $privateKey] = [ | ||||
|             Passport::keyPath('oauth-public.key'), | ||||
|             Passport::keyPath('oauth-private.key'), | ||||
|         ]; | ||||
| 
 | ||||
|         if (file_exists($publicKey) || file_exists($privateKey)) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         file_put_contents($publicKey, (string)$key->getPublicKey()); | ||||
|         file_put_contents($privateKey, $key->toString('PKCS1')); | ||||
|     } | ||||
| } | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user