mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 10:47:00 +00:00 
			
		
		
		
	Compare commits
	
		
			64 Commits
		
	
	
		
			develop-20
			...
			develop-20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c956df7790 | ||
|  | 0fdccec6a8 | ||
|  | 8ded54d7a8 | ||
|  | bb1b4ca5ca | ||
|  | e90d60113b | ||
|  | d95dada0e0 | ||
|  | 8722456595 | ||
|  | b5ad226451 | ||
|  | ddb0e66651 | ||
|  | e802899608 | ||
|  | 0894d3bf42 | ||
|  | 80bcfd3bcd | ||
|  | 8c410f42bd | ||
|  | 7a1021dffc | ||
|  | 63883c9a84 | ||
|  | 50d7f9d1ec | ||
|  | ebc7ea0eb6 | ||
|  | b905efd0aa | ||
|  | 93085599b7 | ||
|  | 8a8bbaf827 | ||
|  | 96a66b894a | ||
|  | 1ba641c279 | ||
|  | 6c5ddfcb8a | ||
|  | 65ddc246dc | ||
|  | e4aff5ff4c | ||
|  | dfece91541 | ||
|  | 6ddda13c3a | ||
|  | 46219c4678 | ||
|  | a1595d0647 | ||
|  | 4ee9f9bb27 | ||
|  | bcaa0bddea | ||
|  | 01cce49070 | ||
|  | 293be04d40 | ||
|  | 44a00ec8eb | ||
|  | d84e772945 | ||
|  | b475f6c51d | ||
|  | 2712662510 | ||
|  | 1f2eeba862 | ||
|  | 46a60134f4 | ||
|  | f3f7820816 | ||
|  | a57f8076b2 | ||
|  | 517afa2273 | ||
|  | 2142b23aec | ||
|  | 1bec43b111 | ||
|  | d2978a5ee8 | ||
|  | 39b61c71e8 | ||
|  | fa2c964790 | ||
|  | 134aeb3a46 | ||
|  | 6f6e1a4ff4 | ||
|  | b743bf3d9e | ||
|  | 84ee6f16c9 | ||
|  | 9fe39e42b3 | ||
|  | 4013c7e316 | ||
|  | 0b76747531 | ||
|  | 3129756b37 | ||
|  | b0df383004 | ||
|  | 9c5b1df86c | ||
|  | 5854e24775 | ||
|  | 3f873422f2 | ||
|  | f898990773 | ||
|  | 11d2f8d471 | ||
|  | 0a48c0c20f | ||
|  | 8bc764d6ef | ||
|  | 4b4f568558 | 
							
								
								
									
										86
									
								
								.ci/php-cs-fixer/composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										86
									
								
								.ci/php-cs-fixer/composer.lock
									
									
									
										generated
									
									
									
								
							| @@ -151,16 +151,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "composer/semver", | ||||
|             "version": "3.4.3", | ||||
|             "version": "3.4.4", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/composer/semver.git", | ||||
|                 "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" | ||||
|                 "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", | ||||
|                 "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", | ||||
|                 "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", | ||||
|                 "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -212,7 +212,7 @@ | ||||
|             "support": { | ||||
|                 "irc": "ircs://irc.libera.chat:6697/composer", | ||||
|                 "issues": "https://github.com/composer/semver/issues", | ||||
|                 "source": "https://github.com/composer/semver/tree/3.4.3" | ||||
|                 "source": "https://github.com/composer/semver/tree/3.4.4" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -222,13 +222,9 @@ | ||||
|                 { | ||||
|                     "url": "https://github.com/composer", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/composer/composer", | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-09-19T14:15:21+00:00" | ||||
|             "time": "2025-08-20T19:15:30+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "composer/xdebug-handler", | ||||
| @@ -959,23 +955,23 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "react/promise", | ||||
|             "version": "v3.2.0", | ||||
|             "version": "v3.3.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/reactphp/promise.git", | ||||
|                 "reference": "8a164643313c71354582dc850b42b33fa12a4b63" | ||||
|                 "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", | ||||
|                 "reference": "8a164643313c71354582dc850b42b33fa12a4b63", | ||||
|                 "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", | ||||
|                 "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
|                 "php": ">=7.1.0" | ||||
|             }, | ||||
|             "require-dev": { | ||||
|                 "phpstan/phpstan": "1.10.39 || 1.4.10", | ||||
|                 "phpstan/phpstan": "1.12.28 || 1.4.10", | ||||
|                 "phpunit/phpunit": "^9.6 || ^7.5" | ||||
|             }, | ||||
|             "type": "library", | ||||
| @@ -1020,7 +1016,7 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/reactphp/promise/issues", | ||||
|                 "source": "https://github.com/reactphp/promise/tree/v3.2.0" | ||||
|                 "source": "https://github.com/reactphp/promise/tree/v3.3.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -1028,7 +1024,7 @@ | ||||
|                     "type": "open_collective" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-05-24T10:39:05+00:00" | ||||
|             "time": "2025-08-19T18:57:03+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "react/socket", | ||||
| @@ -1787,7 +1783,7 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-ctype", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-ctype.git", | ||||
| @@ -1846,7 +1842,7 @@ | ||||
|                 "portable" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -1857,6 +1853,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
| @@ -1866,16 +1866,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-intl-grapheme", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-intl-grapheme.git", | ||||
|                 "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" | ||||
|                 "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", | ||||
|                 "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", | ||||
|                 "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", | ||||
|                 "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -1924,7 +1924,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -1935,16 +1935,20 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-09-09T11:45:10+00:00" | ||||
|             "time": "2025-06-27T09:58:17+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-intl-normalizer", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-intl-normalizer.git", | ||||
| @@ -2005,7 +2009,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -2016,6 +2020,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
| @@ -2025,7 +2033,7 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-mbstring", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-mbstring.git", | ||||
| @@ -2086,7 +2094,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -2097,6 +2105,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
| @@ -2106,7 +2118,7 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-php80", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-php80.git", | ||||
| @@ -2166,7 +2178,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -2177,6 +2189,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
| @@ -2186,7 +2202,7 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-php81", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-php81.git", | ||||
| @@ -2242,7 +2258,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -2253,6 +2269,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/label-actions.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/label-actions.yml
									
									
									
									
										vendored
									
									
								
							| @@ -25,7 +25,7 @@ feature: | ||||
|  | ||||
|       This issue has been marked as a feature request. | ||||
|  | ||||
|       If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates. | ||||
|       If you come across this issue, please be aware there is NO need to reply with "+1" or "I need this too" or "any updates?" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates. | ||||
|  | ||||
|       Thank you for your contributions. | ||||
|  | ||||
| @@ -39,7 +39,7 @@ epic: | ||||
|  | ||||
|       This issue has been marked as an epic. In epics, large amounts of works are collected that will be part of a major new feature. If you have more ideas that could be a part of this epic, feel free to reply. | ||||
|  | ||||
|       *However*, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. | ||||
|       *However*, please be aware there is NO need to reply with "+1" or "I need this too" or "any updates?" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. | ||||
|  | ||||
|       If you are merely interested in this epic's progress, you can subscribe to this issue to get updates. | ||||
|  | ||||
| @@ -56,7 +56,7 @@ enhancement: | ||||
|  | ||||
|       This issue has been marked as an enhancement. | ||||
|  | ||||
|       If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates. | ||||
|       If you come across this issue, please be aware there is NO need to reply with "+1" or "I need this too" or "any updates?" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates. | ||||
|  | ||||
|       Thank you for your contributions. | ||||
|  | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/release-notes/alpha.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/release-notes/alpha.md
									
									
									
									
										vendored
									
									
								
							| @@ -16,6 +16,10 @@ Alpha releases are created to test new features and fixes before they are includ | ||||
| 
 | ||||
| The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/). | ||||
| 
 | ||||
| ## Develop with Firefly III | ||||
| 
 | ||||
| Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III. | ||||
| 
 | ||||
| ## Support Firefly III | ||||
| 
 | ||||
| Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration. | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/release-notes/beta.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/release-notes/beta.md
									
									
									
									
										vendored
									
									
								
							| @@ -16,6 +16,10 @@ Alpha releases are created to test new features and fixes before they are includ | ||||
| 
 | ||||
| The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/). | ||||
| 
 | ||||
| ## Develop with Firefly III | ||||
| 
 | ||||
| Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III. | ||||
| 
 | ||||
| ## Support Firefly III | ||||
| 
 | ||||
| Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration. | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/release-notes/branch.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/release-notes/branch.md
									
									
									
									
										vendored
									
									
								
							| @@ -16,6 +16,10 @@ There is no changelog for this release, as it is not final. However, [changelog. | ||||
| 
 | ||||
| The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/). | ||||
| 
 | ||||
| ## Develop with Firefly III | ||||
| 
 | ||||
| Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III. | ||||
| 
 | ||||
| ## Support Firefly III | ||||
| 
 | ||||
| Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration. | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/release-notes/develop.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/release-notes/develop.md
									
									
									
									
										vendored
									
									
								
							| @@ -16,6 +16,10 @@ The changelog for this release may not be up-to-date, so it is not included. How | ||||
| 
 | ||||
| The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/). | ||||
| 
 | ||||
| ## Develop with Firefly III | ||||
| 
 | ||||
| Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III. | ||||
| 
 | ||||
| ## Support Firefly III | ||||
| 
 | ||||
| Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration. | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/release-notes/release.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/release-notes/release.md
									
									
									
									
										vendored
									
									
								
							| @@ -11,6 +11,10 @@ Welcome to release %version of Firefly III. It contains the latest fixes, transl | ||||
| 
 | ||||
| The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/). | ||||
| 
 | ||||
| ## Develop with Firefly III | ||||
| 
 | ||||
| Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III. | ||||
| 
 | ||||
| ## Support Firefly III | ||||
| 
 | ||||
| Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration. | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/depsreview.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/depsreview.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,7 +9,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: 'Checkout repository' | ||||
|         uses: actions/checkout@v4 | ||||
|         uses: actions/checkout@v5 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: 'Dependency review' | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							| @@ -21,7 +21,7 @@ jobs: | ||||
|       discussions: write | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: JC5/lock-threads@v6.0.2 | ||||
|       - uses: JC5/lock-threads@v6.0.6 | ||||
|         with: | ||||
|           issue-inactive-days: 21 | ||||
|           pr-inactive-days: 21 | ||||
|   | ||||
							
								
								
									
										16
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -50,7 +50,7 @@ jobs: | ||||
|           git pull | ||||
|           echo "Current branch is $(git branch --show-current)" | ||||
|         env: | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }} | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }} | ||||
|       - name: Configure Git | ||||
|         run: | | ||||
|           # do some configuration | ||||
| @@ -118,7 +118,7 @@ jobs: | ||||
|         env: | ||||
|           FIREFLY_III_ROOT: /github/workspace | ||||
|           GH_TOKEN: "" | ||||
|           FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }} | ||||
|           FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }} | ||||
|       - name: Generate JSON v1 | ||||
|         id: json-v1 | ||||
|         uses: JC5/firefly-iii-dev@main | ||||
| @@ -221,7 +221,7 @@ jobs: | ||||
|           echo "tarName=$tarName" >> "$GITHUB_ENV" | ||||
|           echo "BRANCH_NAME=$BRANCH_NAME" >> "$GITHUB_ENV" | ||||
|         env: | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }} | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }} | ||||
|       - name: Commit all changes | ||||
|         run: | | ||||
|           # add all content, except output.txt (this contains the changelog and/or the download instructions) | ||||
| @@ -232,12 +232,12 @@ jobs: | ||||
|           git commit -m "🤖 Auto commit for release '$version' on $(date +'%Y-%m-%d')" || true | ||||
|           git push | ||||
|         env: | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }} | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }} | ||||
|       - name: Generate release description | ||||
|         id: release-description | ||||
|         uses: JC5/firefly-iii-dev@main | ||||
|         with: | ||||
|           action: "ff3:generate-release-notes firefly-iii ${{ github.event.inputs.version }}" | ||||
|           action: "ff3:generate-release-notes firefly-iii ${{ inputs.version || 'develop' }}" | ||||
|           output: 'output' | ||||
|         env: | ||||
|           FIREFLY_III_ROOT: /github/workspace | ||||
| @@ -291,7 +291,7 @@ jobs: | ||||
|           echo "DONE!" | ||||
|         env: | ||||
|           GH_TOKEN: ${{ github.token }} | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }} | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }} | ||||
|       - name: Create archives | ||||
|         run: | | ||||
|           echo "Create zip file $zipName" | ||||
| @@ -375,7 +375,7 @@ jobs: | ||||
|           fi | ||||
|         env: | ||||
|           GH_TOKEN: ${{ github.token }} | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }} | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }} | ||||
|       - name: Upload artifacts | ||||
|         run: | | ||||
|           # add zip file to release. | ||||
| @@ -411,4 +411,4 @@ jobs: | ||||
|           rm -f $tarName.sha256 | ||||
|         env: | ||||
|           GH_TOKEN: ${{ github.token }} | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }} | ||||
|           version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }} | ||||
|   | ||||
| @@ -97,22 +97,23 @@ class CategoryController extends Controller | ||||
|         $collector  = app(GroupCollectorInterface::class); | ||||
|         $collector->setRange($start, $end)->withAccountInformation(); | ||||
|         $collector->setXorAccounts($accounts)->withCategoryInformation(); | ||||
|         $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::RECONCILIATION->value]); | ||||
|         $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value]); | ||||
|         $journals   = $collector->getExtractedJournals(); | ||||
| 
 | ||||
|         /** @var array $journal */ | ||||
|         foreach ($journals as $journal) { | ||||
|             // find journal:
 | ||||
|             $journalCurrencyId                = (int)$journal['currency_id']; | ||||
|             $currency                         = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId); | ||||
|             $currencies[$journalCurrencyId]   = $currency; | ||||
|             $currencyId                       = (int)$currency->id; | ||||
|             $currencyName                     = (string)$currency->name; | ||||
|             $currencyCode                     = (string)$currency->code; | ||||
|             $currencySymbol                   = (string)$currency->symbol; | ||||
|             $currencyDecimalPlaces            = (int)$currency->decimal_places; | ||||
|             $amount                           = Steam::positive($journal['amount']); | ||||
|             $pcAmount                         = null; | ||||
|             $journalCurrencyId              = (int)$journal['currency_id']; | ||||
|             $type                           = $journal['transaction_type_type']; | ||||
|             $currency                       = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId); | ||||
|             $currencies[$journalCurrencyId] = $currency; | ||||
|             $currencyId                     = (int)$currency->id; | ||||
|             $currencyName                   = (string)$currency->name; | ||||
|             $currencyCode                   = (string)$currency->code; | ||||
|             $currencySymbol                 = (string)$currency->symbol; | ||||
|             $currencyDecimalPlaces          = (int)$currency->decimal_places; | ||||
|             $amount                         = Steam::positive((string)$journal['amount']); | ||||
|             $pcAmount                       = null; | ||||
| 
 | ||||
|             // overrule if necessary:
 | ||||
|             if ($this->convertToPrimary && $journalCurrencyId === $this->primaryCurrency->id) { | ||||
| @@ -129,8 +130,8 @@ class CategoryController extends Controller | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             $categoryName                     = $journal['category_name'] ?? (string)trans('firefly.no_category'); | ||||
|             $key                              = sprintf('%s-%s', $categoryName, $currencyCode); | ||||
|             $categoryName                   = $journal['category_name'] ?? (string)trans('firefly.no_category'); | ||||
|             $key                            = sprintf('%s-%s', $categoryName, $currencyCode); | ||||
|             // create arrays
 | ||||
|             $return[$key] ??= [ | ||||
|                 'label'                           => $categoryName, | ||||
| @@ -150,23 +151,37 @@ class CategoryController extends Controller | ||||
|                 'yAxisID'                         => 0, | ||||
|                 'type'                            => 'bar', | ||||
|                 'entries'                         => [ | ||||
|                     'spent' => '0', | ||||
|                     'spent'  => '0', | ||||
|                     'earned' => '0', | ||||
|                 ], | ||||
|                 'pc_entries'                      => [ | ||||
|                     'spent' => '0', | ||||
|                     'spent'  => '0', | ||||
|                     'earned' => '0', | ||||
|                 ], | ||||
|             ]; | ||||
| 
 | ||||
|             // add monies
 | ||||
|             $return[$key]['entries']['spent'] = bcadd($return[$key]['entries']['spent'], (string)$amount); | ||||
|             if (null !== $pcAmount) { | ||||
|                 $return[$key]['pc_entries']['spent'] = bcadd($return[$key]['pc_entries']['spent'], (string)$pcAmount); | ||||
|             // expenses to spent
 | ||||
|             if (TransactionTypeEnum::WITHDRAWAL->value === $type) { | ||||
|                 $return[$key]['entries']['spent'] = bcadd($return[$key]['entries']['spent'], $amount); | ||||
|                 if (null !== $pcAmount) { | ||||
|                     $return[$key]['pc_entries']['spent'] = bcadd($return[$key]['pc_entries']['spent'], $pcAmount); | ||||
|                 } | ||||
| 
 | ||||
|                 continue; | ||||
|             } | ||||
|             // positive amount = earned
 | ||||
|             if (TransactionTypeEnum::DEPOSIT->value === $type) { | ||||
|                 $return[$key]['entries']['earned'] = bcadd($return[$key]['entries']['earned'], $amount); | ||||
|                 if (null !== $pcAmount) { | ||||
|                     $return[$key]['pc_entries']['earned'] = bcadd($return[$key]['pc_entries']['earned'], $pcAmount); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         $return     = array_values($return); | ||||
| 
 | ||||
|         // order by amount
 | ||||
|         usort($return, static fn (array $a, array $b) => (float)$a['entries']['spent'] < (float)$b['entries']['spent'] ? 1 : -1); | ||||
|         usort($return, static fn (array $a, array $b) => ((float)$a['entries']['spent'] + (float)$a['entries']['earned']) < ((float)$b['entries']['spent'] + (float)$b['entries']['earned']) ? 1 : -1); | ||||
| 
 | ||||
|         return response()->json($this->clean($return)); | ||||
|     } | ||||
|   | ||||
| @@ -96,7 +96,6 @@ class ShowController extends Controller | ||||
|         $paginator    = new LengthAwarePaginator($budgetLimits, $count, $pageSize, $this->parameters->get('page')); | ||||
|         $paginator->setPath(route('api.v1.budgets.limits.index', [$budget->id]).$this->buildParams()); | ||||
| 
 | ||||
| 
 | ||||
|         // enrich
 | ||||
|         $enrichment   = new BudgetLimitEnrichment(); | ||||
|         $enrichment->setUser($admin); | ||||
|   | ||||
| @@ -86,7 +86,7 @@ class UpdateController extends Controller | ||||
|         $admin             = auth()->user(); | ||||
|         $enrichment        = new BudgetLimitEnrichment(); | ||||
|         $enrichment->setUser($admin); | ||||
|         $budgetLimit       = $enrichment->enrich($budgetLimit); | ||||
|         $budgetLimit       = $enrichment->enrichSingle($budgetLimit); | ||||
| 
 | ||||
|         /** @var BudgetLimitTransformer $transformer */ | ||||
|         $transformer       = app(BudgetLimitTransformer::class); | ||||
|   | ||||
| @@ -32,7 +32,9 @@ use FireflyIII\Generator\Webhook\MessageGeneratorInterface; | ||||
| use FireflyIII\Models\TransactionGroup; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; | ||||
| use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment; | ||||
| use FireflyIII\Transformers\WebhookTransformer; | ||||
| use FireflyIII\User; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| use Illuminate\Pagination\LengthAwarePaginator; | ||||
| use Illuminate\Support\Collection; | ||||
| @@ -90,6 +92,13 @@ class ShowController extends Controller | ||||
|         $paginator   = new LengthAwarePaginator($webhooks, $count, $pageSize, $this->parameters->get('page')); | ||||
|         $paginator->setPath(route('api.v1.webhooks.index').$this->buildParams()); | ||||
| 
 | ||||
|         // enrich
 | ||||
|         /** @var User $admin */ | ||||
|         $admin       = auth()->user(); | ||||
|         $enrichment  = new WebhookEnrichment(); | ||||
|         $enrichment->setUser($admin); | ||||
|         $webhooks    = $enrichment->enrich($webhooks); | ||||
| 
 | ||||
|         /** @var WebhookTransformer $transformer */ | ||||
|         $transformer = app(WebhookTransformer::class); | ||||
|         $transformer->setParameters($this->parameters); | ||||
| @@ -117,6 +126,13 @@ class ShowController extends Controller | ||||
|         Log::channel('audit')->info(sprintf('User views webhook #%d.', $webhook->id)); | ||||
|         $manager     = $this->getManager(); | ||||
| 
 | ||||
|         // enrich
 | ||||
|         /** @var User $admin */ | ||||
|         $admin       = auth()->user(); | ||||
|         $enrichment  = new WebhookEnrichment(); | ||||
|         $enrichment->setUser($admin); | ||||
|         $webhook     = $enrichment->enrichSingle($webhook); | ||||
| 
 | ||||
|         /** @var WebhookTransformer $transformer */ | ||||
|         $transformer = app(WebhookTransformer::class); | ||||
|         $transformer->setParameters($this->parameters); | ||||
| @@ -156,7 +172,7 @@ class ShowController extends Controller | ||||
|         $engine->generateMessages(); | ||||
| 
 | ||||
|         // trigger event to send them:
 | ||||
|         Log::debug('send event RequestedSendWebhookMessages'); | ||||
|         Log::debug('send event RequestedSendWebhookMessages from ShowController::triggerTransaction()'); | ||||
|         event(new RequestedSendWebhookMessages()); | ||||
| 
 | ||||
|         return response()->json([], 204); | ||||
|   | ||||
| @@ -27,7 +27,9 @@ namespace FireflyIII\Api\V1\Controllers\Webhook; | ||||
| use FireflyIII\Api\V1\Controllers\Controller; | ||||
| use FireflyIII\Api\V1\Requests\Models\Webhook\CreateRequest; | ||||
| use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; | ||||
| use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment; | ||||
| use FireflyIII\Transformers\WebhookTransformer; | ||||
| use FireflyIII\User; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| use League\Fractal\Resource\Item; | ||||
| @@ -68,6 +70,15 @@ class StoreController extends Controller | ||||
|         } | ||||
| 
 | ||||
|         $webhook     = $this->repository->store($data); | ||||
| 
 | ||||
|         // enrich
 | ||||
|         /** @var User $admin */ | ||||
|         $admin       = auth()->user(); | ||||
|         $enrichment  = new WebhookEnrichment(); | ||||
|         $enrichment->setUser($admin); | ||||
|         $webhook     = $enrichment->enrichSingle($webhook); | ||||
| 
 | ||||
| 
 | ||||
|         $manager     = $this->getManager(); | ||||
| 
 | ||||
|         Log::channel('audit')->info('User stores new webhook', $data); | ||||
|   | ||||
| @@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller; | ||||
| use FireflyIII\Api\V1\Requests\Models\Webhook\UpdateRequest; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; | ||||
| use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment; | ||||
| use FireflyIII\Transformers\WebhookTransformer; | ||||
| use FireflyIII\User; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| use League\Fractal\Resource\Item; | ||||
| @@ -70,6 +72,13 @@ class UpdateController extends Controller | ||||
|         $webhook     = $this->repository->update($webhook, $data); | ||||
|         $manager     = $this->getManager(); | ||||
| 
 | ||||
|         // enrich
 | ||||
|         /** @var User $admin */ | ||||
|         $admin       = auth()->user(); | ||||
|         $enrichment  = new WebhookEnrichment(); | ||||
|         $enrichment->setUser($admin); | ||||
|         $webhook     = $enrichment->enrichSingle($webhook); | ||||
| 
 | ||||
|         Log::channel('audit')->info(sprintf('User updates webhook #%d', $webhook->id), $data); | ||||
| 
 | ||||
|         /** @var WebhookTransformer $transformer */ | ||||
|   | ||||
| @@ -64,6 +64,7 @@ class ChartRequest extends FormRequest | ||||
|             'end'         => 'required|date|after:1970-01-02|before:2038-01-17|after_or_equal:start', | ||||
|             'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))), | ||||
|             'period'      => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))), | ||||
|             'accounts'    => 'nullable|array', | ||||
|             'accounts.*'  => 'exists:accounts,id', | ||||
|         ]; | ||||
| 
 | ||||
|   | ||||
| @@ -24,15 +24,13 @@ declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Api\V1\Requests\Models\Webhook; | ||||
| 
 | ||||
| use FireflyIII\Enums\WebhookResponse; | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| use FireflyIII\Exceptions\FireflyException; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Rules\IsBoolean; | ||||
| use FireflyIII\Support\Request\ChecksLogin; | ||||
| use FireflyIII\Support\Request\ConvertsDataTypes; | ||||
| use Illuminate\Contracts\Validation\Validator; | ||||
| use FireflyIII\Support\Request\ValidatesWebhooks; | ||||
| use Illuminate\Foundation\Http\FormRequest; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| /** | ||||
|  * Class CreateRequest | ||||
| @@ -41,27 +39,28 @@ class CreateRequest extends FormRequest | ||||
| { | ||||
|     use ChecksLogin; | ||||
|     use ConvertsDataTypes; | ||||
|     use ValidatesWebhooks; | ||||
| 
 | ||||
|     public function getData(): array | ||||
|     { | ||||
|         $triggers           = Webhook::getTriggersForValidation(); | ||||
|         $responses          = Webhook::getResponsesForValidation(); | ||||
|         $deliveries         = Webhook::getDeliveriesForValidation(); | ||||
| 
 | ||||
|         $fields             = [ | ||||
|             'title'    => ['title', 'convertString'], | ||||
|             'active'   => ['active', 'boolean'], | ||||
|             'trigger'  => ['trigger', 'convertString'], | ||||
|             'response' => ['response', 'convertString'], | ||||
|             'delivery' => ['delivery', 'convertString'], | ||||
|             'url'      => ['url', 'convertString'], | ||||
|         $fields               = [ | ||||
|             'title'  => ['title', 'convertString'], | ||||
|             'active' => ['active', 'boolean'], | ||||
|             'url'    => ['url', 'convertString'], | ||||
|         ]; | ||||
|         $triggers             = $this->get('triggers', []); | ||||
|         $responses            = $this->get('responses', []); | ||||
|         $deliveries           = $this->get('deliveries', []); | ||||
| 
 | ||||
|         // this is the way.
 | ||||
|         $return             = $this->getAllData($fields); | ||||
|         $return['trigger']  = $triggers[$return['trigger']] ?? (int)$return['trigger']; | ||||
|         $return['response'] = $responses[$return['response']] ?? (int)$return['response']; | ||||
|         $return['delivery'] = $deliveries[$return['delivery']] ?? (int)$return['delivery']; | ||||
|         if (0 === count($triggers) || 0 === count($responses) || 0 === count($deliveries)) { | ||||
|             throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.'); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         $return               = $this->getAllData($fields); | ||||
|         $return['triggers']   = $triggers; | ||||
|         $return['responses']  = $responses; | ||||
|         $return['deliveries'] = $deliveries; | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| @@ -71,59 +70,24 @@ class CreateRequest extends FormRequest | ||||
|      */ | ||||
|     public function rules(): array | ||||
|     { | ||||
|         $triggers       = implode(',', array_keys(Webhook::getTriggersForValidation())); | ||||
|         $responses      = implode(',', array_keys(Webhook::getResponsesForValidation())); | ||||
|         $deliveries     = implode(',', array_keys(Webhook::getDeliveriesForValidation())); | ||||
|         $triggers       = implode(',', array_values(Webhook::getTriggers())); | ||||
|         $responses      = implode(',', array_values(Webhook::getResponses())); | ||||
|         $deliveries     = implode(',', array_values(Webhook::getDeliveries())); | ||||
|         $validProtocols = config('firefly.valid_url_protocols'); | ||||
| 
 | ||||
|         return [ | ||||
|             'title'    => 'required|min:1|max:255|uniqueObjectForUser:webhooks,title', | ||||
|             'active'   => [new IsBoolean()], | ||||
|             'trigger'  => sprintf('required|in:%s', $triggers), | ||||
|             'response' => sprintf('required|in:%s', $responses), | ||||
|             'delivery' => sprintf('required|in:%s', $deliveries), | ||||
|             'url'      => ['required', sprintf('url:%s', $validProtocols), 'uniqueWebhook'], | ||||
|             'title'        => 'required|min:1|max:255|uniqueObjectForUser:webhooks,title', | ||||
|             'active'       => [new IsBoolean()], | ||||
|             'trigger'      => 'prohibited', | ||||
|             'triggers'     => 'required|array|min:1|max:10', | ||||
|             'triggers.*'   => sprintf('required|in:%s', $triggers), | ||||
|             'response'     => 'prohibited', | ||||
|             'responses'    => 'required|array|min:1|max:1', | ||||
|             'responses.*'  => sprintf('required|in:%s', $responses), | ||||
|             'delivery'     => 'prohibited', | ||||
|             'deliveries'   => 'required|array|min:1|max:1', | ||||
|             'deliveries.*' => sprintf('required|in:%s', $deliveries), | ||||
|             'url'          => ['required', sprintf('url:%s', $validProtocols)], | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function withValidator(Validator $validator): void | ||||
|     { | ||||
|         $validator->after( | ||||
|             function (Validator $validator): void { | ||||
|                 Log::debug('Validating webhook'); | ||||
|                 $data      = $validator->getData(); | ||||
|                 $trigger   = $data['trigger'] ?? null; | ||||
|                 $response  = $data['response'] ?? null; | ||||
|                 if (null === $trigger || null === $response) { | ||||
|                     Log::debug('No trigger or response, return.'); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $triggers  = array_keys(Webhook::getTriggersForValidation()); | ||||
|                 $responses = array_keys(Webhook::getResponsesForValidation()); | ||||
|                 if (!in_array($trigger, $triggers, true) || !in_array($response, $responses, true)) { | ||||
|                     return; | ||||
|                 } | ||||
|                 // cannot deliver budget info.
 | ||||
|                 if (is_int($trigger)) { | ||||
|                     Log::debug(sprintf('Trigger was integer (%d).', $trigger)); | ||||
|                     $trigger = WebhookTrigger::from($trigger)->name; | ||||
|                 } | ||||
|                 if (is_int($response)) { | ||||
|                     Log::debug(sprintf('Response was integer (%d).', $response)); | ||||
|                     $response = WebhookResponse::from($response)->name; | ||||
|                 } | ||||
|                 Log::debug(sprintf('Trigger is %s, response is %s', $trigger, $response)); | ||||
|                 if (str_contains($trigger, 'TRANSACTION') && str_contains($response, 'BUDGET')) { | ||||
|                     $validator->errors()->add('response', trans('validation.webhook_budget_info')); | ||||
|                 } | ||||
|                 if (str_contains($trigger, 'BUDGET') && str_contains($response, 'ACCOUNT')) { | ||||
|                     $validator->errors()->add('response', trans('validation.webhook_account_info')); | ||||
|                 } | ||||
|                 if (str_contains($trigger, 'BUDGET') && str_contains($response, 'TRANSACTION')) { | ||||
|                     $validator->errors()->add('response', trans('validation.webhook_transaction_info')); | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -24,15 +24,13 @@ declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Api\V1\Requests\Models\Webhook; | ||||
| 
 | ||||
| use FireflyIII\Enums\WebhookResponse; | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| use FireflyIII\Exceptions\FireflyException; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Rules\IsBoolean; | ||||
| use FireflyIII\Support\Request\ChecksLogin; | ||||
| use FireflyIII\Support\Request\ConvertsDataTypes; | ||||
| use Illuminate\Contracts\Validation\Validator; | ||||
| use FireflyIII\Support\Request\ValidatesWebhooks; | ||||
| use Illuminate\Foundation\Http\FormRequest; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| /** | ||||
|  * Class UpdateRequest | ||||
| @@ -41,38 +39,29 @@ class UpdateRequest extends FormRequest | ||||
| { | ||||
|     use ChecksLogin; | ||||
|     use ConvertsDataTypes; | ||||
|     use ValidatesWebhooks; | ||||
| 
 | ||||
|     public function getData(): array | ||||
|     { | ||||
|         $triggers         = Webhook::getTriggersForValidation(); | ||||
|         $responses        = Webhook::getResponsesForValidation(); | ||||
|         $deliveries       = Webhook::getDeliveriesForValidation(); | ||||
| 
 | ||||
|         $fields           = [ | ||||
|         $fields               = [ | ||||
|             'title'    => ['title', 'convertString'], | ||||
|             'active'   => ['active', 'boolean'], | ||||
|             'trigger'  => ['trigger', 'convertString'], | ||||
|             'response' => ['response', 'convertString'], | ||||
|             'delivery' => ['delivery', 'convertString'], | ||||
|             'url'      => ['url', 'convertString'], | ||||
|         ]; | ||||
| 
 | ||||
|         // this is the way.
 | ||||
|         $return           = $this->getAllData($fields); | ||||
|         if (array_key_exists('trigger', $return)) { | ||||
|             $return['trigger'] = $triggers[$return['trigger']] ?? 0; | ||||
|         } | ||||
|         if (array_key_exists('response', $return)) { | ||||
|             $return['response'] = $responses[$return['response']] ?? 0; | ||||
|         } | ||||
|         if (array_key_exists('delivery', $return)) { | ||||
|             $return['delivery'] = $deliveries[$return['delivery']] ?? 0; | ||||
|         } | ||||
|         $return['secret'] = null !== $this->get('secret'); | ||||
|         if (null !== $this->get('title')) { | ||||
|             $return['title'] = $this->convertString('title'); | ||||
|         $triggers             = $this->get('triggers', []); | ||||
|         $responses            = $this->get('responses', []); | ||||
|         $deliveries           = $this->get('deliveries', []); | ||||
| 
 | ||||
|         if (0 === count($triggers) || 0 === count($responses) || 0 === count($deliveries)) { | ||||
|             throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.'); | ||||
|         } | ||||
| 
 | ||||
|         $return               = $this->getAllData($fields); | ||||
|         $return['triggers']   = $triggers; | ||||
|         $return['responses']  = $responses; | ||||
|         $return['deliveries'] = $deliveries; | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
| @@ -81,62 +70,29 @@ class UpdateRequest extends FormRequest | ||||
|      */ | ||||
|     public function rules(): array | ||||
|     { | ||||
|         $triggers       = implode(',', array_keys(Webhook::getTriggersForValidation())); | ||||
|         $responses      = implode(',', array_keys(Webhook::getResponsesForValidation())); | ||||
|         $deliveries     = implode(',', array_keys(Webhook::getDeliveriesForValidation())); | ||||
|         $triggers       = implode(',', array_values(Webhook::getTriggers())); | ||||
|         $responses      = implode(',', array_values(Webhook::getResponses())); | ||||
|         $deliveries     = implode(',', array_values(Webhook::getDeliveries())); | ||||
|         $validProtocols = config('firefly.valid_url_protocols'); | ||||
| 
 | ||||
|         /** @var Webhook $webhook */ | ||||
|         $webhook        = $this->route()->parameter('webhook'); | ||||
| 
 | ||||
|         return [ | ||||
|             'title'    => sprintf('min:1|max:255|uniqueObjectForUser:webhooks,title,%d', $webhook->id), | ||||
|             'active'   => [new IsBoolean()], | ||||
|             'trigger'  => sprintf('in:%s', $triggers), | ||||
|             'response' => sprintf('in:%s', $responses), | ||||
|             'delivery' => sprintf('in:%s', $deliveries), | ||||
|             'url'      => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)], | ||||
|             'title'        => sprintf('min:1|max:255|uniqueObjectForUser:webhooks,title,%d', $webhook->id), | ||||
|             'active'       => [new IsBoolean()], | ||||
| 
 | ||||
|             'trigger'      => 'prohibited', | ||||
|             'triggers'     => 'required|array|min:1|max:10', | ||||
|             'triggers.*'   => sprintf('required|in:%s', $triggers), | ||||
|             'response'     => 'prohibited', | ||||
|             'responses'    => 'required|array|min:1|max:1', | ||||
|             'responses.*'  => sprintf('required|in:%s', $responses), | ||||
|             'delivery'     => 'prohibited', | ||||
|             'deliveries'   => 'required|array|min:1|max:1', | ||||
|             'deliveries.*' => sprintf('required|in:%s', $deliveries), | ||||
| 
 | ||||
|             'url'          => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)], | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function withValidator(Validator $validator): void | ||||
|     { | ||||
|         $validator->after( | ||||
|             function (Validator $validator): void { | ||||
|                 Log::debug('Validating webhook'); | ||||
|                 $data      = $validator->getData(); | ||||
|                 $trigger   = $data['trigger'] ?? null; | ||||
|                 $response  = $data['response'] ?? null; | ||||
|                 if (null === $trigger || null === $response) { | ||||
|                     Log::debug('No trigger or response, return.'); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $triggers  = array_keys(Webhook::getTriggersForValidation()); | ||||
|                 $responses = array_keys(Webhook::getResponsesForValidation()); | ||||
|                 if (!in_array($trigger, $triggers, true) || !in_array($response, $responses, true)) { | ||||
|                     return; | ||||
|                 } | ||||
|                 // cannot deliver budget info.
 | ||||
|                 if (is_int($trigger)) { | ||||
|                     Log::debug(sprintf('Trigger was integer (%d).', $trigger)); | ||||
|                     $trigger = WebhookTrigger::from($trigger)->name; | ||||
|                 } | ||||
|                 if (is_int($response)) { | ||||
|                     Log::debug(sprintf('Response was integer (%d).', $response)); | ||||
|                     $response = WebhookResponse::from($response)->name; | ||||
|                 } | ||||
|                 Log::debug(sprintf('Trigger is %s, response is %s', $trigger, $response)); | ||||
|                 if (str_contains($trigger, 'TRANSACTION') && str_contains($response, 'BUDGET')) { | ||||
|                     $validator->errors()->add('response', trans('validation.webhook_budget_info')); | ||||
|                 } | ||||
|                 if (str_contains($trigger, 'BUDGET') && str_contains($response, 'ACCOUNT')) { | ||||
|                     $validator->errors()->add('response', trans('validation.webhook_account_info')); | ||||
|                 } | ||||
|                 if (str_contains($trigger, 'BUDGET') && str_contains($response, 'TRANSACTION')) { | ||||
|                     $validator->errors()->add('response', trans('validation.webhook_transaction_info')); | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -28,6 +28,7 @@ namespace FireflyIII\Casts; | ||||
| use Carbon\Carbon; | ||||
| use Illuminate\Contracts\Database\Eloquent\CastsAttributes; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| /** | ||||
|  * Class SeparateTimezoneCaster | ||||
| @@ -51,6 +52,7 @@ class SeparateTimezoneCaster implements CastsAttributes | ||||
|         $timeZone = $attributes[sprintf('%s_tz', $key)] ?? config('app.timezone'); | ||||
| 
 | ||||
|         return Carbon::parse($value, $timeZone)->setTimezone(config('app.timezone')); | ||||
|         // Log::debug(sprintf('SeparateTimezoneCaster: %s.%s = %s', str_replace('FireflyIII\\Models\\','',get_class($model)), $key, $result->toAtomString()));
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|   | ||||
| @@ -75,6 +75,7 @@ class UpgradesDatabase extends Command | ||||
|             'upgrade:610-currency-preferences', | ||||
|             'upgrade:620-piggy-banks', | ||||
|             'upgrade:620-pc-amounts', | ||||
|             'upgrade:640-upgrade-webhooks', | ||||
|             'firefly-iii:correct-database', | ||||
|         ]; | ||||
|         $args     = []; | ||||
|   | ||||
							
								
								
									
										95
									
								
								app/Console/Commands/Upgrade/UpgradesWebhooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								app/Console/Commands/Upgrade/UpgradesWebhooks.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Console\Commands\Upgrade; | ||||
| 
 | ||||
| use FireflyIII\Console\Commands\ShowsFriendlyMessages; | ||||
| use FireflyIII\Enums\WebhookDelivery; | ||||
| use FireflyIII\Enums\WebhookResponse; | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Models\WebhookDelivery as WebhookDeliveryModel; | ||||
| use FireflyIII\Models\WebhookResponse as WebhookResponseModel; | ||||
| use FireflyIII\Models\WebhookTrigger as WebhookTriggerModel; | ||||
| use Illuminate\Console\Command; | ||||
| 
 | ||||
| class UpgradesWebhooks extends Command | ||||
| { | ||||
|     use ShowsFriendlyMessages; | ||||
| 
 | ||||
|     public const string CONFIG_NAME = '640_upgrade_webhooks'; | ||||
|     protected $description          = 'Upgrade webhooks so they can handle multiple triggers.'; | ||||
|     protected $signature            = 'upgrade:640-upgrade-webhooks {--F|force : Force the execution of this command.}'; | ||||
| 
 | ||||
|     /** | ||||
|      * Execute the console command. | ||||
|      */ | ||||
|     public function handle(): int | ||||
|     { | ||||
|         if ($this->isExecuted() && true !== $this->option('force')) { | ||||
|             $this->friendlyInfo('This command has already been executed.'); | ||||
| 
 | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
|         $this->upgradeWebhooks(); | ||||
|         $this->markAsExecuted(); | ||||
|         $this->friendlyPositive('Upgraded webhooks.'); | ||||
| 
 | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     private function isExecuted(): bool | ||||
|     { | ||||
|         $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); | ||||
|         if (null !== $configVar) { | ||||
|             return (bool)$configVar->data; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     private function upgradeWebhooks(): void | ||||
|     { | ||||
|         $set = Webhook::where('delivery', '>', 1)->orWhere('trigger', '>', 1)->orWhere('response', '>', 1)->get(); | ||||
| 
 | ||||
|         /** @var Webhook $webhook */ | ||||
|         foreach ($set as $webhook) { | ||||
|             $this->upgradeWebhook($webhook); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function upgradeWebhook(Webhook $webhook): void | ||||
|     { | ||||
|         $delivery          = WebhookDelivery::tryFrom((int)$webhook->delivery); | ||||
|         $response          = WebhookResponse::tryFrom((int)$webhook->response); | ||||
|         $trigger           = WebhookTrigger::tryFrom((int)$webhook->trigger); | ||||
|         if (null === $delivery || null === $response || null === $trigger) { | ||||
|             $this->friendlyError(sprintf('[a] Webhook #%d has an invalid delivery, response or trigger value. Will not upgrade.', $webhook->id)); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $deliveryModel     = WebhookDeliveryModel::where('key', $delivery->value)->first(); | ||||
|         $responseModel     = WebhookResponseModel::where('key', $response->value)->first(); | ||||
|         $triggerModel      = WebhookTriggerModel::where('key', $trigger->value)->first(); | ||||
|         if (null === $deliveryModel || null === $responseModel || null === $triggerModel) { | ||||
|             $this->friendlyError(sprintf('[b] Webhook #%d has an invalid delivery, response or trigger model. Will not upgrade.', $webhook->id)); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $webhook->webhookDeliveries()->attach([$deliveryModel->id]); | ||||
|         $webhook->webhookResponses()->attach([$responseModel->id]); | ||||
|         $webhook->webhookTriggers()->attach([$triggerModel->id]); | ||||
|         $webhook->delivery = 1; | ||||
|         $webhook->response = 1; | ||||
|         $webhook->trigger  = 1; | ||||
|         $webhook->save(); | ||||
|         $this->friendlyPositive(sprintf('Webhook #%d upgraded.', $webhook->id)); | ||||
|     } | ||||
| 
 | ||||
|     private function markAsExecuted(): void | ||||
|     { | ||||
|         app('fireflyconfig')->set(self::CONFIG_NAME, true); | ||||
|     } | ||||
| } | ||||
| @@ -32,5 +32,6 @@ enum WebhookResponse: int | ||||
|     case TRANSACTIONS = 200; | ||||
|     case ACCOUNTS     = 210; | ||||
|     case BUDGET       = 230; | ||||
|     case RELEVANT     = 240; | ||||
|     case NONE         = 220; | ||||
| } | ||||
|   | ||||
| @@ -29,6 +29,7 @@ namespace FireflyIII\Enums; | ||||
|  */ | ||||
| enum WebhookTrigger: int | ||||
| { | ||||
|     case ANY                       = 50; | ||||
|     case STORE_TRANSACTION         = 100; | ||||
|     case UPDATE_TRANSACTION        = 110; | ||||
|     case DESTROY_TRANSACTION       = 120; | ||||
|   | ||||
| @@ -1,39 +0,0 @@ | ||||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * Created.php | ||||
|  * Copyright (c) 2023 james@firefly-iii.org | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Events\Model\BudgetLimit; | ||||
| 
 | ||||
| use FireflyIII\Events\Event; | ||||
| use FireflyIII\Models\BudgetLimit; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| /** | ||||
|  * Class Created | ||||
|  */ | ||||
| class Created extends Event | ||||
| { | ||||
|     use SerializesModels; | ||||
| 
 | ||||
|     public function __construct(public BudgetLimit $budgetLimit) {} | ||||
| } | ||||
| @@ -1,39 +0,0 @@ | ||||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * Deleted.php | ||||
|  * Copyright (c) 2023 james@firefly-iii.org | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Events\Model\BudgetLimit; | ||||
| 
 | ||||
| use FireflyIII\Events\Event; | ||||
| use FireflyIII\Models\BudgetLimit; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| /** | ||||
|  * Class Deleted | ||||
|  */ | ||||
| class Deleted extends Event | ||||
| { | ||||
|     use SerializesModels; | ||||
| 
 | ||||
|     public function __construct(public BudgetLimit $budgetLimit) {} | ||||
| } | ||||
| @@ -1,39 +0,0 @@ | ||||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * Updated.php | ||||
|  * Copyright (c) 2023 james@firefly-iii.org | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Events\Model\BudgetLimit; | ||||
| 
 | ||||
| use FireflyIII\Events\Event; | ||||
| use FireflyIII\Models\BudgetLimit; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| /** | ||||
|  * Class Updated | ||||
|  */ | ||||
| class Updated extends Event | ||||
| { | ||||
|     use SerializesModels; | ||||
| 
 | ||||
|     public function __construct(public BudgetLimit $budgetLimit) {} | ||||
| } | ||||
| @@ -30,6 +30,7 @@ use FireflyIII\Models\Recurrence; | ||||
| use FireflyIII\Services\Internal\Support\RecurringTransactionTrait; | ||||
| use FireflyIII\Services\Internal\Support\TransactionTypeTrait; | ||||
| use FireflyIII\User; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| use Illuminate\Support\MessageBag; | ||||
| 
 | ||||
| /** | ||||
| @@ -62,8 +63,8 @@ class RecurrenceFactory | ||||
|             $type = $this->findTransactionType(ucfirst((string) $data['recurrence']['type'])); | ||||
|         } catch (FireflyException $e) { | ||||
|             $message = sprintf('Cannot make a recurring transaction of type "%s"', $data['recurrence']['type']); | ||||
|             app('log')->error($message); | ||||
|             app('log')->error($e->getTraceAsString()); | ||||
|             Log::error($message); | ||||
|             Log::error($e->getTraceAsString()); | ||||
| 
 | ||||
|             throw new FireflyException($message, 0, $e); | ||||
|         } | ||||
| @@ -101,17 +102,18 @@ class RecurrenceFactory | ||||
| 
 | ||||
|         $recurrence        = new Recurrence( | ||||
|             [ | ||||
|                 'user_id'             => $this->user->id, | ||||
|                 'user_group_id'       => $this->user->user_group_id, | ||||
|                 'transaction_type_id' => $type->id, | ||||
|                 'title'               => $title, | ||||
|                 'description'         => $description, | ||||
|                 'first_date'          => $firstDate?->format('Y-m-d'), | ||||
|                 'repeat_until'        => $repetitions > 0 ? null : $repeatUntilString, | ||||
|                 'latest_date'         => null, | ||||
|                 'repetitions'         => $repetitions, | ||||
|                 'apply_rules'         => $applyRules, | ||||
|                 'active'              => $active, | ||||
|                 'user_id'                => $this->user->id, | ||||
|                 'user_group_id'          => $this->user->user_group_id, | ||||
|                 'transaction_type_id'    => $type->id, | ||||
|                 'title'                  => $title, | ||||
|                 'description'            => $description, | ||||
|                 'first_date'             => $firstDate?->format('Y-m-d'), | ||||
|                 'first_date_tz'          => $firstDate?->format('e'), | ||||
|                 'repeat_until'           => $repetitions > 0 ? null : $repeatUntilString, | ||||
|                 'latest_date'            => null, | ||||
|                 'repetitions'            => $repetitions, | ||||
|                 'apply_rules'            => $applyRules, | ||||
|                 'active'                 => $active, | ||||
|             ] | ||||
|         ); | ||||
|         $recurrence->save(); | ||||
| @@ -125,8 +127,8 @@ class RecurrenceFactory | ||||
|         try { | ||||
|             $this->createTransactions($recurrence, $data['transactions'] ?? []); | ||||
|         } catch (FireflyException $e) { | ||||
|             app('log')->error($e->getMessage()); | ||||
|             app('log')->error($e->getTraceAsString()); | ||||
|             Log::error($e->getMessage()); | ||||
|             Log::error($e->getTraceAsString()); | ||||
|             $recurrence->forceDelete(); | ||||
|             $message = sprintf('Could not create recurring transaction: %s', $e->getMessage()); | ||||
|             $this->errors->add('store', $message); | ||||
|   | ||||
| @@ -27,13 +27,21 @@ namespace FireflyIII\Generator\Webhook; | ||||
| use FireflyIII\Enums\WebhookResponse; | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| use FireflyIII\Exceptions\FireflyException; | ||||
| use FireflyIII\Models\Budget; | ||||
| use FireflyIII\Models\BudgetLimit; | ||||
| use FireflyIII\Models\Transaction; | ||||
| use FireflyIII\Models\TransactionGroup; | ||||
| use FireflyIII\Models\TransactionJournal; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Models\WebhookMessage; | ||||
| use FireflyIII\Models\WebhookResponse as WebhookResponseModel; | ||||
| use FireflyIII\Models\WebhookTrigger as WebhookTriggerModel; | ||||
| use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment; | ||||
| use FireflyIII\Support\JsonApi\Enrichments\BudgetEnrichment; | ||||
| use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment; | ||||
| use FireflyIII\Transformers\AccountTransformer; | ||||
| use FireflyIII\Transformers\BudgetLimitTransformer; | ||||
| use FireflyIII\Transformers\BudgetTransformer; | ||||
| use FireflyIII\Transformers\TransactionGroupTransformer; | ||||
| use FireflyIII\User; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| @@ -74,7 +82,13 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
| 
 | ||||
|     private function getWebhooks(): Collection | ||||
|     { | ||||
|         return $this->user->webhooks()->where('active', true)->where('trigger', $this->trigger)->get(['webhooks.*']); | ||||
|         return $this->user->webhooks() | ||||
|             ->leftJoin('webhook_webhook_trigger', 'webhook_webhook_trigger.webhook_id', 'webhooks.id') | ||||
|             ->leftJoin('webhook_triggers', 'webhook_webhook_trigger.webhook_trigger_id', 'webhook_triggers.id') | ||||
|             ->where('active', true) | ||||
|             ->whereIn('webhook_triggers.title', [$this->trigger->name, WebhookTrigger::ANY->name]) | ||||
|             ->get(['webhooks.*']) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -109,24 +123,25 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
|      */ | ||||
|     private function generateMessage(Webhook $webhook, Model $model): void | ||||
|     { | ||||
|         $class        = $model::class; | ||||
|         $class         = $model::class; | ||||
|         // Line is ignored because all of Firefly III's Models have an id property.
 | ||||
|         Log::debug(sprintf('Now in generateMessage(#%d, %s#%d)', $webhook->id, $class, $model->id)); | ||||
|         Log::debug($webhook->response); | ||||
|         Log::debug(WebhookResponse::from($webhook->response)->name); | ||||
|         $uuid         = Uuid::uuid4(); | ||||
|         $basicMessage = [ | ||||
|         $uuid          = Uuid::uuid4(); | ||||
| 
 | ||||
|         /** @var WebhookResponseModel $response */ | ||||
|         $response      = $webhook->webhookResponses()->first(); | ||||
|         $triggers      = $this->getTriggerTitles($webhook->webhookTriggers()->get()); | ||||
|         $basicMessage  = [ | ||||
|             'uuid'          => $uuid->toString(), | ||||
|             'user_id'       => 0, | ||||
|             'user_group_id' => 0, | ||||
|             'trigger'       => WebhookTrigger::from((int) $webhook->trigger)->name, | ||||
|             'response'      => WebhookResponse::from((int) $webhook->response)->name, | ||||
|             'trigger'       => $this->trigger->name, | ||||
|             'response'      => $response->title, // guess that the database is correct.
 | ||||
|             'url'           => $webhook->url, | ||||
|             'version'       => sprintf('v%d', $this->getVersion()), | ||||
|             'content'       => [], | ||||
|         ]; | ||||
| 
 | ||||
|         // depends on the model how user_id is set:
 | ||||
|         switch ($class) { | ||||
|             default: | ||||
|                 // Line is ignored because all of Firefly III's Models have an id property.
 | ||||
| @@ -134,6 +149,21 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
| 
 | ||||
|                 return; | ||||
| 
 | ||||
|             case Budget::class: | ||||
|                 /** @var Budget $model */ | ||||
|                 $basicMessage['user_id']       = $model->user_id; | ||||
|                 $basicMessage['user_group_id'] = $model->user_group_id; | ||||
|                 $relevantResponse              = WebhookResponse::BUDGET->name; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case BudgetLimit::class: | ||||
|                 $basicMessage['user_id']       = $model->budget->user_id; | ||||
|                 $basicMessage['user_group_id'] = $model->budget->user_group_id; | ||||
|                 $relevantResponse              = WebhookResponse::BUDGET->name; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case TransactionGroup::class: | ||||
|                 /** @var TransactionGroup $model */ | ||||
|                 $basicMessage['user_id']       = $model->user_id; | ||||
| @@ -141,20 +171,46 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
| 
 | ||||
|                 break; | ||||
|         } | ||||
|         $responseTitle = $this->getRelevantResponse($triggers, $response, $class); | ||||
| 
 | ||||
|         // then depends on the response what to put in the message:
 | ||||
|         switch ($webhook->response) { | ||||
|         switch ($responseTitle) { | ||||
|             default: | ||||
|                 Log::error(sprintf('The response code for webhook #%d is "%d" and the message generator cant handle it. Soft fail.', $webhook->id, $webhook->response)); | ||||
|                 Log::error(sprintf('The response code for webhook #%d is "%s" and the message generator cant handle it. Soft fail.', $webhook->id, $webhook->response)); | ||||
| 
 | ||||
|                 return; | ||||
| 
 | ||||
|             case WebhookResponse::NONE->value: | ||||
|             case WebhookResponse::BUDGET->name: | ||||
|                 $basicMessage['content'] = []; | ||||
|                 if ($model instanceof Budget) { | ||||
|                     $enrichment              = new BudgetEnrichment(); | ||||
|                     $enrichment->setUser($model->user); | ||||
|                     $model                   = $enrichment->enrichSingle($model); | ||||
|                     $transformer             = new BudgetTransformer(); | ||||
|                     $basicMessage['content'] = $transformer->transform($model); | ||||
|                 } | ||||
|                 if ($model instanceof BudgetLimit) { | ||||
|                     $user                    = $model->budget->user; | ||||
|                     $enrichment              = new BudgetLimitEnrichment(); | ||||
|                     $enrichment->setUser($user); | ||||
| 
 | ||||
|                     $parameters              = new ParameterBag(); | ||||
|                     $parameters->set('start', $model->start_date); | ||||
|                     $parameters->set('end', $model->end_date); | ||||
| 
 | ||||
|                     $model                   = $enrichment->enrichSingle($model); | ||||
|                     $transformer             = new BudgetLimitTransformer(); | ||||
|                     $transformer->setParameters($parameters); | ||||
|                     $basicMessage['content'] = $transformer->transform($model); | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case WebhookResponse::NONE->name: | ||||
|                 $basicMessage['content'] = []; | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case WebhookResponse::TRANSACTIONS->value: | ||||
|             case WebhookResponse::TRANSACTIONS->name: | ||||
|                 /** @var TransactionGroup $model */ | ||||
|                 $transformer             = new TransactionGroupTransformer(); | ||||
| 
 | ||||
| @@ -171,7 +227,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
| 
 | ||||
|                 break; | ||||
| 
 | ||||
|             case WebhookResponse::ACCOUNTS->value: | ||||
|             case WebhookResponse::ACCOUNTS->name: | ||||
|                 /** @var TransactionGroup $model */ | ||||
|                 $accounts                = $this->collectAccounts($model); | ||||
|                 $enrichment              = new AccountEnrichment(); | ||||
| @@ -238,4 +294,50 @@ class StandardMessageGenerator implements MessageGeneratorInterface | ||||
|     { | ||||
|         $this->webhooks = $webhooks; | ||||
|     } | ||||
| 
 | ||||
|     private function getRelevantResponse(array $triggers, WebhookResponseModel $response, $class): string | ||||
|     { | ||||
|         // return none if none.
 | ||||
|         if (WebhookResponse::NONE->name === $response->title) { | ||||
|             Log::debug(sprintf('Return "%s" because requested nothing.', WebhookResponse::NONE->name)); | ||||
| 
 | ||||
|             return WebhookResponse::NONE->name; | ||||
|         } | ||||
| 
 | ||||
|         if (WebhookResponse::RELEVANT->name === $response->title) { | ||||
|             Log::debug('Expected response is any relevant data.'); | ||||
| 
 | ||||
|             // depends on the $class
 | ||||
|             switch ($class) { | ||||
|                 case TransactionGroup::class: | ||||
|                     Log::debug(sprintf('Return "%s" because class is %s', WebhookResponse::TRANSACTIONS->name, $class)); | ||||
| 
 | ||||
|                     return WebhookResponse::TRANSACTIONS->name; | ||||
| 
 | ||||
|                 case Budget::class: | ||||
|                 case BudgetLimit::class: | ||||
|                     Log::debug(sprintf('Return "%s" because class is %s', WebhookResponse::BUDGET->name, $class)); | ||||
| 
 | ||||
|                     return WebhookResponse::BUDGET->name; | ||||
| 
 | ||||
|                 default: | ||||
|                     throw new FireflyException(sprintf('Cannot deal with "relevant" if the given object is a "%s"', $class)); | ||||
|             } | ||||
|         } | ||||
|         Log::debug(sprintf('Return response again: %s', $response->title)); | ||||
| 
 | ||||
|         return $response->title; | ||||
|     } | ||||
| 
 | ||||
|     private function getTriggerTitles(Collection $collection): array | ||||
|     { | ||||
|         $return = []; | ||||
| 
 | ||||
|         /** @var WebhookTriggerModel $item */ | ||||
|         foreach ($collection as $item) { | ||||
|             $return[] = $item->title; | ||||
|         } | ||||
| 
 | ||||
|         return array_unique($return); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -108,10 +108,10 @@ class BillEventHandler | ||||
|     { | ||||
|         Log::debug(sprintf('Now in %s', __METHOD__)); | ||||
| 
 | ||||
|         $bill = $event->bill; | ||||
|         $bill       = $event->bill; | ||||
| 
 | ||||
|         /** @var bool $preference */ | ||||
|         Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data; | ||||
|         $preference = Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data; | ||||
| 
 | ||||
|         if (true === $preference) { | ||||
|             Log::debug('Bill reminder is true!'); | ||||
|   | ||||
| @@ -55,7 +55,7 @@ class DestroyedGroupEventHandler | ||||
|         $engine->setObjects(new Collection([$group])); | ||||
|         $engine->setTrigger(WebhookTrigger::DESTROY_TRANSACTION); | ||||
|         $engine->generateMessages(); | ||||
| 
 | ||||
|         Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); | ||||
|         event(new RequestedSendWebhookMessages()); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -121,6 +121,7 @@ class StoredGroupEventHandler | ||||
|         $engine->generateMessages(); | ||||
| 
 | ||||
|         // trigger event to send them:
 | ||||
|         Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); | ||||
|         event(new RequestedSendWebhookMessages()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -167,6 +167,7 @@ class UpdatedGroupEventHandler | ||||
|         $engine->setTrigger(WebhookTrigger::UPDATE_TRANSACTION); | ||||
|         $engine->generateMessages(); | ||||
| 
 | ||||
|         Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); | ||||
|         event(new RequestedSendWebhookMessages()); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -24,17 +24,37 @@ declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Handlers\Observer; | ||||
| 
 | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| use FireflyIII\Events\RequestedSendWebhookMessages; | ||||
| use FireflyIII\Generator\Webhook\MessageGeneratorInterface; | ||||
| use FireflyIII\Models\BudgetLimit; | ||||
| use FireflyIII\Support\Facades\Amount; | ||||
| use FireflyIII\Support\Http\Api\ExchangeRateConverter; | ||||
| use FireflyIII\Support\Observers\RecalculatesAvailableBudgetsTrait; | ||||
| use Illuminate\Support\Collection; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| class BudgetLimitObserver | ||||
| { | ||||
|     use RecalculatesAvailableBudgetsTrait; | ||||
| 
 | ||||
|     public function created(BudgetLimit $budgetLimit): void | ||||
|     { | ||||
|         Log::debug('Observe "created" of a budget limit.'); | ||||
|         $this->updatePrimaryCurrencyAmount($budgetLimit); | ||||
|         $this->updateAvailableBudget($budgetLimit); | ||||
| 
 | ||||
|         $user   = $budgetLimit->budget->user; | ||||
| 
 | ||||
|         /** @var MessageGeneratorInterface $engine */ | ||||
|         $engine = app(MessageGeneratorInterface::class); | ||||
|         $engine->setUser($user); | ||||
|         $engine->setObjects(new Collection()->push($budgetLimit)); | ||||
|         $engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT); | ||||
|         $engine->generateMessages(); | ||||
| 
 | ||||
|         Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); | ||||
|         event(new RequestedSendWebhookMessages()); | ||||
|     } | ||||
| 
 | ||||
|     private function updatePrimaryCurrencyAmount(BudgetLimit $budgetLimit): void | ||||
| @@ -60,5 +80,18 @@ class BudgetLimitObserver | ||||
|     { | ||||
|         Log::debug('Observe "updated" of a budget limit.'); | ||||
|         $this->updatePrimaryCurrencyAmount($budgetLimit); | ||||
|         $this->updateAvailableBudget($budgetLimit); | ||||
| 
 | ||||
|         $user   = $budgetLimit->budget->user; | ||||
| 
 | ||||
|         /** @var MessageGeneratorInterface $engine */ | ||||
|         $engine = app(MessageGeneratorInterface::class); | ||||
|         $engine->setUser($user); | ||||
|         $engine->setObjects(new Collection()->push($budgetLimit)); | ||||
|         $engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT); | ||||
|         $engine->generateMessages(); | ||||
| 
 | ||||
|         Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); | ||||
|         event(new RequestedSendWebhookMessages()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -23,19 +23,70 @@ declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Handlers\Observer; | ||||
| 
 | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| use FireflyIII\Events\RequestedSendWebhookMessages; | ||||
| use FireflyIII\Generator\Webhook\MessageGeneratorInterface; | ||||
| use FireflyIII\Models\Attachment; | ||||
| use FireflyIII\Models\Budget; | ||||
| use FireflyIII\Models\BudgetLimit; | ||||
| use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; | ||||
| use FireflyIII\Support\Observers\RecalculatesAvailableBudgetsTrait; | ||||
| use Illuminate\Support\Collection; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| /** | ||||
|  * Class BudgetObserver | ||||
|  */ | ||||
| class BudgetObserver | ||||
| { | ||||
|     use RecalculatesAvailableBudgetsTrait; | ||||
| 
 | ||||
|     public function created(Budget $budget): void | ||||
|     { | ||||
|         Log::debug(sprintf('Observe "created" of budget #%d ("%s").', $budget->id, $budget->name)); | ||||
| 
 | ||||
|         // fire event.
 | ||||
|         $user   = $budget->user; | ||||
| 
 | ||||
|         /** @var MessageGeneratorInterface $engine */ | ||||
|         $engine = app(MessageGeneratorInterface::class); | ||||
|         $engine->setUser($user); | ||||
|         $engine->setObjects(new Collection()->push($budget)); | ||||
|         $engine->setTrigger(WebhookTrigger::STORE_BUDGET); | ||||
|         $engine->generateMessages(); | ||||
|         Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); | ||||
|         event(new RequestedSendWebhookMessages()); | ||||
|     } | ||||
| 
 | ||||
|     public function updated(Budget $budget): void | ||||
|     { | ||||
|         Log::debug(sprintf('Observe "updated" of budget #%d ("%s").', $budget->id, $budget->name)); | ||||
|         $user   = $budget->user; | ||||
| 
 | ||||
|         /** @var MessageGeneratorInterface $engine */ | ||||
|         $engine = app(MessageGeneratorInterface::class); | ||||
|         $engine->setUser($user); | ||||
|         $engine->setObjects(new Collection()->push($budget)); | ||||
|         $engine->setTrigger(WebhookTrigger::UPDATE_BUDGET); | ||||
|         $engine->generateMessages(); | ||||
|         Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); | ||||
|         event(new RequestedSendWebhookMessages()); | ||||
|     } | ||||
| 
 | ||||
|     public function deleting(Budget $budget): void | ||||
|     { | ||||
|         app('log')->debug('Observe "deleting" of a budget.'); | ||||
|         Log::debug('Observe "deleting" of a budget.'); | ||||
| 
 | ||||
|         $user         = $budget->user; | ||||
| 
 | ||||
|         /** @var MessageGeneratorInterface $engine */ | ||||
|         $engine       = app(MessageGeneratorInterface::class); | ||||
|         $engine->setUser($user); | ||||
|         $engine->setObjects(new Collection()->push($budget)); | ||||
|         $engine->setTrigger(WebhookTrigger::DESTROY_BUDGET); | ||||
|         $engine->generateMessages(); | ||||
|         Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__)); | ||||
|         event(new RequestedSendWebhookMessages()); | ||||
| 
 | ||||
|         $repository   = app(AttachmentRepositoryInterface::class); | ||||
|         $repository->setUser($budget->user); | ||||
| @@ -49,7 +100,10 @@ class BudgetObserver | ||||
|         /** @var BudgetLimit $budgetLimit */ | ||||
|         foreach ($budgetLimits as $budgetLimit) { | ||||
|             // this loop exists so several events are fired.
 | ||||
|             $budgetLimit->delete(); | ||||
|             $copy     = clone $budgetLimit; | ||||
|             $copy->id = 0; | ||||
|             $this->updateAvailableBudget($copy); | ||||
|             $budgetLimit->deleteQuietly(); // delete is quietly when in a loop.
 | ||||
|         } | ||||
| 
 | ||||
|         $budget->notes()->delete(); | ||||
|   | ||||
| @@ -24,6 +24,7 @@ declare(strict_types=1); | ||||
| namespace FireflyIII\Handlers\Observer; | ||||
| 
 | ||||
| use FireflyIII\Models\TransactionGroup; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| /** | ||||
|  * Class TransactionGroup | ||||
| @@ -32,7 +33,7 @@ class TransactionGroupObserver | ||||
| { | ||||
|     public function deleting(TransactionGroup $transactionGroup): void | ||||
|     { | ||||
|         app('log')->debug('Observe "deleting" of a transaction group.'); | ||||
|         Log::debug('Observe "deleting" of a transaction group.'); | ||||
|         foreach ($transactionGroup->transactionJournals()->get() as $journal) { | ||||
|             $journal->delete(); | ||||
|         } | ||||
|   | ||||
| @@ -26,6 +26,7 @@ namespace FireflyIII\Handlers\Observer; | ||||
| use FireflyIII\Models\Attachment; | ||||
| use FireflyIII\Models\TransactionJournal; | ||||
| use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| /** | ||||
|  * Class TransactionJournalObserver | ||||
| @@ -34,7 +35,7 @@ class TransactionJournalObserver | ||||
| { | ||||
|     public function deleting(TransactionJournal $transactionJournal): void | ||||
|     { | ||||
|         app('log')->debug('Observe "deleting" of a transaction journal.'); | ||||
|         Log::debug('Observe "deleting" of a transaction journal.'); | ||||
| 
 | ||||
|         $repository = app(AttachmentRepositoryInterface::class); | ||||
|         $repository->setUser($transactionJournal->user); | ||||
|   | ||||
| @@ -72,7 +72,7 @@ class TransactionObserver | ||||
|         } | ||||
| 
 | ||||
|         $transaction->saveQuietly(); | ||||
|         Log::debug('Transaction primary currency amounts are updated.'); | ||||
|         Log::debug(sprintf('Transaction #%d primary currency amounts are updated.', $transaction->id)); | ||||
|     } | ||||
| 
 | ||||
|     public function deleting(?Transaction $transaction): void | ||||
|   | ||||
| @@ -158,18 +158,8 @@ class ShowController extends Controller | ||||
| 
 | ||||
|         Log::debug('End collect transactions'); | ||||
|         $timer->stop('collection'); | ||||
| 
 | ||||
|         // enrich data in arrays.
 | ||||
| 
 | ||||
|         // enrich
 | ||||
|         //        $enrichment   = new TransactionGroupEnrichment();
 | ||||
|         //        $enrichment->setUser(auth()->user());
 | ||||
|         //        $groups->setCollection($enrichment->enrich($groups->getCollection()));
 | ||||
| 
 | ||||
| 
 | ||||
|         $groups->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')])); | ||||
|         $showAll          = false; | ||||
|         // correct
 | ||||
|         $now              = today()->endOfDay(); | ||||
|         if ($now->gt($end) || $now->lt($start)) { | ||||
|             $now = $end; | ||||
|   | ||||
| @@ -129,7 +129,6 @@ class IndexController extends Controller | ||||
|             $spent    = $spentArr[$this->primaryCurrency->id]['sum'] ?? '0'; | ||||
|             unset($spentArr); | ||||
|         } | ||||
| 
 | ||||
|         // number of days for consistent budgeting.
 | ||||
|         $activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description.
 | ||||
|         $activeDaysLeft   = $this->activeDaysLeft($start, $end);   // see method description.
 | ||||
|   | ||||
| @@ -37,6 +37,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; | ||||
| use FireflyIII\Support\CacheProperties; | ||||
| use FireflyIII\Support\Facades\Preferences; | ||||
| use FireflyIII\Support\Facades\Steam; | ||||
| use FireflyIII\Support\Http\Api\ExchangeRateConverter; | ||||
| use FireflyIII\Support\Http\Controllers\AugumentData; | ||||
| use FireflyIII\Support\Http\Controllers\ChartGeneration; | ||||
| use FireflyIII\Support\Http\Controllers\DateCalculation; | ||||
| @@ -504,6 +505,7 @@ class AccountController extends Controller | ||||
|         Log::debug(sprintf('Step is %s', $step)); | ||||
|         $locale          = Steam::getLocale(); | ||||
|         $return          = []; | ||||
|         $converter       = new ExchangeRateConverter(); | ||||
| 
 | ||||
|         // 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.
 | ||||
| @@ -512,10 +514,10 @@ class AccountController extends Controller | ||||
|         $current         = app('navigation')->endOfX($current, $step, null); | ||||
|         $format          = (string)trans('config.month_and_day_js', [], $locale); | ||||
|         $accountCurrency = $this->accountRepository->getAccountCurrency($account); | ||||
| 
 | ||||
|         Log::debug('Get and filter balance for entire range start'); | ||||
|         $range           = Steam::finalAccountBalanceInRange($account, $start, $end, $this->convertToPrimary); | ||||
|         $range           = Steam::filterAccountBalances($range, $account, $this->convertToPrimary, $accountCurrency); | ||||
| 
 | ||||
|         Log::debug('Get and filter balance for entire range end'); | ||||
|         // temp, get end balance.
 | ||||
|         Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); | ||||
|         Steam::finalAccountBalance($account, $end); | ||||
| @@ -552,7 +554,15 @@ class AccountController extends Controller | ||||
|                     $carbon = Carbon::createFromFormat('Y-m-d', $newRange[$expectedIndex]['date'])->endOfDay(); | ||||
|                 } | ||||
|             } | ||||
|             Log::debug(sprintf('momentBalance is now %s', json_encode($momentBalance))); | ||||
|             Log::debug(sprintf('momentBalance[%s] is now %s', $current->format('Y-m-d H:i:s'), json_encode($momentBalance))); | ||||
| 
 | ||||
|             // check, perhaps recalculate the amount in currency X if the
 | ||||
|             if ($accountCurrency->id !== $this->primaryCurrency->id && $this->convertToPrimary && array_key_exists($accountCurrency->code, $momentBalance)) { | ||||
|                 $converted                   = $converter->convert($accountCurrency, $this->primaryCurrency, $current, $momentBalance[$accountCurrency->code]); | ||||
|                 $momentBalance['pc_balance'] = $converted; | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             $return        = $this->updateChartKeys($return, $momentBalance); | ||||
|             $previous      = $momentBalance; | ||||
| 
 | ||||
|   | ||||
| @@ -105,7 +105,6 @@ class EditController extends Controller | ||||
|         /** @var RecurrenceTransformer $transformer */ | ||||
|         $transformer                      = app(RecurrenceTransformer::class); | ||||
|         $transformer->setParameters(new ParameterBag()); | ||||
| 
 | ||||
|         $array                            = $transformer->transform($recurrence); | ||||
|         $budgets                          = ExpandedForm::makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets()); | ||||
|         $bills                            = ExpandedForm::makeSelectListWithEmpty($this->billRepository->getActiveBills()); | ||||
|   | ||||
| @@ -37,6 +37,7 @@ use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Collection; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| /** | ||||
|  * Class CreateAutoBudgetLimits | ||||
| @@ -59,7 +60,7 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|             $newDate    = clone $date; | ||||
|             $newDate->startOfDay(); | ||||
|             $this->date = $newDate; | ||||
|             app('log')->debug(sprintf('Created new CreateAutoBudgetLimits("%s")', $this->date->format('Y-m-d'))); | ||||
|             Log::debug(sprintf('Created new CreateAutoBudgetLimits("%s")', $this->date->format('Y-m-d'))); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @@ -70,9 +71,9 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|      */ | ||||
|     public function handle(): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now at start of CreateAutoBudgetLimits() job for %s.', $this->date->format('D d M Y'))); | ||||
|         Log::debug(sprintf('Now at start of CreateAutoBudgetLimits() job for %s.', $this->date->format('D d M Y'))); | ||||
|         $autoBudgets = AutoBudget::get(); | ||||
|         app('log')->debug(sprintf('Found %d auto budgets.', $autoBudgets->count())); | ||||
|         Log::debug(sprintf('Found %d auto budgets.', $autoBudgets->count())); | ||||
|         foreach ($autoBudgets as $autoBudget) { | ||||
|             $this->handleAutoBudget($autoBudget); | ||||
|         } | ||||
| @@ -84,18 +85,18 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|     private function handleAutoBudget(AutoBudget $autoBudget): void | ||||
|     { | ||||
|         if (null === $autoBudget->budget) { | ||||
|             app('log')->info(sprintf('Auto budget #%d is associated with a deleted budget.', $autoBudget->id)); | ||||
|             Log::info(sprintf('Auto budget #%d is associated with a deleted budget.', $autoBudget->id)); | ||||
|             $autoBudget->delete(); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         if (false === $autoBudget->budget->active) { | ||||
|             app('log')->info(sprintf('Auto budget #%d is associated with an inactive budget.', $autoBudget->id)); | ||||
|             Log::info(sprintf('Auto budget #%d is associated with an inactive budget.', $autoBudget->id)); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         if (!$this->isMagicDay($autoBudget)) { | ||||
|             app('log')->info( | ||||
|             Log::info( | ||||
|                 sprintf( | ||||
|                     'Today (%s) is not a magic day for %s auto-budget #%d (part of budget #%d "%s")', | ||||
|                     $this->date->format('Y-m-d'), | ||||
| @@ -105,11 +106,11 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|                     $autoBudget->budget->name | ||||
|                 ) | ||||
|             ); | ||||
|             app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|             Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         app('log')->info( | ||||
|         Log::info( | ||||
|             sprintf( | ||||
|                 'Today (%s) is a magic day for %s auto-budget #%d (part of budget #%d "%s")', | ||||
|                 $this->date->format('Y-m-d'), | ||||
| @@ -131,7 +132,7 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|             // that's easy: create one.
 | ||||
|             // do nothing else.
 | ||||
|             $this->createBudgetLimit($autoBudget, $start, $end); | ||||
|             app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|             Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| @@ -139,18 +140,18 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|         if (!$budgetLimit instanceof BudgetLimit && AutoBudgetType::AUTO_BUDGET_ROLLOVER->value === (int) $autoBudget->auto_budget_type) { | ||||
|             // budget limit exists already,
 | ||||
|             $this->createRollover($autoBudget); | ||||
|             app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|             Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         if (!$budgetLimit instanceof BudgetLimit && AutoBudgetType::AUTO_BUDGET_ADJUSTED->value === (int) $autoBudget->auto_budget_type) { | ||||
|             // budget limit exists already,
 | ||||
|             $this->createAdjustedLimit($autoBudget); | ||||
|             app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|             Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|         Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -193,7 +194,7 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
| 
 | ||||
|     private function findBudgetLimit(Budget $budget, Carbon $start, Carbon $end): ?BudgetLimit | ||||
|     { | ||||
|         app('log')->debug( | ||||
|         Log::debug( | ||||
|             sprintf( | ||||
|                 'Going to find a budget limit for budget #%d ("%s") between %s and %s', | ||||
|                 $budget->id, | ||||
| @@ -212,21 +213,21 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
| 
 | ||||
|     private function createBudgetLimit(AutoBudget $autoBudget, Carbon $start, Carbon $end, ?string $amount = null): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('No budget limit exist. Must create one for auto-budget #%d', $autoBudget->id)); | ||||
|         Log::debug(sprintf('No budget limit exist. Must create one for auto-budget #%d', $autoBudget->id)); | ||||
|         if (null !== $amount) { | ||||
|             app('log')->debug(sprintf('Amount is overruled and will be set to %s', $amount)); | ||||
|             Log::debug(sprintf('Amount is overruled and will be set to %s', $amount)); | ||||
|         } | ||||
|         $budgetLimit             = new BudgetLimit(); | ||||
|         $budgetLimit->budget()->associate($autoBudget->budget); | ||||
|         $budgetLimit->transactionCurrency()->associate($autoBudget->transactionCurrency); | ||||
|         $budgetLimit->start_date = $start; | ||||
|         $budgetLimit->end_date   = $end; | ||||
|         $budgetLimit->start_date = clone $start; | ||||
|         $budgetLimit->end_date   = clone $end; | ||||
|         $budgetLimit->amount     = $amount ?? $autoBudget->amount; | ||||
|         $budgetLimit->period     = $autoBudget->period; | ||||
|         $budgetLimit->generated  = 1; | ||||
|         $budgetLimit->save(); | ||||
| 
 | ||||
|         app('log')->debug(sprintf('Created budget limit #%d.', $budgetLimit->id)); | ||||
|         Log::debug(sprintf('Created budget limit #%d.', $budgetLimit->id)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -234,7 +235,7 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|      */ | ||||
|     private function createRollover(AutoBudget $autoBudget): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id)); | ||||
|         Log::debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id)); | ||||
|         // current period:
 | ||||
|         $start         = app('navigation')->startOfPeriod($this->date, $autoBudget->period); | ||||
|         $end           = app('navigation')->endOfPeriod($start, $autoBudget->period); | ||||
| @@ -243,7 +244,7 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|         $previousStart = app('navigation')->subtractPeriod($start, $autoBudget->period); | ||||
|         $previousEnd   = app('navigation')->endOfPeriod($previousStart, $autoBudget->period); | ||||
| 
 | ||||
|         app('log')->debug( | ||||
|         Log::debug( | ||||
|             sprintf( | ||||
|                 'Current period is %s-%s, so previous period is %s-%s', | ||||
|                 $start->format('Y-m-d'), | ||||
| @@ -257,44 +258,44 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|         $budgetLimit   = $this->findBudgetLimit($autoBudget->budget, $previousStart, $previousEnd); | ||||
| 
 | ||||
|         if (!$budgetLimit instanceof BudgetLimit) { | ||||
|             app('log')->debug('No budget limit exists in previous period, so create one.'); | ||||
|             Log::debug('No budget limit exists in previous period, so create one.'); | ||||
|             // if not, create it and we're done.
 | ||||
|             $this->createBudgetLimit($autoBudget, $start, $end); | ||||
|             app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|             Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug('Budget limit exists for previous period.'); | ||||
|         Log::debug('Budget limit exists for previous period.'); | ||||
|         // if has one, calculate expenses and use that as a base.
 | ||||
|         $repository    = app(OperationsRepositoryInterface::class); | ||||
|         $repository->setUser($autoBudget->budget->user); | ||||
|         $spent         = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency); | ||||
|         $currencyId    = $autoBudget->transaction_currency_id; | ||||
|         $spentAmount   = $spent[$currencyId]['sum'] ?? '0'; | ||||
|         app('log')->debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); | ||||
|         Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); | ||||
| 
 | ||||
|         // if you spent more in previous budget period, than whatever you had previous budget period, the amount resets
 | ||||
|         // previous budget limit + spent
 | ||||
|         $budgetLeft    = bcadd($budgetLimit->amount, $spentAmount); | ||||
|         $totalAmount   = $autoBudget->amount; | ||||
|         app('log')->debug(sprintf('Total amount left for previous budget period is %s', $budgetLeft)); | ||||
|         Log::debug(sprintf('Total amount left for previous budget period is %s', $budgetLeft)); | ||||
| 
 | ||||
|         if (-1 !== bccomp('0', $budgetLeft)) { | ||||
|             app('log')->info(sprintf('The amount left is negative, so it will be reset to %s.', $totalAmount)); | ||||
|             Log::info(sprintf('The amount left is negative, so it will be reset to %s.', $totalAmount)); | ||||
|         } | ||||
|         if (1 !== bccomp('0', $budgetLeft)) { | ||||
|             $totalAmount = bcadd($budgetLeft, $totalAmount); | ||||
|             app('log')->info(sprintf('The amount left is positive, so the new amount will be %s.', $totalAmount)); | ||||
|             Log::info(sprintf('The amount left is positive, so the new amount will be %s.', $totalAmount)); | ||||
|         } | ||||
| 
 | ||||
|         // create budget limit:
 | ||||
|         $this->createBudgetLimit($autoBudget, $start, $end, $totalAmount); | ||||
|         app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|         Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|     } | ||||
| 
 | ||||
|     private function createAdjustedLimit(AutoBudget $autoBudget): void | ||||
|     { | ||||
|         app('log')->debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id)); | ||||
|         Log::debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id)); | ||||
|         // current period:
 | ||||
|         $start           = app('navigation')->startOfPeriod($this->date, $autoBudget->period); | ||||
|         $end             = app('navigation')->endOfPeriod($start, $autoBudget->period); | ||||
| @@ -303,7 +304,7 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|         $previousStart   = app('navigation')->subtractPeriod($start, $autoBudget->period); | ||||
|         $previousEnd     = app('navigation')->endOfPeriod($previousStart, $autoBudget->period); | ||||
| 
 | ||||
|         app('log')->debug( | ||||
|         Log::debug( | ||||
|             sprintf( | ||||
|                 'Current period is %s-%s, so previous period is %s-%s', | ||||
|                 $start->format('Y-m-d'), | ||||
| @@ -317,13 +318,13 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|         $budgetLimit     = $this->findBudgetLimit($autoBudget->budget, $previousStart, $previousEnd); | ||||
| 
 | ||||
|         if (!$budgetLimit instanceof BudgetLimit) { | ||||
|             app('log')->debug('No budget limit exists in previous period, so create one.'); | ||||
|             Log::debug('No budget limit exists in previous period, so create one.'); | ||||
|             // if not, create standard amount, and we're done.
 | ||||
|             $this->createBudgetLimit($autoBudget, $start, $end); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         app('log')->debug('Budget limit exists for previous period.'); | ||||
|         Log::debug('Budget limit exists for previous period.'); | ||||
| 
 | ||||
|         // if has one, calculate expenses and use that as a base.
 | ||||
|         $repository      = app(OperationsRepositoryInterface::class); | ||||
| @@ -331,31 +332,31 @@ class CreateAutoBudgetLimits implements ShouldQueue | ||||
|         $spent           = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency); | ||||
|         $currencyId      = $autoBudget->transaction_currency_id; | ||||
|         $spentAmount     = $spent[$currencyId]['sum'] ?? '0'; | ||||
|         app('log')->debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); | ||||
|         Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount)); | ||||
| 
 | ||||
|         // what you spent in previous period PLUS the amount for the current period,
 | ||||
|         // if that is more than zero, that's the amount that will be set.
 | ||||
| 
 | ||||
|         $budgetAvailable = bcadd(bcadd($budgetLimit->amount, $autoBudget->amount), $spentAmount); | ||||
|         $totalAmount     = $autoBudget->amount; | ||||
|         app('log')->debug(sprintf('Total amount available for current budget period is %s', $budgetAvailable)); | ||||
|         Log::debug(sprintf('Total amount available for current budget period is %s', $budgetAvailable)); | ||||
| 
 | ||||
|         if (-1 !== bccomp($budgetAvailable, $totalAmount)) { | ||||
|             app('log')->info(sprintf('There is no overspending, no need to adjust. Budget limit amount will be %s.', $budgetAvailable)); | ||||
|             Log::info(sprintf('There is no overspending, no need to adjust. Budget limit amount will be %s.', $budgetAvailable)); | ||||
|             // create budget limit:
 | ||||
|             $this->createBudgetLimit($autoBudget, $start, $end, $budgetAvailable); | ||||
|         } | ||||
|         if (1 !== bccomp($budgetAvailable, $totalAmount) && 1 === bccomp($budgetAvailable, '0')) { | ||||
|             app('log')->info(sprintf('There was overspending, so the new amount will be %s.', $budgetAvailable)); | ||||
|             Log::info(sprintf('There was overspending, so the new amount will be %s.', $budgetAvailable)); | ||||
|             // create budget limit:
 | ||||
|             $this->createBudgetLimit($autoBudget, $start, $end, $budgetAvailable); | ||||
|         } | ||||
|         if (1 !== bccomp($budgetAvailable, $totalAmount) && -1 === bccomp($budgetAvailable, '0')) { | ||||
|             app('log')->info('There was overspending, but so much even this period cant fix that. Reset it to 1.'); | ||||
|             Log::info('There was overspending, but so much even this period cant fix that. Reset it to 1.'); | ||||
|             // create budget limit:
 | ||||
|             $this->createBudgetLimit($autoBudget, $start, $end, '1'); | ||||
|         } | ||||
|         app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|         Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id)); | ||||
|     } | ||||
| 
 | ||||
|     public function setDate(Carbon $date): void | ||||
|   | ||||
| @@ -24,9 +24,6 @@ declare(strict_types=1); | ||||
| namespace FireflyIII\Models; | ||||
| 
 | ||||
| use FireflyIII\Casts\SeparateTimezoneCaster; | ||||
| use FireflyIII\Events\Model\BudgetLimit\Created; | ||||
| use FireflyIII\Events\Model\BudgetLimit\Deleted; | ||||
| use FireflyIII\Events\Model\BudgetLimit\Updated; | ||||
| use FireflyIII\Support\Models\ReturnsIntegerIdTrait; | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| @@ -37,12 +34,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||||
| class BudgetLimit extends Model | ||||
| { | ||||
|     use ReturnsIntegerIdTrait; | ||||
|     protected $dispatchesEvents | ||||
|                         = [ | ||||
|             'created' => Created::class, | ||||
|             'updated' => Updated::class, | ||||
|             'deleted' => Deleted::class, | ||||
|         ]; | ||||
| 
 | ||||
|     protected $fillable = ['budget_id', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'amount', 'transaction_currency_id', 'native_amount']; | ||||
| 
 | ||||
|   | ||||
| @@ -24,14 +24,15 @@ declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Models; | ||||
| 
 | ||||
| use FireflyIII\Enums\WebhookDelivery; | ||||
| use FireflyIII\Enums\WebhookResponse; | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| use FireflyIII\Enums\WebhookDelivery as WebhookDeliveryEnum; | ||||
| use FireflyIII\Enums\WebhookResponse as WebhookResponseEnum; | ||||
| use FireflyIII\Enums\WebhookTrigger as WebhookTriggerEnum; | ||||
| use FireflyIII\Support\Models\ReturnsIntegerIdTrait; | ||||
| use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait; | ||||
| use FireflyIII\User; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||
| use Illuminate\Database\Eloquent\Relations\BelongsToMany; | ||||
| use Illuminate\Database\Eloquent\Relations\HasMany; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||||
| @@ -56,7 +57,7 @@ class Webhook extends Model | ||||
|     public static function getDeliveries(): array | ||||
|     { | ||||
|         $array = []; | ||||
|         $set   = WebhookDelivery::cases(); | ||||
|         $set   = WebhookDeliveryEnum::cases(); | ||||
|         foreach ($set as $item) { | ||||
|             $array[$item->value] = $item->name; | ||||
|         } | ||||
| @@ -67,7 +68,7 @@ class Webhook extends Model | ||||
|     public static function getDeliveriesForValidation(): array | ||||
|     { | ||||
|         $array = []; | ||||
|         $set   = WebhookDelivery::cases(); | ||||
|         $set   = WebhookDeliveryEnum::cases(); | ||||
|         foreach ($set as $item) { | ||||
|             $array[$item->name]  = $item->value; | ||||
|             $array[$item->value] = $item->value; | ||||
| @@ -79,7 +80,7 @@ class Webhook extends Model | ||||
|     public static function getResponses(): array | ||||
|     { | ||||
|         $array = []; | ||||
|         $set   = WebhookResponse::cases(); | ||||
|         $set   = WebhookResponseEnum::cases(); | ||||
|         foreach ($set as $item) { | ||||
|             $array[$item->value] = $item->name; | ||||
|         } | ||||
| @@ -90,7 +91,7 @@ class Webhook extends Model | ||||
|     public static function getResponsesForValidation(): array | ||||
|     { | ||||
|         $array = []; | ||||
|         $set   = WebhookResponse::cases(); | ||||
|         $set   = WebhookResponseEnum::cases(); | ||||
|         foreach ($set as $item) { | ||||
|             $array[$item->name]  = $item->value; | ||||
|             $array[$item->value] = $item->value; | ||||
| @@ -102,7 +103,7 @@ class Webhook extends Model | ||||
|     public static function getTriggers(): array | ||||
|     { | ||||
|         $array = []; | ||||
|         $set   = WebhookTrigger::cases(); | ||||
|         $set   = WebhookTriggerEnum::cases(); | ||||
|         foreach ($set as $item) { | ||||
|             $array[$item->value] = $item->name; | ||||
|         } | ||||
| @@ -113,7 +114,7 @@ class Webhook extends Model | ||||
|     public static function getTriggersForValidation(): array | ||||
|     { | ||||
|         $array = []; | ||||
|         $set   = WebhookTrigger::cases(); | ||||
|         $set   = WebhookTriggerEnum::cases(); | ||||
|         foreach ($set as $item) { | ||||
|             $array[$item->name]  = $item->value; | ||||
|             $array[$item->value] = $item->value; | ||||
| @@ -130,7 +131,7 @@ class Webhook extends Model | ||||
|     public static function routeBinder(string $value): self | ||||
|     { | ||||
|         if (auth()->check()) { | ||||
|             $webhookId = (int) $value; | ||||
|             $webhookId = (int)$value; | ||||
| 
 | ||||
|             /** @var User $user */ | ||||
|             $user      = auth()->user(); | ||||
| @@ -155,6 +156,21 @@ class Webhook extends Model | ||||
|         return $this->hasMany(WebhookMessage::class); | ||||
|     } | ||||
| 
 | ||||
|     public function webhookDeliveries(): BelongsToMany | ||||
|     { | ||||
|         return $this->belongsToMany(WebhookDelivery::class); | ||||
|     } | ||||
| 
 | ||||
|     public function webhookResponses(): BelongsToMany | ||||
|     { | ||||
|         return $this->belongsToMany(WebhookResponse::class); | ||||
|     } | ||||
| 
 | ||||
|     public function webhookTriggers(): BelongsToMany | ||||
|     { | ||||
|         return $this->belongsToMany(WebhookTrigger::class); | ||||
|     } | ||||
| 
 | ||||
|     protected function casts(): array | ||||
|     { | ||||
|         return [ | ||||
|   | ||||
							
								
								
									
										26
									
								
								app/Models/WebhookDelivery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/Models/WebhookDelivery.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Models; | ||||
| 
 | ||||
| use FireflyIII\Support\Models\ReturnsIntegerIdTrait; | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| 
 | ||||
| class WebhookDelivery extends Model | ||||
| { | ||||
|     use ReturnsIntegerIdTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * Get the ID | ||||
|      * | ||||
|      * @SuppressWarnings("PHPMD.ShortMethodName") | ||||
|      */ | ||||
|     protected function key(): Attribute | ||||
|     { | ||||
|         return Attribute::make( | ||||
|             get: static fn ($value) => (int) $value, | ||||
|         ); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										26
									
								
								app/Models/WebhookResponse.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/Models/WebhookResponse.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Models; | ||||
| 
 | ||||
| use FireflyIII\Support\Models\ReturnsIntegerIdTrait; | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| 
 | ||||
| class WebhookResponse extends Model | ||||
| { | ||||
|     use ReturnsIntegerIdTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * Get the ID | ||||
|      * | ||||
|      * @SuppressWarnings("PHPMD.ShortMethodName") | ||||
|      */ | ||||
|     protected function key(): Attribute | ||||
|     { | ||||
|         return Attribute::make( | ||||
|             get: static fn ($value) => (int) $value, | ||||
|         ); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										26
									
								
								app/Models/WebhookTrigger.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/Models/WebhookTrigger.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Models; | ||||
| 
 | ||||
| use FireflyIII\Support\Models\ReturnsIntegerIdTrait; | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| 
 | ||||
| class WebhookTrigger extends Model | ||||
| { | ||||
|     use ReturnsIntegerIdTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * Get the ID | ||||
|      * | ||||
|      * @SuppressWarnings("PHPMD.ShortMethodName") | ||||
|      */ | ||||
|     protected function key(): Attribute | ||||
|     { | ||||
|         return Attribute::make( | ||||
|             get: static fn ($value) => (int) $value, | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| @@ -29,9 +29,6 @@ use FireflyIII\Events\DestroyedTransactionGroup; | ||||
| use FireflyIII\Events\DetectedNewIPAddress; | ||||
| use FireflyIII\Events\Model\Bill\WarnUserAboutBill; | ||||
| use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions; | ||||
| use FireflyIII\Events\Model\BudgetLimit\Created; | ||||
| use FireflyIII\Events\Model\BudgetLimit\Deleted; | ||||
| use FireflyIII\Events\Model\BudgetLimit\Updated; | ||||
| use FireflyIII\Events\Model\PiggyBank\ChangedAmount; | ||||
| use FireflyIII\Events\Model\PiggyBank\ChangedName; | ||||
| use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray; | ||||
| @@ -219,17 +216,6 @@ class EventServiceProvider extends ServiceProvider | ||||
|                 'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName', | ||||
|             ], | ||||
| 
 | ||||
|             // budget related events: CRUD budget limit
 | ||||
|             Created::class                           => [ | ||||
|                 'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@created', | ||||
|             ], | ||||
|             Updated::class                           => [ | ||||
|                 'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@updated', | ||||
|             ], | ||||
|             Deleted::class                           => [ | ||||
|                 'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@deleted', | ||||
|             ], | ||||
| 
 | ||||
|             // rule actions
 | ||||
|             RuleActionFailedOnArray::class           => [ | ||||
|                 'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnArray', | ||||
|   | ||||
| @@ -268,7 +268,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface | ||||
|          */ | ||||
|         foreach ($budgets as $index => $budget) { | ||||
|             $budget->order = $index + 1; | ||||
|             $budget->save(); | ||||
|             $budget->saveQuietly(); | ||||
|         } | ||||
|         // other budgets, set to 0.
 | ||||
|         $this->user->budgets()->where('active', 0)->update(['order' => 0]); | ||||
|   | ||||
| @@ -24,9 +24,13 @@ declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Repositories\Webhook; | ||||
| 
 | ||||
| use FireflyIII\Exceptions\FireflyException; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Models\WebhookAttempt; | ||||
| use FireflyIII\Models\WebhookDelivery; | ||||
| use FireflyIII\Models\WebhookMessage; | ||||
| use FireflyIII\Models\WebhookResponse; | ||||
| use FireflyIII\Models\WebhookTrigger; | ||||
| use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface; | ||||
| use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait; | ||||
| use Illuminate\Support\Collection; | ||||
| @@ -41,11 +45,20 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac | ||||
| 
 | ||||
|     public function all(): Collection | ||||
|     { | ||||
|         return $this->user->webhooks()->get(); | ||||
|         return $this->user->webhooks() | ||||
|             // only get upgraded webhooks
 | ||||
|             ->where('delivery', 1) | ||||
|             ->where('response', 1) | ||||
|             ->where('trigger', 1) | ||||
|             ->get() | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     public function destroy(Webhook $webhook): void | ||||
|     { | ||||
|         // force delete all messages and attempts:
 | ||||
|         $webhook->webhookMessages()->delete(); | ||||
| 
 | ||||
|         $webhook->delete(); | ||||
|     } | ||||
| 
 | ||||
| @@ -87,38 +100,108 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac | ||||
| 
 | ||||
|     public function store(array $data): Webhook | ||||
|     { | ||||
|         $secret   = Str::random(24); | ||||
|         $fullData = [ | ||||
|         $secret     = Str::random(24); | ||||
|         $fullData   = [ | ||||
|             'user_id'       => $this->user->id, | ||||
|             'user_group_id' => $this->user->user_group_id, | ||||
|             'active'        => $data['active'] ?? false, | ||||
|             'title'         => $data['title'] ?? null, | ||||
|             'trigger'       => $data['trigger'], | ||||
|             'response'      => $data['response'], | ||||
|             'delivery'      => $data['delivery'], | ||||
|             //            'trigger'       => $data['trigger'],
 | ||||
|             //            'response'      => $data['response'],
 | ||||
|             //            'delivery'      => $data['delivery'],
 | ||||
|             'trigger'       => 1, | ||||
|             'response'      => 1, | ||||
|             'delivery'      => 1, | ||||
|             'secret'        => $secret, | ||||
|             'url'           => $data['url'], | ||||
|         ]; | ||||
| 
 | ||||
|         return Webhook::create($fullData); | ||||
|         /** @var Webhook $webhook */ | ||||
|         $webhook    = Webhook::create($fullData); | ||||
|         $triggers   = new Collection(); | ||||
|         $responses  = new Collection(); | ||||
|         $deliveries = new Collection(); | ||||
| 
 | ||||
|         foreach ($data['triggers'] as $trigger) { | ||||
|             // get the relevant ID:
 | ||||
|             $object = WebhookTrigger::where('title', $trigger)->first(); | ||||
|             if (null === $object) { | ||||
|                 throw new FireflyException(sprintf('Could not find webhook trigger with title "%s".', $trigger)); | ||||
|             } | ||||
|             $triggers->push($object); | ||||
|         } | ||||
|         $webhook->webhookTriggers()->saveMany($triggers); | ||||
| 
 | ||||
|         foreach ($data['responses'] as $response) { | ||||
|             // get the relevant ID:
 | ||||
|             $object = WebhookResponse::where('title', $response)->first(); | ||||
|             if (null === $object) { | ||||
|                 throw new FireflyException(sprintf('Could not find webhook response with title "%s".', $response)); | ||||
|             } | ||||
|             $responses->push($object); | ||||
|         } | ||||
|         $webhook->webhookResponses()->saveMany($responses); | ||||
| 
 | ||||
|         foreach ($data['deliveries'] as $delivery) { | ||||
|             // get the relevant ID:
 | ||||
|             $object = WebhookDelivery::where('title', $delivery)->first(); | ||||
|             if (null === $object) { | ||||
|                 throw new FireflyException(sprintf('Could not find webhook delivery with title "%s".', $delivery)); | ||||
|             } | ||||
|             $deliveries->push($object); | ||||
|         } | ||||
|         $webhook->webhookDeliveries()->saveMany($deliveries); | ||||
| 
 | ||||
|         return $webhook; | ||||
|     } | ||||
| 
 | ||||
|     public function update(Webhook $webhook, array $data): Webhook | ||||
|     { | ||||
|         $webhook->active   = $data['active'] ?? $webhook->active; | ||||
|         $webhook->trigger  = $data['trigger'] ?? $webhook->trigger; | ||||
|         $webhook->response = $data['response'] ?? $webhook->response; | ||||
|         $webhook->delivery = $data['delivery'] ?? $webhook->delivery; | ||||
|         $webhook->title    = $data['title'] ?? $webhook->title; | ||||
|         $webhook->url      = $data['url'] ?? $webhook->url; | ||||
|         $webhook->active = $data['active'] ?? $webhook->active; | ||||
|         $webhook->title  = $data['title'] ?? $webhook->title; | ||||
|         $webhook->url    = $data['url'] ?? $webhook->url; | ||||
| 
 | ||||
|         if (true === $data['secret']) { | ||||
|         if (array_key_exists('secret', $data) && true === $data['secret']) { | ||||
|             $secret          = Str::random(24); | ||||
|             $webhook->secret = $secret; | ||||
|         } | ||||
| 
 | ||||
|         $webhook->save(); | ||||
| 
 | ||||
|         $triggers        = new Collection(); | ||||
|         $responses       = new Collection(); | ||||
|         $deliveries      = new Collection(); | ||||
| 
 | ||||
|         foreach ($data['triggers'] as $trigger) { | ||||
|             // get the relevant ID:
 | ||||
|             $object = WebhookTrigger::where('title', $trigger)->first(); | ||||
|             if (null === $object) { | ||||
|                 throw new FireflyException(sprintf('Could not find webhook trigger with title "%s".', $trigger)); | ||||
|             } | ||||
|             $triggers->push($object); | ||||
|         } | ||||
|         $webhook->webhookTriggers()->sync($triggers); | ||||
| 
 | ||||
|         foreach ($data['responses'] as $response) { | ||||
|             // get the relevant ID:
 | ||||
|             $object = WebhookResponse::where('title', $response)->first(); | ||||
|             if (null === $object) { | ||||
|                 throw new FireflyException(sprintf('Could not find webhook response with title "%s".', $response)); | ||||
|             } | ||||
|             $responses->push($object); | ||||
|         } | ||||
|         $webhook->webhookResponses()->sync($responses); | ||||
| 
 | ||||
|         foreach ($data['deliveries'] as $delivery) { | ||||
|             // get the relevant ID:
 | ||||
|             $object = WebhookDelivery::where('title', $delivery)->first(); | ||||
|             if (null === $object) { | ||||
|                 throw new FireflyException(sprintf('Could not find webhook delivery with title "%s".', $delivery)); | ||||
|             } | ||||
|             $deliveries->push($object); | ||||
|         } | ||||
|         $webhook->webhookDeliveries()->sync($deliveries); | ||||
| 
 | ||||
|         return $webhook; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -52,6 +52,7 @@ class EitherConfigKey | ||||
|             'firefly.languages', | ||||
|             'app.timezone', | ||||
|             'firefly.valid_view_ranges', | ||||
|             'firefly.preselected_accounts', | ||||
| 
 | ||||
|             // triggers and actions:
 | ||||
|             'firefly.rule-actions', | ||||
|   | ||||
| @@ -103,7 +103,7 @@ class ExchangeRateConverter | ||||
| 
 | ||||
|         // find in cache
 | ||||
|         if (null !== $res) { | ||||
|             Log::debug(sprintf('ExchangeRateConverter: Return cached rate from %s to %s on %s.', $from->code, $to->code, $date->format('Y-m-d'))); | ||||
|             Log::debug(sprintf('ExchangeRateConverter: Return cached rate (%s) from %s to %s on %s.', $res, $from->code, $to->code, $date->format('Y-m-d'))); | ||||
| 
 | ||||
|             return $res; | ||||
|         } | ||||
|   | ||||
| @@ -249,14 +249,14 @@ class AccountEnrichment implements EnrichmentInterface | ||||
|                 'opening_balance_date'   => null, | ||||
|                 'opening_balance_amount' => null, | ||||
|                 'account_number'         => null, | ||||
|                 'notes'                  => $notes[$id] ?? null, | ||||
|                 'notes'                  => $this->notes[$id] ?? null, | ||||
|                 'last_activity'          => $this->lastActivities[$id] ?? null, | ||||
|             ]; | ||||
| 
 | ||||
|             // add object group if available
 | ||||
|             if (array_key_exists($id, $this->mappedObjects)) { | ||||
|                 $key                        = $this->mappedObjects[$id]; | ||||
|                 $meta['object_group_id']    = $this->objectGroups[$key]['id']; | ||||
|                 $meta['object_group_id']    = (string) $this->objectGroups[$key]['id']; | ||||
|                 $meta['object_group_title'] = $this->objectGroups[$key]['title']; | ||||
|                 $meta['object_group_order'] = $this->objectGroups[$key]['order']; | ||||
|             } | ||||
|   | ||||
| @@ -117,7 +117,7 @@ class BudgetEnrichment implements EnrichmentInterface | ||||
|             // add object group if available
 | ||||
|             if (array_key_exists($id, $this->mappedObjects)) { | ||||
|                 $key                        = $this->mappedObjects[$id]; | ||||
|                 $meta['object_group_id']    = $this->objectGroups[$key]['id']; | ||||
|                 $meta['object_group_id']    = (string) $this->objectGroups[$key]['id']; | ||||
|                 $meta['object_group_title'] = $this->objectGroups[$key]['title']; | ||||
|                 $meta['object_group_order'] = $this->objectGroups[$key]['order']; | ||||
|             } | ||||
|   | ||||
| @@ -135,6 +135,7 @@ class BudgetLimitEnrichment implements EnrichmentInterface | ||||
|         /** @var BudgetLimit $budgetLimit */ | ||||
|         foreach ($this->collection as $budgetLimit) { | ||||
|             $id                  = (int)$budgetLimit->id; | ||||
|             $filteredExpenses    = $this->filterToBudget($expenses, $budgetLimit->budget_id); | ||||
|             $filteredExpenses    = $repository->sumCollectedExpenses($expenses, $budgetLimit->start_date, $budgetLimit->end_date, $budgetLimit->transactionCurrency, false); | ||||
|             $this->expenses[$id] = array_values($filteredExpenses); | ||||
| 
 | ||||
| @@ -175,4 +176,11 @@ class BudgetLimitEnrichment implements EnrichmentInterface | ||||
|             }, $first); | ||||
|         }, $this->expenses); | ||||
|     } | ||||
| 
 | ||||
|     private function filterToBudget(array $expenses, int $budget): array | ||||
|     { | ||||
|         return array_filter($expenses, function (array $item) use ($budget) { | ||||
|             return (int)$item['budget_id'] === $budget; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -129,7 +129,7 @@ class RecurringEnrichment implements EnrichmentInterface | ||||
|             $recurrence                     = $this->collection->filter(function (Recurrence $item) use ($repetition) { | ||||
|                 return (int)$item->id === (int)$repetition->recurrence_id; | ||||
|             })->first(); | ||||
|             $fromDate                       = $recurrence->latest_date ?? $recurrence->first_date; | ||||
|             $fromDate                       = clone ($recurrence->latest_date ?? $recurrence->first_date); | ||||
|             $id                             = (int)$repetition->recurrence_id; | ||||
|             $repId                          = (int)$repetition->id; | ||||
|             $this->repetitions[$id] ??= []; | ||||
| @@ -137,6 +137,7 @@ class RecurringEnrichment implements EnrichmentInterface | ||||
|             // get the (future) occurrences for this specific type of repetition:
 | ||||
|             $amount                         = 'daily' === $repetition->repetition_type ? 9 : 5; | ||||
|             $set                            = $repository->getXOccurrencesSince($repetition, $fromDate, now(config('app.timezone')), $amount); | ||||
|             $occurrences                    = []; | ||||
| 
 | ||||
|             /** @var Carbon $carbon */ | ||||
|             foreach ($set as $carbon) { | ||||
|   | ||||
| @@ -101,7 +101,7 @@ class SubscriptionEnrichment implements EnrichmentInterface | ||||
|             // add object group if available
 | ||||
|             if (array_key_exists($id, $this->mappedObjects)) { | ||||
|                 $key                        = $this->mappedObjects[$id]; | ||||
|                 $meta['object_group_id']    = $objectGroups[$key]['id']; | ||||
|                 $meta['object_group_id']    = (string) $objectGroups[$key]['id']; | ||||
|                 $meta['object_group_title'] = $objectGroups[$key]['title']; | ||||
|                 $meta['object_group_order'] = $objectGroups[$key]['order']; | ||||
|             } | ||||
|   | ||||
							
								
								
									
										143
									
								
								app/Support/JsonApi/Enrichments/WebhookEnrichment.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								app/Support/JsonApi/Enrichments/WebhookEnrichment.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Support\JsonApi\Enrichments; | ||||
| 
 | ||||
| use FireflyIII\Enums\WebhookDelivery as WebhookDeliveryEnum; | ||||
| use FireflyIII\Enums\WebhookResponse as WebhookResponseEnum; | ||||
| use FireflyIII\Enums\WebhookTrigger as WebhookTriggerEnum; | ||||
| use FireflyIII\Models\UserGroup; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use FireflyIII\Models\WebhookDelivery; | ||||
| use FireflyIII\Models\WebhookResponse; | ||||
| use FireflyIII\Models\WebhookTrigger; | ||||
| use FireflyIII\User; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| use Illuminate\Support\Collection; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| use stdClass; | ||||
| 
 | ||||
| class WebhookEnrichment implements EnrichmentInterface | ||||
| { | ||||
|     private Collection $collection; | ||||
|     private User       $user; | ||||
|     private UserGroup  $userGroup; | ||||
|     private array      $ids          = []; | ||||
|     private array      $deliveries   = []; | ||||
|     private array      $responses    = []; | ||||
|     private array      $triggers     = []; | ||||
| 
 | ||||
|     private array $webhookDeliveries = []; | ||||
|     private array $webhookResponses  = []; | ||||
|     private array $webhookTriggers   = []; | ||||
| 
 | ||||
|     public function enrich(Collection $collection): Collection | ||||
|     { | ||||
|         $this->collection = $collection; | ||||
|         if ($this->collection->count() > 0) { | ||||
|             $this->collectIds(); | ||||
|             $this->collectInfo(); | ||||
|             $this->collectWebhookInfo(); | ||||
|             $this->appendCollectedInfo(); | ||||
|         } | ||||
| 
 | ||||
|         return $this->collection; | ||||
|     } | ||||
| 
 | ||||
|     public function enrichSingle(array|Model $model): array|Model | ||||
|     { | ||||
|         Log::debug(__METHOD__); | ||||
|         $collection = new Collection([$model]); | ||||
|         $collection = $this->enrich($collection); | ||||
| 
 | ||||
|         return $collection->first(); | ||||
|     } | ||||
| 
 | ||||
|     public function setUser(User $user): void | ||||
|     { | ||||
|         $this->user = $user; | ||||
|     } | ||||
| 
 | ||||
|     public function setUserGroup(UserGroup $userGroup): void | ||||
|     { | ||||
|         $this->userGroup = $userGroup; | ||||
|     } | ||||
| 
 | ||||
|     private function collectIds(): void | ||||
|     { | ||||
|         /** @var Webhook $webhook */ | ||||
|         foreach ($this->collection as $webhook) { | ||||
|             $this->ids[] = $webhook->id; | ||||
|         } | ||||
|         $this->ids = array_unique($this->ids); | ||||
|     } | ||||
| 
 | ||||
|     private function collectInfo(): void | ||||
|     { | ||||
|         $all = WebhookDelivery::get(); | ||||
| 
 | ||||
|         /** @var WebhookDelivery $item */ | ||||
|         foreach ($all as $item) { | ||||
|             $this->deliveries[$item->id] = $item->key; | ||||
|         } | ||||
|         $all = WebhookResponse::get(); | ||||
| 
 | ||||
|         /** @var WebhookResponse $item */ | ||||
|         foreach ($all as $item) { | ||||
|             $this->responses[$item->id] = $item->key; | ||||
|         } | ||||
|         $all = WebhookTrigger::get(); | ||||
| 
 | ||||
|         /** @var WebhookTrigger $item */ | ||||
|         foreach ($all as $item) { | ||||
|             $this->triggers[$item->id] = $item->key; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private function collectWebhookInfo(): void | ||||
|     { | ||||
|         $set = DB::table('webhook_webhook_delivery')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_delivery_id']); | ||||
| 
 | ||||
|         /** @var stdClass $item */ | ||||
|         foreach ($set as $item) { | ||||
|             $id                             = $item->webhook_id; | ||||
|             $deliveryId                     = $item->webhook_delivery_id; | ||||
|             $this->webhookDeliveries[$id][] = WebhookDeliveryEnum::from($this->deliveries[$deliveryId])->name; | ||||
|         } | ||||
| 
 | ||||
|         $set = DB::table('webhook_webhook_response')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_response_id']); | ||||
| 
 | ||||
|         /** @var stdClass $item */ | ||||
|         foreach ($set as $item) { | ||||
|             $id                            = $item->webhook_id; | ||||
|             $responseId                    = $item->webhook_response_id; | ||||
|             $this->webhookResponses[$id][] = WebhookResponseEnum::from($this->responses[$responseId])->name; | ||||
|         } | ||||
| 
 | ||||
|         $set = DB::table('webhook_webhook_trigger')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_trigger_id']); | ||||
| 
 | ||||
|         /** @var stdClass $item */ | ||||
|         foreach ($set as $item) { | ||||
|             $id                           = $item->webhook_id; | ||||
|             $triggerId                    = $item->webhook_trigger_id; | ||||
|             $this->webhookTriggers[$id][] = WebhookTriggerEnum::from($this->triggers[$triggerId])->name; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function appendCollectedInfo(): void | ||||
|     { | ||||
|         $this->collection = $this->collection->map(function (Webhook $item) { | ||||
|             $meta       = [ | ||||
|                 'deliveries' => $this->webhookDeliveries[$item->id] ?? [], | ||||
|                 'responses'  => $this->webhookResponses[$item->id] ?? [], | ||||
|                 'triggers'   => $this->webhookTriggers[$item->id] ?? [], | ||||
|             ]; | ||||
|             $item->meta = $meta; | ||||
| 
 | ||||
|             return $item; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -1,32 +1,9 @@ | ||||
| <?php | ||||
| 
 | ||||
| /* | ||||
|  * BudgetLimitHandler.php | ||||
|  * Copyright (c) 2023 james@firefly-iii.org | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Handlers\Events\Model; | ||||
| namespace FireflyIII\Support\Observers; | ||||
| 
 | ||||
| use FireflyIII\Events\Model\BudgetLimit\Created; | ||||
| use FireflyIII\Events\Model\BudgetLimit\Deleted; | ||||
| use FireflyIII\Events\Model\BudgetLimit\Updated; | ||||
| use FireflyIII\Models\AvailableBudget; | ||||
| use FireflyIII\Models\Budget; | ||||
| use FireflyIII\Models\BudgetLimit; | ||||
| @@ -39,17 +16,8 @@ use Spatie\Period\Boundaries; | ||||
| use Spatie\Period\Period; | ||||
| use Spatie\Period\Precision; | ||||
| 
 | ||||
| /** | ||||
|  * Class BudgetLimitHandler | ||||
|  */ | ||||
| class BudgetLimitHandler | ||||
| trait RecalculatesAvailableBudgetsTrait | ||||
| { | ||||
|     public function created(Created $event): void | ||||
|     { | ||||
|         Log::debug(sprintf('BudgetLimitHandler::created(#%s)', $event->budgetLimit->id)); | ||||
|         $this->updateAvailableBudget($event->budgetLimit); | ||||
|     } | ||||
| 
 | ||||
|     private function updateAvailableBudget(BudgetLimit $budgetLimit): void | ||||
|     { | ||||
|         Log::debug(sprintf('Now in updateAvailableBudget(limit #%d)', $budgetLimit->id)); | ||||
| @@ -241,18 +209,4 @@ class BudgetLimitHandler | ||||
| 
 | ||||
|         return $amount; | ||||
|     } | ||||
| 
 | ||||
|     public function deleted(Deleted $event): void | ||||
|     { | ||||
|         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 | ||||
|     { | ||||
|         Log::debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id)); | ||||
|         $this->updateAvailableBudget($event->budgetLimit); | ||||
|     } | ||||
| } | ||||
| @@ -255,7 +255,7 @@ trait ConvertsDataTypes | ||||
|         if (10 === strlen((string) $value)) { | ||||
|             // probably a date format.
 | ||||
|             try { | ||||
|                 $carbon = Carbon::createFromFormat('Y-m-d', $value); | ||||
|                 $carbon = Carbon::createFromFormat('Y-m-d', $value, config('app.timezone')); | ||||
|             } catch (InvalidDateException $e) { // @phpstan-ignore-line
 | ||||
|                 Log::error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage())); | ||||
| 
 | ||||
| @@ -276,7 +276,7 @@ trait ConvertsDataTypes | ||||
| 
 | ||||
|         // is an atom string, I hope?
 | ||||
|         try { | ||||
|             $carbon = Carbon::parse($value); | ||||
|             $carbon = Carbon::parse($value, $value, config('app.timezone')); | ||||
|         } catch (InvalidDateException $e) { // @phpstan-ignore-line
 | ||||
|             Log::error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage())); | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										74
									
								
								app/Support/Request/ValidatesWebhooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								app/Support/Request/ValidatesWebhooks.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| namespace FireflyIII\Support\Request; | ||||
| 
 | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| use FireflyIII\Models\Webhook; | ||||
| use Illuminate\Contracts\Validation\Validator; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| trait ValidatesWebhooks | ||||
| { | ||||
|     public function withValidator(Validator $validator): void | ||||
|     { | ||||
|         $validator->after( | ||||
|             function (Validator $validator): void { | ||||
|                 Log::debug('Validating webhook'); | ||||
|                 if ($validator->failed()) { | ||||
|                     return; | ||||
|                 } | ||||
|                 $data           = $validator->getData(); | ||||
|                 $triggers       = $data['triggers'] ?? []; | ||||
|                 $responses      = $data['responses'] ?? []; | ||||
| 
 | ||||
|                 if (0 === count($triggers) || 0 === count($responses)) { | ||||
|                     Log::debug('No trigger or response, return.'); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $validTriggers  = array_values(Webhook::getTriggers()); | ||||
|                 $validResponses = array_values(Webhook::getResponses()); | ||||
|                 $containsAny    = false; | ||||
|                 $count          = 0; | ||||
|                 foreach ($triggers as $trigger) { | ||||
|                     if (!in_array($trigger, $validTriggers, true)) { | ||||
|                         return; | ||||
|                     } | ||||
|                     ++$count; | ||||
|                     if ($trigger === WebhookTrigger::ANY->name) { | ||||
|                         $containsAny = true; | ||||
|                     } | ||||
|                 } | ||||
|                 if ($containsAny && $count > 1) { | ||||
|                     $validator->errors()->add('triggers.0', trans('validation.only_any_trigger')); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 foreach ($responses as $response) { | ||||
|                     if (!in_array($response, $validResponses, true)) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 // some combinations are illegal.
 | ||||
|                 foreach ($triggers as $i => $trigger) { | ||||
|                     $forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger)); | ||||
|                     if (null === $forbidden) { | ||||
|                         $validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger])); | ||||
| 
 | ||||
|                         continue; | ||||
|                     } | ||||
|                     foreach ($responses as $ii => $response) { | ||||
|                         if (in_array($response, $forbidden, true)) { | ||||
|                             Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response)); | ||||
|                             $validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response])); | ||||
| 
 | ||||
|                             return; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| @@ -224,6 +224,7 @@ class Steam | ||||
|          */ | ||||
|         $request              = clone $start; | ||||
|         $request->subDay()->endOfDay(); | ||||
|         Log::debug('Get first balance to start.'); | ||||
|         Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String())); | ||||
|         $startBalance         = $this->finalAccountBalance($account, $request); | ||||
|         $primaryCurrency      = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup); | ||||
| @@ -315,7 +316,7 @@ class Steam | ||||
|             Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance); | ||||
|         } | ||||
|         $cache->store($balances); | ||||
|         Log::debug('End of method'); | ||||
|         Log::debug('End of method finalAccountBalanceInRange'); | ||||
| 
 | ||||
|         return $balances; | ||||
|     } | ||||
| @@ -356,8 +357,10 @@ class Steam | ||||
|                 continue; | ||||
|             } | ||||
|             $accountSum           = array_values($accountSum)[0]; | ||||
|             $sumOfAmount          = (string)$accountSum['sum_of_amount']; | ||||
|             $sumOfAmount          = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount); | ||||
|             $sumsByCode           = [ | ||||
|                 $accountSum['code'] => $accountSum['sum_of_amount'], | ||||
|                 $accountSum['code'] => $sumOfAmount, | ||||
|             ]; | ||||
| 
 | ||||
|             // Log::debug('All balances are (joined)', $others);
 | ||||
|   | ||||
| @@ -51,9 +51,12 @@ class WebhookTransformer extends AbstractTransformer | ||||
|             'active'     => $webhook->active, | ||||
|             'title'      => $webhook->title, | ||||
|             'secret'     => $webhook->secret, | ||||
|             'trigger'    => $this->getEnum('trigger', $webhook->trigger), | ||||
|             'response'   => $this->getEnum('response', $webhook->response), | ||||
|             'delivery'   => $this->getEnum('delivery', $webhook->delivery), | ||||
|             'triggers'   => $webhook->meta['triggers'], | ||||
|             'deliveries' => $webhook->meta['deliveries'], | ||||
|             'responses'  => $webhook->meta['responses'], | ||||
|             //            'trigger'    => $this->getEnum('trigger', $webhook->trigger),
 | ||||
|             //            'response'   => $this->getEnum('response', $webhook->response),
 | ||||
|             //            'delivery'   => $this->getEnum('delivery', $webhook->delivery),
 | ||||
|             'url'        => $webhook->url, | ||||
|             'links'      => [ | ||||
|                 [ | ||||
|   | ||||
							
								
								
									
										51
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -3,7 +3,56 @@ | ||||
| All notable changes to this project will be documented in this file. | ||||
| This project adheres to [Semantic Versioning](http://semver.org/). | ||||
| 
 | ||||
| ## 6.3.0 - 2025-08-xx | ||||
| ## 6.4.0 - 2025-09-01 | ||||
| 
 | ||||
| ### Added | ||||
| 
 | ||||
| - [Issue 5532](https://github.com/firefly-iii/firefly-iii/issues/5532) (Asset prices and exchange rates) reported by @svozniuk | ||||
| - [Discussion 10725](https://github.com/orgs/firefly-iii/discussions/10725) (New webhook triggers) started by @Billos. See the [documentation](https://docs.firefly-iii.org/how-to/firefly-iii/features/webhooks/). | ||||
| 
 | ||||
| ### Changed | ||||
| 
 | ||||
| - Initial release. | ||||
| 
 | ||||
| ### Fixed | ||||
| 
 | ||||
| - [Issue 10790](https://github.com/firefly-iii/firefly-iii/issues/10790) (Undefined variable $occurrences) reported by @senna1992 | ||||
| - [Issue 10791](https://github.com/firefly-iii/firefly-iii/issues/10791) (Clone and edit a transaction with a different currency doesn't clear the foreign transaction amount) reported by @jxtxzzw | ||||
| - [Issue 10794](https://github.com/firefly-iii/firefly-iii/issues/10794) (Error with recurring transaction) reported by @MaximSN | ||||
| - [Issue 10799](https://github.com/firefly-iii/firefly-iii/issues/10799) (Budget - "Left (per day)" not showing the correct value) reported by @GensHaze | ||||
| - [Issue 10802](https://github.com/firefly-iii/firefly-iii/issues/10802) (Crash when trying to update a budget limit) reported by @Billos | ||||
| - [Issue 10803](https://github.com/firefly-iii/firefly-iii/issues/10803) (Issue in /v1/budget-limits spent attribute) reported by @Billos | ||||
| - [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro | ||||
| 
 | ||||
| ### API | ||||
| 
 | ||||
| - [Issue 8345](https://github.com/firefly-iii/firefly-iii/issues/8345) (API: Distinguish spent & earned at `/v2/chart/category/dashboard` (or future `v2/categories`)) reported by @dreautall | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ## 6.3.2 - 2025-08-20 | ||||
| 
 | ||||
| ### Fixed | ||||
| 
 | ||||
| - [Discussion 10768](https://github.com/orgs/firefly-iii/discussions/10768) (Argument #1 ($start) must be of type Carbon\Carbon, null given) started by @tangodance | ||||
| - [Issue 10771](https://github.com/firefly-iii/firefly-iii/issues/10771) (/v1/budgets/{id}/limits seems broken) reported by @Sceptorrh | ||||
| - [Issue 10773](https://github.com/firefly-iii/firefly-iii/issues/10773) (API: Wrong Return types) reported by @dreautall | ||||
| - [Issue 10775](https://github.com/firefly-iii/firefly-iii/issues/10775) (API: /v1/chart/account/overview broken) reported by @dreautall | ||||
| - [Issue 10782](https://github.com/firefly-iii/firefly-iii/issues/10782) ([error'] /accounts/[asset,expense,revenue]) reported by @vkanev | ||||
| 
 | ||||
| ## 6.3.1 - 2025-08-19 | ||||
| 
 | ||||
| ### Fixed  | ||||
| 
 | ||||
| - [Discussion 10768](https://github.com/orgs/firefly-iii/discussions/10768) (Argument #1 ($start) must be of type Carbon\Carbon, null given) started by @tangodance | ||||
| - [Issue 10771](https://github.com/firefly-iii/firefly-iii/issues/10771) (/v1/budgets/{id}/limits seems broken) reported by @Sceptorrh | ||||
| - [Issue 10773](https://github.com/firefly-iii/firefly-iii/issues/10773) (API: Wrong Return types) reported by @dreautall | ||||
| - [Issue 10775](https://github.com/firefly-iii/firefly-iii/issues/10775) (API: /v1/chart/account/overview broken) reported by @dreautall | ||||
| - [Issue 10782](https://github.com/firefly-iii/firefly-iii/issues/10782) ([error'] /accounts/[asset,expense,revenue]) reported by @vkanev | ||||
| 
 | ||||
| ## 6.3.0 - 2025-08-17 | ||||
| 
 | ||||
| > [!WARNING] | ||||
| > Firefly III v6.3.0 introduces a lot of API changes that deal with multi-currency support. Make sure your beloved apps are updated to support this. | ||||
|   | ||||
							
								
								
									
										218
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										218
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							| @@ -1370,16 +1370,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "guzzlehttp/promises", | ||||
|             "version": "2.2.0", | ||||
|             "version": "2.3.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/guzzle/promises.git", | ||||
|                 "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" | ||||
|                 "reference": "481557b130ef3790cf82b713667b43030dc9c957" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", | ||||
|                 "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", | ||||
|                 "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", | ||||
|                 "reference": "481557b130ef3790cf82b713667b43030dc9c957", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -1387,7 +1387,7 @@ | ||||
|             }, | ||||
|             "require-dev": { | ||||
|                 "bamarni/composer-bin-plugin": "^1.8.2", | ||||
|                 "phpunit/phpunit": "^8.5.39 || ^9.6.20" | ||||
|                 "phpunit/phpunit": "^8.5.44 || ^9.6.25" | ||||
|             }, | ||||
|             "type": "library", | ||||
|             "extra": { | ||||
| @@ -1433,7 +1433,7 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/guzzle/promises/issues", | ||||
|                 "source": "https://github.com/guzzle/promises/tree/2.2.0" | ||||
|                 "source": "https://github.com/guzzle/promises/tree/2.3.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -1449,7 +1449,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2025-03-27T13:27:01+00:00" | ||||
|             "time": "2025-08-22T14:34:08+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "guzzlehttp/psr7", | ||||
| @@ -1569,16 +1569,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "guzzlehttp/uri-template", | ||||
|             "version": "v1.0.4", | ||||
|             "version": "v1.0.5", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/guzzle/uri-template.git", | ||||
|                 "reference": "30e286560c137526eccd4ce21b2de477ab0676d2" | ||||
|                 "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/guzzle/uri-template/zipball/30e286560c137526eccd4ce21b2de477ab0676d2", | ||||
|                 "reference": "30e286560c137526eccd4ce21b2de477ab0676d2", | ||||
|                 "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", | ||||
|                 "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -1587,7 +1587,7 @@ | ||||
|             }, | ||||
|             "require-dev": { | ||||
|                 "bamarni/composer-bin-plugin": "^1.8.2", | ||||
|                 "phpunit/phpunit": "^8.5.36 || ^9.6.15", | ||||
|                 "phpunit/phpunit": "^8.5.44 || ^9.6.25", | ||||
|                 "uri-template/tests": "1.0.0" | ||||
|             }, | ||||
|             "type": "library", | ||||
| @@ -1635,7 +1635,7 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/guzzle/uri-template/issues", | ||||
|                 "source": "https://github.com/guzzle/uri-template/tree/v1.0.4" | ||||
|                 "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -1651,7 +1651,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2025-02-03T10:55:03+00:00" | ||||
|             "time": "2025-08-22T14:27:06+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "jc5/google2fa-laravel", | ||||
| @@ -1878,16 +1878,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "laravel/framework", | ||||
|             "version": "v12.24.0", | ||||
|             "version": "v12.25.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/laravel/framework.git", | ||||
|                 "reference": "6dcf2c46da23d159f35d6246234953a74b740d83" | ||||
|                 "reference": "2ee2ba94ae60efd24c7a787cbb1a2f82f714bb20" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/laravel/framework/zipball/6dcf2c46da23d159f35d6246234953a74b740d83", | ||||
|                 "reference": "6dcf2c46da23d159f35d6246234953a74b740d83", | ||||
|                 "url": "https://api.github.com/repos/laravel/framework/zipball/2ee2ba94ae60efd24c7a787cbb1a2f82f714bb20", | ||||
|                 "reference": "2ee2ba94ae60efd24c7a787cbb1a2f82f714bb20", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -2091,7 +2091,7 @@ | ||||
|                 "issues": "https://github.com/laravel/framework/issues", | ||||
|                 "source": "https://github.com/laravel/framework" | ||||
|             }, | ||||
|             "time": "2025-08-13T20:30:36+00:00" | ||||
|             "time": "2025-08-18T22:20:52+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "laravel/passport", | ||||
| @@ -4734,16 +4734,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "phpoption/phpoption", | ||||
|             "version": "1.9.3", | ||||
|             "version": "1.9.4", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/schmittjoh/php-option.git", | ||||
|                 "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" | ||||
|                 "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", | ||||
|                 "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", | ||||
|                 "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", | ||||
|                 "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -4751,7 +4751,7 @@ | ||||
|             }, | ||||
|             "require-dev": { | ||||
|                 "bamarni/composer-bin-plugin": "^1.8.2", | ||||
|                 "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" | ||||
|                 "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" | ||||
|             }, | ||||
|             "type": "library", | ||||
|             "extra": { | ||||
| @@ -4793,7 +4793,7 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/schmittjoh/php-option/issues", | ||||
|                 "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" | ||||
|                 "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -4805,7 +4805,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-07-20T21:41:07+00:00" | ||||
|             "time": "2025-08-21T11:53:16+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "phpseclib/phpseclib", | ||||
| @@ -7956,7 +7956,7 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-ctype", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-ctype.git", | ||||
| @@ -8015,7 +8015,7 @@ | ||||
|                 "portable" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -8026,6 +8026,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
| @@ -8035,16 +8039,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-intl-grapheme", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-intl-grapheme.git", | ||||
|                 "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" | ||||
|                 "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", | ||||
|                 "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", | ||||
|                 "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", | ||||
|                 "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -8093,7 +8097,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -8104,16 +8108,20 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-09-09T11:45:10+00:00" | ||||
|             "time": "2025-06-27T09:58:17+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-intl-idn", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-intl-idn.git", | ||||
| @@ -8176,7 +8184,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -8187,6 +8195,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
| @@ -8196,7 +8208,7 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-intl-normalizer", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-intl-normalizer.git", | ||||
| @@ -8257,7 +8269,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -8268,6 +8280,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
| @@ -8277,7 +8293,7 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-mbstring", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-mbstring.git", | ||||
| @@ -8338,7 +8354,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -8349,6 +8365,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
| @@ -8358,7 +8378,7 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-php80", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-php80.git", | ||||
| @@ -8418,7 +8438,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -8429,6 +8449,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
| @@ -8438,16 +8462,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-php83", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-php83.git", | ||||
|                 "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" | ||||
|                 "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", | ||||
|                 "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", | ||||
|                 "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", | ||||
|                 "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -8494,7 +8518,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -8505,25 +8529,29 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2024-09-09T11:45:10+00:00" | ||||
|             "time": "2025-07-08T02:45:35+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-php84", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-php84.git", | ||||
|                 "reference": "000df7860439609837bbe28670b0be15783b7fbf" | ||||
|                 "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", | ||||
|                 "reference": "000df7860439609837bbe28670b0be15783b7fbf", | ||||
|                 "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", | ||||
|                 "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -8570,7 +8598,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -8581,25 +8609,29 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2025-02-20T12:04:08+00:00" | ||||
|             "time": "2025-06-24T13:30:11+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-php85", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-php85.git", | ||||
|                 "reference": "6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd" | ||||
|                 "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd", | ||||
|                 "reference": "6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd", | ||||
|                 "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", | ||||
|                 "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -8646,7 +8678,7 @@ | ||||
|                 "shim" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-php85/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -8657,16 +8689,20 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2025-05-02T08:40:52+00:00" | ||||
|             "time": "2025-06-23T16:12:55+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "symfony/polyfill-uuid", | ||||
|             "version": "v1.32.0", | ||||
|             "version": "v1.33.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/symfony/polyfill-uuid.git", | ||||
| @@ -8725,7 +8761,7 @@ | ||||
|                 "uuid" | ||||
|             ], | ||||
|             "support": { | ||||
|                 "source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0" | ||||
|                 "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -8736,6 +8772,10 @@ | ||||
|                     "url": "https://github.com/fabpot", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://github.com/nicolas-grekas", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | ||||
|                     "type": "tidelift" | ||||
| @@ -10348,16 +10388,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "composer/class-map-generator", | ||||
|             "version": "1.6.1", | ||||
|             "version": "1.6.2", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/composer/class-map-generator.git", | ||||
|                 "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34" | ||||
|                 "reference": "ba9f089655d4cdd64e762a6044f411ccdaec0076" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/composer/class-map-generator/zipball/134b705ddb0025d397d8318a75825fe3c9d1da34", | ||||
|                 "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34", | ||||
|                 "url": "https://api.github.com/repos/composer/class-map-generator/zipball/ba9f089655d4cdd64e762a6044f411ccdaec0076", | ||||
|                 "reference": "ba9f089655d4cdd64e762a6044f411ccdaec0076", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -10401,7 +10441,7 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/composer/class-map-generator/issues", | ||||
|                 "source": "https://github.com/composer/class-map-generator/tree/1.6.1" | ||||
|                 "source": "https://github.com/composer/class-map-generator/tree/1.6.2" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -10411,13 +10451,9 @@ | ||||
|                 { | ||||
|                     "url": "https://github.com/composer", | ||||
|                     "type": "github" | ||||
|                 }, | ||||
|                 { | ||||
|                     "url": "https://tidelift.com/funding/github/packagist/composer/composer", | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2025-03-24T13:50:44+00:00" | ||||
|             "time": "2025-08-20T18:52:43+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "composer/pcre", | ||||
| @@ -10500,16 +10536,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "driftingly/rector-laravel", | ||||
|             "version": "2.0.6", | ||||
|             "version": "2.0.7", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/driftingly/rector-laravel.git", | ||||
|                 "reference": "5be95811801fc06126dd844beaeb6a41721ba3d3" | ||||
|                 "reference": "625dc02cee08d47ecf0ac86de2f02a55026cf34e" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/5be95811801fc06126dd844beaeb6a41721ba3d3", | ||||
|                 "reference": "5be95811801fc06126dd844beaeb6a41721ba3d3", | ||||
|                 "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/625dc02cee08d47ecf0ac86de2f02a55026cf34e", | ||||
|                 "reference": "625dc02cee08d47ecf0ac86de2f02a55026cf34e", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -10529,9 +10565,9 @@ | ||||
|             "description": "Rector upgrades rules for Laravel Framework", | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/driftingly/rector-laravel/issues", | ||||
|                 "source": "https://github.com/driftingly/rector-laravel/tree/2.0.6" | ||||
|                 "source": "https://github.com/driftingly/rector-laravel/tree/2.0.7" | ||||
|             }, | ||||
|             "time": "2025-08-08T22:10:01+00:00" | ||||
|             "time": "2025-08-19T20:49:47+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "fakerphp/faker", | ||||
| @@ -11771,16 +11807,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "phpunit/phpunit", | ||||
|             "version": "12.3.5", | ||||
|             "version": "12.3.6", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/sebastianbergmann/phpunit.git", | ||||
|                 "reference": "f10ba5f12a256026ad3c7ee4894ffe47f60d7dc7" | ||||
|                 "reference": "a2cab3224f687150ac2f3cc13d99b64ba1e1d088" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f10ba5f12a256026ad3c7ee4894ffe47f60d7dc7", | ||||
|                 "reference": "f10ba5f12a256026ad3c7ee4894ffe47f60d7dc7", | ||||
|                 "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2cab3224f687150ac2f3cc13d99b64ba1e1d088", | ||||
|                 "reference": "a2cab3224f687150ac2f3cc13d99b64ba1e1d088", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -11800,7 +11836,7 @@ | ||||
|                 "phpunit/php-text-template": "^5.0.0", | ||||
|                 "phpunit/php-timer": "^8.0.0", | ||||
|                 "sebastian/cli-parser": "^4.0.0", | ||||
|                 "sebastian/comparator": "^7.1.2", | ||||
|                 "sebastian/comparator": "^7.1.3", | ||||
|                 "sebastian/diff": "^7.0.0", | ||||
|                 "sebastian/environment": "^8.0.3", | ||||
|                 "sebastian/exporter": "^7.0.0", | ||||
| @@ -11848,7 +11884,7 @@ | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/sebastianbergmann/phpunit/issues", | ||||
|                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy", | ||||
|                 "source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.5" | ||||
|                 "source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.6" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -11872,7 +11908,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2025-08-16T05:20:09+00:00" | ||||
|             "time": "2025-08-20T14:43:23+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "rector/rector", | ||||
| @@ -11993,16 +12029,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "sebastian/comparator", | ||||
|             "version": "7.1.2", | ||||
|             "version": "7.1.3", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/sebastianbergmann/comparator.git", | ||||
|                 "reference": "1a7c2bce03a13a457ed3c975dfd331b3b4b133aa" | ||||
|                 "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1a7c2bce03a13a457ed3c975dfd331b3b4b133aa", | ||||
|                 "reference": "1a7c2bce03a13a457ed3c975dfd331b3b4b133aa", | ||||
|                 "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148", | ||||
|                 "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @@ -12061,7 +12097,7 @@ | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/sebastianbergmann/comparator/issues", | ||||
|                 "security": "https://github.com/sebastianbergmann/comparator/security/policy", | ||||
|                 "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.2" | ||||
|                 "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3" | ||||
|             }, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -12081,7 +12117,7 @@ | ||||
|                     "type": "tidelift" | ||||
|                 } | ||||
|             ], | ||||
|             "time": "2025-08-10T08:50:08+00:00" | ||||
|             "time": "2025-08-20T11:27:00+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "sebastian/complexity", | ||||
|   | ||||
| @@ -78,8 +78,8 @@ return [ | ||||
|         'running_balance_column' => env('USE_RUNNING_BALANCE', false), | ||||
|         // see cer.php for exchange rates feature flag.
 | ||||
|     ], | ||||
|     'version'                      => 'develop/2025-08-18', | ||||
|     'build_time'                   => 1755509249, | ||||
|     'version'                      => 'develop/2025-08-23', | ||||
|     'build_time'                   => 1755940389, | ||||
|     'api_version'                  => '2.1.0', // field is no longer used.
 | ||||
|     'db_version'                   => 26, | ||||
| 
 | ||||
|   | ||||
| @@ -248,6 +248,7 @@ return [ | ||||
|                 'multi_account_warning_deposit', | ||||
|                 'multi_account_warning_transfer', | ||||
| 
 | ||||
|                 'webhook_trigger_ANY', | ||||
|                 'webhook_trigger_STORE_TRANSACTION', | ||||
|                 'webhook_trigger_UPDATE_TRANSACTION', | ||||
|                 'webhook_trigger_DESTROY_TRANSACTION', | ||||
| @@ -258,6 +259,7 @@ return [ | ||||
|                 'webhook_trigger_STORE_UPDATE_BUDGET_LIMIT', | ||||
| 
 | ||||
|                 'webhook_response_TRANSACTIONS', | ||||
|                 'webhook_response_RELEVANT', | ||||
|                 'webhook_response_ACCOUNTS', | ||||
|                 'webhook_response_NONE', | ||||
|                 'webhook_delivery_JSON', | ||||
|   | ||||
							
								
								
									
										86
									
								
								config/webhooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								config/webhooks.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| <?php | ||||
| 
 | ||||
| declare(strict_types=1); | ||||
| 
 | ||||
| // this is hard coded, which is unfortunate.
 | ||||
| 
 | ||||
| use FireflyIII\Enums\WebhookResponse; | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| 
 | ||||
| return [ | ||||
|     'force_relevant_response' => [ | ||||
|         WebhookTrigger::STORE_TRANSACTION->name         => [ | ||||
|             WebhookTrigger::STORE_BUDGET->name, | ||||
|             WebhookTrigger::UPDATE_BUDGET->name, | ||||
|             WebhookTrigger::DESTROY_BUDGET->name, | ||||
|             WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name, | ||||
| 
 | ||||
|         ], | ||||
|         WebhookTrigger::UPDATE_TRANSACTION->name        => [ | ||||
|             WebhookTrigger::STORE_BUDGET->name, | ||||
|             WebhookTrigger::UPDATE_BUDGET->name, | ||||
|             WebhookTrigger::DESTROY_BUDGET->name, | ||||
|             WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name, | ||||
|         ], | ||||
|         WebhookTrigger::DESTROY_TRANSACTION->name       => [ | ||||
|             WebhookTrigger::STORE_BUDGET->name, | ||||
|             WebhookTrigger::UPDATE_BUDGET->name, | ||||
|             WebhookTrigger::DESTROY_BUDGET->name, | ||||
|             WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name, | ||||
|         ], | ||||
|         WebhookTrigger::STORE_BUDGET->name              => [ | ||||
|             WebhookTrigger::STORE_TRANSACTION->name, | ||||
|             WebhookTrigger::UPDATE_TRANSACTION->name, | ||||
|             WebhookTrigger::DESTROY_TRANSACTION->name, | ||||
| 
 | ||||
|         ], | ||||
|         WebhookTrigger::UPDATE_BUDGET->name             => [ | ||||
|             WebhookTrigger::STORE_TRANSACTION->name, | ||||
|             WebhookTrigger::UPDATE_TRANSACTION->name, | ||||
|             WebhookTrigger::DESTROY_TRANSACTION->name, | ||||
|         ], | ||||
|         WebhookTrigger::DESTROY_BUDGET->name            => [ | ||||
|             WebhookTrigger::STORE_TRANSACTION->name, | ||||
|             WebhookTrigger::UPDATE_TRANSACTION->name, | ||||
|             WebhookTrigger::DESTROY_TRANSACTION->name, | ||||
|         ], | ||||
|         WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name => [ | ||||
|             WebhookTrigger::STORE_TRANSACTION->name, | ||||
|             WebhookTrigger::UPDATE_TRANSACTION->name, | ||||
|             WebhookTrigger::DESTROY_TRANSACTION->name, | ||||
|         ], | ||||
|     ], | ||||
|     'forbidden_responses'     => [ | ||||
|         WebhookTrigger::ANY->name                       => [ | ||||
|             WebhookResponse::BUDGET->name, | ||||
|             WebhookResponse::TRANSACTIONS->name, | ||||
|             WebhookResponse::ACCOUNTS->name, | ||||
|         ], | ||||
|         WebhookTrigger::STORE_TRANSACTION->name         => [ | ||||
|             WebhookResponse::BUDGET->name, | ||||
|         ], | ||||
|         WebhookTrigger::UPDATE_TRANSACTION->name        => [ | ||||
|             WebhookResponse::BUDGET->name, | ||||
|         ], | ||||
|         WebhookTrigger::DESTROY_TRANSACTION->name       => [ | ||||
|             WebhookResponse::BUDGET->name, | ||||
|         ], | ||||
|         WebhookTrigger::STORE_BUDGET->name              => [ | ||||
|             WebhookResponse::TRANSACTIONS->name, | ||||
|             WebhookResponse::ACCOUNTS->name, | ||||
| 
 | ||||
|         ], | ||||
|         WebhookTrigger::UPDATE_BUDGET->name             => [ | ||||
|             WebhookResponse::TRANSACTIONS->name, | ||||
|             WebhookResponse::ACCOUNTS->name, | ||||
|         ], | ||||
|         WebhookTrigger::DESTROY_BUDGET->name            => [ | ||||
|             WebhookResponse::TRANSACTIONS->name, | ||||
|             WebhookResponse::ACCOUNTS->name, | ||||
|         ], | ||||
|         WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT->name => [ | ||||
|             WebhookResponse::TRANSACTIONS->name, | ||||
|             WebhookResponse::ACCOUNTS->name, | ||||
|         ], | ||||
|     ], | ||||
| ]; | ||||
| @@ -0,0 +1,127 @@ | ||||
| <?php | ||||
| 
 | ||||
| use Illuminate\Database\Migrations\Migration; | ||||
| use Illuminate\Database\QueryException; | ||||
| use Illuminate\Database\Schema\Blueprint; | ||||
| use Illuminate\Support\Facades\Schema; | ||||
| 
 | ||||
| return new class extends Migration { | ||||
|     private const TABLE_ALREADY_EXISTS = 'If this table exists already (see the error message), this is not a problem. Other errors? Please open a discussion on GitHub.'; | ||||
|     private const TABLE_ERROR          = 'Could not create table "%s": %s'; | ||||
| 
 | ||||
|     /** | ||||
|      * Run the migrations. | ||||
|      */ | ||||
|     public function up(): void | ||||
|     { | ||||
|         if (!Schema::hasTable('webhook_triggers')) { | ||||
|             Schema::create('webhook_triggers', function (Blueprint $table) { | ||||
|                 $table->id(); | ||||
|                 $table->timestamps(); | ||||
|                 $table->smallInteger('key')->unsigned(); | ||||
|                 $table->string('title', 100); | ||||
|                 $table->unique(['key', 'title']); | ||||
|             }); | ||||
|         } | ||||
|         if (!Schema::hasTable('webhook_responses')) { | ||||
|             Schema::create('webhook_responses', function (Blueprint $table) { | ||||
|                 $table->id(); | ||||
|                 $table->timestamps(); | ||||
|                 $table->smallInteger('key')->unsigned(); | ||||
|                 $table->string('title', 100); | ||||
|                 $table->unique(['key', 'title']); | ||||
|             }); | ||||
|         } | ||||
|         if (!Schema::hasTable('webhook_deliveries')) { | ||||
|             Schema::create('webhook_deliveries', function (Blueprint $table) { | ||||
|                 $table->id(); | ||||
|                 $table->timestamps(); | ||||
|                 $table->smallInteger('key')->unsigned(); | ||||
|                 $table->string('title', 100); | ||||
|                 $table->unique(['key', 'title']); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         // webhook_webhook_trigger
 | ||||
|         if (!Schema::hasTable('webhook_webhook_trigger')) { | ||||
|             try { | ||||
|                 Schema::create( | ||||
|                     'webhook_webhook_trigger', | ||||
|                     static function (Blueprint $table): void { | ||||
|                         $table->increments('id'); | ||||
|                         $table->integer('webhook_id', false, true); | ||||
|                         $table->bigInteger('webhook_trigger_id', false, true); | ||||
|                         $table->foreign('webhook_id')->references('id')->on('webhooks')->onDelete('cascade'); | ||||
|                         $table->foreign('webhook_trigger_id','link_to_trigger')->references('id')->on('webhook_triggers')->onDelete('cascade'); | ||||
| 
 | ||||
|                         // unique combi:
 | ||||
|                         $table->unique(['webhook_id', 'webhook_trigger_id']); | ||||
|                     } | ||||
|                 ); | ||||
|             } catch (QueryException $e) { | ||||
|                 app('log')->error(sprintf(self::TABLE_ERROR, 'webhook_webhook_trigger', $e->getMessage())); | ||||
|                 app('log')->error(self::TABLE_ALREADY_EXISTS); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         // webhook_webhook_response
 | ||||
|         if (!Schema::hasTable('webhook_webhook_response')) { | ||||
|             try { | ||||
|                 Schema::create( | ||||
|                     'webhook_webhook_response', | ||||
|                     static function (Blueprint $table): void { | ||||
|                         $table->increments('id'); | ||||
|                         $table->integer('webhook_id', false, true); | ||||
|                         $table->bigInteger('webhook_response_id', false, true); | ||||
|                         $table->foreign('webhook_id')->references('id')->on('webhooks')->onDelete('cascade'); | ||||
|                         $table->foreign('webhook_response_id','link_to_response')->references('id')->on('webhook_responses')->onDelete('cascade'); | ||||
| 
 | ||||
|                         // unique combi:
 | ||||
|                         $table->unique(['webhook_id', 'webhook_response_id']); | ||||
|                     } | ||||
|                 ); | ||||
|             } catch (QueryException $e) { | ||||
|                 app('log')->error(sprintf(self::TABLE_ERROR, 'webhook_webhook_response', $e->getMessage())); | ||||
|                 app('log')->error(self::TABLE_ALREADY_EXISTS); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // webhook_webhook_delivery
 | ||||
|         if (!Schema::hasTable('webhook_webhook_delivery')) { | ||||
|             try { | ||||
|                 Schema::create( | ||||
|                     'webhook_webhook_delivery', | ||||
|                     static function (Blueprint $table): void { | ||||
|                         $table->increments('id'); | ||||
|                         $table->integer('webhook_id', false, true); | ||||
|                         $table->bigInteger('webhook_delivery_id', false, true); | ||||
|                         $table->foreign('webhook_id')->references('id')->on('webhooks')->onDelete('cascade'); | ||||
|                         $table->foreign('webhook_delivery_id','link_to_delivery')->references('id')->on('webhook_deliveries')->onDelete('cascade'); | ||||
| 
 | ||||
|                         // unique combi:
 | ||||
|                         $table->unique(['webhook_id', 'webhook_delivery_id']); | ||||
|                     } | ||||
|                 ); | ||||
|             } catch (QueryException $e) { | ||||
|                 app('log')->error(sprintf(self::TABLE_ERROR, 'webhook_webhook_delivery', $e->getMessage())); | ||||
|                 app('log')->error(self::TABLE_ALREADY_EXISTS); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Reverse the migrations. | ||||
|      */ | ||||
|     public function down(): void | ||||
|     { | ||||
|         Schema::dropIfExists('webhook_webhook_delivery'); | ||||
|         Schema::dropIfExists('webhook_webhook_trigger'); | ||||
|         Schema::dropIfExists('webhook_webhook_response'); | ||||
| 
 | ||||
|         Schema::dropIfExists('webhook_triggers'); | ||||
|         Schema::dropIfExists('webhook_responses'); | ||||
|         Schema::dropIfExists('webhook_deliveries'); | ||||
|     } | ||||
| }; | ||||
| @@ -35,25 +35,10 @@ class AccountTypeSeeder extends Seeder | ||||
| { | ||||
|     public function run(): void | ||||
|     { | ||||
|         $types = [ | ||||
|             AccountTypeEnum::DEFAULT->value, | ||||
|             AccountTypeEnum::CASH->value, | ||||
|             AccountTypeEnum::ASSET->value, | ||||
|             AccountTypeEnum::EXPENSE->value, | ||||
|             AccountTypeEnum::REVENUE->value, | ||||
|             AccountTypeEnum::INITIAL_BALANCE->value, | ||||
|             AccountTypeEnum::BENEFICIARY->value, | ||||
|             AccountTypeEnum::IMPORT->value, | ||||
|             AccountTypeEnum::LOAN->value, | ||||
|             AccountTypeEnum::RECONCILIATION->value, | ||||
|             AccountTypeEnum::DEBT->value, | ||||
|             AccountTypeEnum::MORTGAGE->value, | ||||
|             AccountTypeEnum::LIABILITY_CREDIT->value, | ||||
|         ]; | ||||
|         foreach ($types as $type) { | ||||
|             if (null === AccountType::where('type', $type)->first()) { | ||||
|         foreach(AccountTypeEnum::cases() as $type) { | ||||
|             if (null === AccountType::where('type', $type->value)->first()) { | ||||
|                 try { | ||||
|                     AccountType::create(['type' => $type]); | ||||
|                     AccountType::create(['type' => $type->value]); | ||||
|                 } catch (PDOException $e) { | ||||
|                     // @ignoreException
 | ||||
|                 } | ||||
|   | ||||
| @@ -43,5 +43,6 @@ class DatabaseSeeder extends Seeder | ||||
|         $this->call(ConfigSeeder::class); | ||||
|         $this->call(UserRoleSeeder::class); | ||||
|         $this->call(ExchangeRateSeeder::class); | ||||
|         $this->call(WebhookDataSeeder::class); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -35,24 +35,17 @@ class TransactionTypeSeeder extends Seeder | ||||
| { | ||||
|     public function run(): void | ||||
|     { | ||||
|         $types = [ | ||||
|             TransactionTypeEnum::WITHDRAWAL->value, | ||||
|             TransactionTypeEnum::DEPOSIT->value, | ||||
|             TransactionTypeEnum::TRANSFER->value, | ||||
|             TransactionTypeEnum::OPENING_BALANCE->value, | ||||
|             TransactionTypeEnum::RECONCILIATION->value, | ||||
|             TransactionTypeEnum::INVALID->value, | ||||
|             TransactionTypeEnum::LIABILITY_CREDIT->value, | ||||
|         ]; | ||||
| 
 | ||||
|         foreach ($types as $type) { | ||||
|             if (null === TransactionType::where('type', $type)->first()) { | ||||
|         /** @var TransactionTypeEnum $type */ | ||||
|         foreach (TransactionTypeEnum::cases() as $type) { | ||||
|             if (null === TransactionType::where('type', $type->value)->first()) { | ||||
|                 try { | ||||
|                     TransactionType::create(['type' => $type]); | ||||
|                     TransactionType::create(['type' => $type->value]); | ||||
|                 } catch (PDOException $e) { | ||||
|                     // @ignoreException
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -39,16 +39,11 @@ class UserRoleSeeder extends Seeder | ||||
|      */ | ||||
|     public function run(): void | ||||
|     { | ||||
|         $roles = []; | ||||
|         /** @var UserRoleEnum $role */ | ||||
|         foreach (UserRoleEnum::cases() as $role) { | ||||
|             $roles[] = $role->value; | ||||
|         } | ||||
| 
 | ||||
|         /** @var string $role */ | ||||
|         foreach ($roles as $role) { | ||||
|             if (null === UserRole::where('title', $role)->first()) { | ||||
|             if (null === UserRole::where('title', $role->value)->first()) { | ||||
|                 try { | ||||
|                     UserRole::create(['title' => $role]); | ||||
|                     UserRole::create(['title' => $role->value]); | ||||
|                 } catch (PDOException $e) { | ||||
|                     // @ignoreException
 | ||||
|                 } | ||||
|   | ||||
							
								
								
									
										48
									
								
								database/seeders/WebhookDataSeeder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								database/seeders/WebhookDataSeeder.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Database\Seeders; | ||||
| 
 | ||||
| use FireflyIII\Enums\WebhookTrigger; | ||||
| use FireflyIII\Enums\WebhookResponse; | ||||
| use FireflyIII\Enums\WebhookDelivery; | ||||
| use FireflyIII\Models\WebhookTrigger as WebhookTriggerModel; | ||||
| use FireflyIII\Models\WebhookResponse as WebhookResponseModel; | ||||
| use FireflyIII\Models\WebhookDelivery as WebhookDeliveryModel; | ||||
| use Illuminate\Database\Seeder; | ||||
| 
 | ||||
| class WebhookDataSeeder extends Seeder | ||||
| { | ||||
|     /** | ||||
|      * Run the database seeds. | ||||
|      */ | ||||
|     public function run(): void | ||||
|     { | ||||
|         foreach (WebhookTrigger::cases() as $trigger) { | ||||
|             if (null === WebhookTriggerModel::where('key', $trigger->value)->where('title', $trigger->name)->first()) { | ||||
|                 try { | ||||
|                     WebhookTriggerModel::create(['key' => $trigger->value, 'title' => $trigger->name]); | ||||
|                 } catch (\PDOException $e) { | ||||
|                     // @ignoreException
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         foreach (WebhookResponse::cases() as $response) { | ||||
|             if (null === WebhookResponseModel::where('key', $response->value)->where('title', $response->name)->first()) { | ||||
|                 try { | ||||
|                     WebhookResponseModel::create(['key' => $response->value, 'title' => $response->name]); | ||||
|                 } catch (\PDOException $e) { | ||||
|                     // @ignoreException
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         foreach (WebhookDelivery::cases() as $delivery) { | ||||
|             if (null === WebhookDeliveryModel::where('key', $delivery->value)->where('title', $delivery->name)->first()) { | ||||
|                 try { | ||||
|                     WebhookDeliveryModel::create(['key' => $delivery->value, 'title' => $delivery->name]); | ||||
|                 } catch (\PDOException $e) { | ||||
|                     // @ignoreException
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										292
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										292
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -2592,9 +2592,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-android-arm-eabi": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.3.tgz", | ||||
|             "integrity": "sha512-UmTdvXnLlqQNOCJnyksjPs1G4GqXNGW1LrzCe8+8QoaLhhDeTXYBgJ3k6x61WIhlHX2U+VzEJ55TtIjR/HTySA==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.48.0.tgz", | ||||
|             "integrity": "sha512-aVzKH922ogVAWkKiyKXorjYymz2084zrhrZRXtLrA5eEx5SO8Dj0c/4FpCHZyn7MKzhW2pW4tK28vVr+5oQ2xw==", | ||||
|             "cpu": [ | ||||
|                 "arm" | ||||
|             ], | ||||
| @@ -2606,9 +2606,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-android-arm64": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.3.tgz", | ||||
|             "integrity": "sha512-8NoxqLpXm7VyeI0ocidh335D6OKT0UJ6fHdnIxf3+6oOerZZc+O7r+UhvROji6OspyPm+rrIdb1gTXtVIqn+Sg==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.48.0.tgz", | ||||
|             "integrity": "sha512-diOdQuw43xTa1RddAFbhIA8toirSzFMcnIg8kvlzRbK26xqEnKJ/vqQnghTAajy2Dcy42v+GMPMo6jq67od+Dw==", | ||||
|             "cpu": [ | ||||
|                 "arm64" | ||||
|             ], | ||||
| @@ -2620,9 +2620,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-darwin-arm64": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.3.tgz", | ||||
|             "integrity": "sha512-csnNavqZVs1+7/hUKtgjMECsNG2cdB8F7XBHP6FfQjqhjF8rzMzb3SLyy/1BG7YSfQ+bG75Ph7DyedbUqwq1rA==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.48.0.tgz", | ||||
|             "integrity": "sha512-QhR2KA18fPlJWFefySJPDYZELaVqIUVnYgAOdtJ+B/uH96CFg2l1TQpX19XpUMWUqMyIiyY45wje8K6F4w4/CA==", | ||||
|             "cpu": [ | ||||
|                 "arm64" | ||||
|             ], | ||||
| @@ -2634,9 +2634,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-darwin-x64": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.3.tgz", | ||||
|             "integrity": "sha512-r2MXNjbuYabSIX5yQqnT8SGSQ26XQc8fmp6UhlYJd95PZJkQD1u82fWP7HqvGUf33IsOC6qsiV+vcuD4SDP6iw==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.48.0.tgz", | ||||
|             "integrity": "sha512-Q9RMXnQVJ5S1SYpNSTwXDpoQLgJ/fbInWOyjbCnnqTElEyeNvLAB3QvG5xmMQMhFN74bB5ZZJYkKaFPcOG8sGg==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
| @@ -2648,9 +2648,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-freebsd-arm64": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.3.tgz", | ||||
|             "integrity": "sha512-uluObTmgPJDuJh9xqxyr7MV61Imq+0IvVsAlWyvxAaBSNzCcmZlhfYcRhCdMaCsy46ccZa7vtDDripgs9Jkqsw==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.48.0.tgz", | ||||
|             "integrity": "sha512-3jzOhHWM8O8PSfyft+ghXZfBkZawQA0PUGtadKYxFqpcYlOYjTi06WsnYBsbMHLawr+4uWirLlbhcYLHDXR16w==", | ||||
|             "cpu": [ | ||||
|                 "arm64" | ||||
|             ], | ||||
| @@ -2662,9 +2662,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-freebsd-x64": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.3.tgz", | ||||
|             "integrity": "sha512-AVJXEq9RVHQnejdbFvh1eWEoobohUYN3nqJIPI4mNTMpsyYN01VvcAClxflyk2HIxvLpRcRggpX1m9hkXkpC/A==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.48.0.tgz", | ||||
|             "integrity": "sha512-NcD5uVUmE73C/TPJqf78hInZmiSBsDpz3iD5MF/BuB+qzm4ooF2S1HfeTChj5K4AV3y19FFPgxonsxiEpy8v/A==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
| @@ -2676,9 +2676,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-arm-gnueabihf": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.3.tgz", | ||||
|             "integrity": "sha512-byyflM+huiwHlKi7VHLAYTKr67X199+V+mt1iRgJenAI594vcmGGddWlu6eHujmcdl6TqSNnvqaXJqZdnEWRGA==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.48.0.tgz", | ||||
|             "integrity": "sha512-JWnrj8qZgLWRNHr7NbpdnrQ8kcg09EBBq8jVOjmtlB3c8C6IrynAJSMhMVGME4YfTJzIkJqvSUSVJRqkDnu/aA==", | ||||
|             "cpu": [ | ||||
|                 "arm" | ||||
|             ], | ||||
| @@ -2690,9 +2690,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-arm-musleabihf": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.3.tgz", | ||||
|             "integrity": "sha512-aLm3NMIjr4Y9LklrH5cu7yybBqoVCdr4Nvnm8WB7PKCn34fMCGypVNpGK0JQWdPAzR/FnoEoFtlRqZbBBLhVoQ==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.48.0.tgz", | ||||
|             "integrity": "sha512-9xu92F0TxuMH0tD6tG3+GtngwdgSf8Bnz+YcsPG91/r5Vgh5LNofO48jV55priA95p3c92FLmPM7CvsVlnSbGQ==", | ||||
|             "cpu": [ | ||||
|                 "arm" | ||||
|             ], | ||||
| @@ -2704,9 +2704,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-arm64-gnu": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.3.tgz", | ||||
|             "integrity": "sha512-VtilE6eznJRDIoFOzaagQodUksTEfLIsvXymS+UdJiSXrPW7Ai+WG4uapAc3F7Hgs791TwdGh4xyOzbuzIZrnw==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.48.0.tgz", | ||||
|             "integrity": "sha512-NLtvJB5YpWn7jlp1rJiY0s+G1Z1IVmkDuiywiqUhh96MIraC0n7XQc2SZ1CZz14shqkM+XN2UrfIo7JB6UufOA==", | ||||
|             "cpu": [ | ||||
|                 "arm64" | ||||
|             ], | ||||
| @@ -2718,9 +2718,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-arm64-musl": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.3.tgz", | ||||
|             "integrity": "sha512-dG3JuS6+cRAL0GQ925Vppafi0qwZnkHdPeuZIxIPXqkCLP02l7ka+OCyBoDEv8S+nKHxfjvjW4OZ7hTdHkx8/w==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.48.0.tgz", | ||||
|             "integrity": "sha512-QJ4hCOnz2SXgCh+HmpvZkM+0NSGcZACyYS8DGbWn2PbmA0e5xUk4bIP8eqJyNXLtyB4gZ3/XyvKtQ1IFH671vQ==", | ||||
|             "cpu": [ | ||||
|                 "arm64" | ||||
|             ], | ||||
| @@ -2732,9 +2732,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-loongarch64-gnu": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.3.tgz", | ||||
|             "integrity": "sha512-iU8DxnxEKJptf8Vcx4XvAUdpkZfaz0KWfRrnIRrOndL0SvzEte+MTM7nDH4A2Now4FvTZ01yFAgj6TX/mZl8hQ==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.48.0.tgz", | ||||
|             "integrity": "sha512-Pk0qlGJnhILdIC5zSKQnprFjrGmjfDM7TPZ0FKJxRkoo+kgMRAg4ps1VlTZf8u2vohSicLg7NP+cA5qE96PaFg==", | ||||
|             "cpu": [ | ||||
|                 "loong64" | ||||
|             ], | ||||
| @@ -2746,9 +2746,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-ppc64-gnu": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.3.tgz", | ||||
|             "integrity": "sha512-VrQZp9tkk0yozJoQvQcqlWiqaPnLM6uY1qPYXvukKePb0fqaiQtOdMJSxNFUZFsGw5oA5vvVokjHrx8a9Qsz2A==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.48.0.tgz", | ||||
|             "integrity": "sha512-/dNFc6rTpoOzgp5GKoYjT6uLo8okR/Chi2ECOmCZiS4oqh3mc95pThWma7Bgyk6/WTEvjDINpiBCuecPLOgBLQ==", | ||||
|             "cpu": [ | ||||
|                 "ppc64" | ||||
|             ], | ||||
| @@ -2760,9 +2760,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-riscv64-gnu": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.3.tgz", | ||||
|             "integrity": "sha512-uf2eucWSUb+M7b0poZ/08LsbcRgaDYL8NCGjUeFMwCWFwOuFcZ8D9ayPl25P3pl+D2FH45EbHdfyUesQ2Lt9wA==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.48.0.tgz", | ||||
|             "integrity": "sha512-YBwXsvsFI8CVA4ej+bJF2d9uAeIiSkqKSPQNn0Wyh4eMDY4wxuSp71BauPjQNCKK2tD2/ksJ7uhJ8X/PVY9bHQ==", | ||||
|             "cpu": [ | ||||
|                 "riscv64" | ||||
|             ], | ||||
| @@ -2774,9 +2774,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-riscv64-musl": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.3.tgz", | ||||
|             "integrity": "sha512-7tnUcDvN8DHm/9ra+/nF7lLzYHDeODKKKrh6JmZejbh1FnCNZS8zMkZY5J4sEipy2OW1d1Ncc4gNHUd0DLqkSg==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.48.0.tgz", | ||||
|             "integrity": "sha512-FI3Rr2aGAtl1aHzbkBIamsQyuauYtTF9SDUJ8n2wMXuuxwchC3QkumZa1TEXYIv/1AUp1a25Kwy6ONArvnyeVQ==", | ||||
|             "cpu": [ | ||||
|                 "riscv64" | ||||
|             ], | ||||
| @@ -2788,9 +2788,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-s390x-gnu": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.3.tgz", | ||||
|             "integrity": "sha512-MUpAOallJim8CsJK+4Lc9tQzlfPbHxWDrGXZm2z6biaadNpvh3a5ewcdat478W+tXDoUiHwErX/dOql7ETcLqg==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.48.0.tgz", | ||||
|             "integrity": "sha512-Dx7qH0/rvNNFmCcIRe1pyQ9/H0XO4v/f0SDoafwRYwc2J7bJZ5N4CHL/cdjamISZ5Cgnon6iazAVRFlxSoHQnQ==", | ||||
|             "cpu": [ | ||||
|                 "s390x" | ||||
|             ], | ||||
| @@ -2802,9 +2802,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-x64-gnu": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.3.tgz", | ||||
|             "integrity": "sha512-F42IgZI4JicE2vM2PWCe0N5mR5vR0gIdORPqhGQ32/u1S1v3kLtbZ0C/mi9FFk7C5T0PgdeyWEPajPjaUpyoKg==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.48.0.tgz", | ||||
|             "integrity": "sha512-GUdZKTeKBq9WmEBzvFYuC88yk26vT66lQV8D5+9TgkfbewhLaTHRNATyzpQwwbHIfJvDJ3N9WJ90wK/uR3cy3Q==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
| @@ -2816,9 +2816,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-linux-x64-musl": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.3.tgz", | ||||
|             "integrity": "sha512-oLc+JrwwvbimJUInzx56Q3ujL3Kkhxehg7O1gWAYzm8hImCd5ld1F2Gry5YDjR21MNb5WCKhC9hXgU7rRlyegQ==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.48.0.tgz", | ||||
|             "integrity": "sha512-ao58Adz/v14MWpQgYAb4a4h3fdw73DrDGtaiF7Opds5wNyEQwtO6M9dBh89nke0yoZzzaegq6J/EXs7eBebG8A==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
| @@ -2830,9 +2830,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-win32-arm64-msvc": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.3.tgz", | ||||
|             "integrity": "sha512-lOrQ+BVRstruD1fkWg9yjmumhowR0oLAAzavB7yFSaGltY8klttmZtCLvOXCmGE9mLIn8IBV/IFrQOWz5xbFPg==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.48.0.tgz", | ||||
|             "integrity": "sha512-kpFno46bHtjZVdRIOxqaGeiABiToo2J+st7Yce+aiAoo1H0xPi2keyQIP04n2JjDVuxBN6bSz9R6RdTK5hIppw==", | ||||
|             "cpu": [ | ||||
|                 "arm64" | ||||
|             ], | ||||
| @@ -2844,9 +2844,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-win32-ia32-msvc": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.3.tgz", | ||||
|             "integrity": "sha512-vvrVKPRS4GduGR7VMH8EylCBqsDcw6U+/0nPDuIjXQRbHJc6xOBj+frx8ksfZAh6+Fptw5wHrN7etlMmQnPQVg==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.48.0.tgz", | ||||
|             "integrity": "sha512-rFYrk4lLk9YUTIeihnQMiwMr6gDhGGSbWThPEDfBoU/HdAtOzPXeexKi7yU8jO+LWRKnmqPN9NviHQf6GDwBcQ==", | ||||
|             "cpu": [ | ||||
|                 "ia32" | ||||
|             ], | ||||
| @@ -2858,9 +2858,9 @@ | ||||
|             ] | ||||
|         }, | ||||
|         "node_modules/@rollup/rollup-win32-x64-msvc": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.3.tgz", | ||||
|             "integrity": "sha512-fi3cPxCnu3ZeM3EwKZPgXbWoGzm2XHgB/WShKI81uj8wG0+laobmqy5wbgEwzstlbLu4MyO8C19FyhhWseYKNQ==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.48.0.tgz", | ||||
|             "integrity": "sha512-sq0hHLTgdtwOPDB5SJOuaoHyiP1qSwg+71TQWk8iDS04bW1wIE0oQ6otPiRj2ZvLYNASLMaTp8QRGUVZ+5OL5A==", | ||||
|             "cpu": [ | ||||
|                 "x64" | ||||
|             ], | ||||
| @@ -3158,9 +3158,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@types/node-forge": { | ||||
|             "version": "1.3.13", | ||||
|             "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.13.tgz", | ||||
|             "integrity": "sha512-zePQJSW5QkwSHKRApqWCVKeKoSOt4xvEnLENZPjyvm9Ezdf/EyDeJM7jqLzOwjVICQQzvLZ63T55MKdJB5H6ww==", | ||||
|             "version": "1.3.14", | ||||
|             "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", | ||||
|             "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
| @@ -3256,42 +3256,42 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@vue/compiler-core": { | ||||
|             "version": "3.5.18", | ||||
|             "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.18.tgz", | ||||
|             "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", | ||||
|             "version": "3.5.19", | ||||
|             "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.19.tgz", | ||||
|             "integrity": "sha512-/afpyvlkrSNYbPo94Qu8GtIOWS+g5TRdOvs6XZNw6pWQQmj5pBgSZvEPOIZlqWq0YvoUhDDQaQ2TnzuJdOV4hA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@babel/parser": "^7.28.0", | ||||
|                 "@vue/shared": "3.5.18", | ||||
|                 "@babel/parser": "^7.28.3", | ||||
|                 "@vue/shared": "3.5.19", | ||||
|                 "entities": "^4.5.0", | ||||
|                 "estree-walker": "^2.0.2", | ||||
|                 "source-map-js": "^1.2.1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@vue/compiler-dom": { | ||||
|             "version": "3.5.18", | ||||
|             "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz", | ||||
|             "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", | ||||
|             "version": "3.5.19", | ||||
|             "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.19.tgz", | ||||
|             "integrity": "sha512-Drs6rPHQZx/pN9S6ml3Z3K/TWCIRPvzG2B/o5kFK9X0MNHt8/E+38tiRfojufrYBfA6FQUFB2qBBRXlcSXWtOA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@vue/compiler-core": "3.5.18", | ||||
|                 "@vue/shared": "3.5.18" | ||||
|                 "@vue/compiler-core": "3.5.19", | ||||
|                 "@vue/shared": "3.5.19" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@vue/compiler-sfc": { | ||||
|             "version": "3.5.18", | ||||
|             "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz", | ||||
|             "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==", | ||||
|             "version": "3.5.19", | ||||
|             "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.19.tgz", | ||||
|             "integrity": "sha512-YWCm1CYaJ+2RvNmhCwI7t3I3nU+hOrWGWMsn+Z/kmm1jy5iinnVtlmkiZwbLlbV1SRizX7vHsc0/bG5dj0zRTg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@babel/parser": "^7.28.0", | ||||
|                 "@vue/compiler-core": "3.5.18", | ||||
|                 "@vue/compiler-dom": "3.5.18", | ||||
|                 "@vue/compiler-ssr": "3.5.18", | ||||
|                 "@vue/shared": "3.5.18", | ||||
|                 "@babel/parser": "^7.28.3", | ||||
|                 "@vue/compiler-core": "3.5.19", | ||||
|                 "@vue/compiler-dom": "3.5.19", | ||||
|                 "@vue/compiler-ssr": "3.5.19", | ||||
|                 "@vue/shared": "3.5.19", | ||||
|                 "estree-walker": "^2.0.2", | ||||
|                 "magic-string": "^0.30.17", | ||||
|                 "postcss": "^8.5.6", | ||||
| @@ -3299,14 +3299,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@vue/compiler-ssr": { | ||||
|             "version": "3.5.18", | ||||
|             "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz", | ||||
|             "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==", | ||||
|             "version": "3.5.19", | ||||
|             "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.19.tgz", | ||||
|             "integrity": "sha512-/wx0VZtkWOPdiQLWPeQeqpHWR/LuNC7bHfSX7OayBTtUy8wur6vT6EQIX6Et86aED6J+y8tTw43qo2uoqGg5sw==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@vue/compiler-dom": "3.5.18", | ||||
|                 "@vue/shared": "3.5.18" | ||||
|                 "@vue/compiler-dom": "3.5.19", | ||||
|                 "@vue/shared": "3.5.19" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@vue/component-compiler-utils": { | ||||
| @@ -3388,9 +3388,9 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/@vue/shared": { | ||||
|             "version": "3.5.18", | ||||
|             "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.18.tgz", | ||||
|             "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==", | ||||
|             "version": "3.5.19", | ||||
|             "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.19.tgz", | ||||
|             "integrity": "sha512-IhXCOn08wgKrLQxRFKKlSacWg4Goi1BolrdEeLYn6tgHjJNXVrWJ5nzoxZqNwl5p88aLlQ8LOaoMa3AYvaKJ/Q==", | ||||
|             "dev": true, | ||||
|             "license": "MIT" | ||||
|         }, | ||||
| @@ -4326,9 +4326,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/browserslist": { | ||||
|             "version": "4.25.2", | ||||
|             "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", | ||||
|             "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", | ||||
|             "version": "4.25.3", | ||||
|             "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", | ||||
|             "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", | ||||
|             "dev": true, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -4346,8 +4346,8 @@ | ||||
|             ], | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "caniuse-lite": "^1.0.30001733", | ||||
|                 "electron-to-chromium": "^1.5.199", | ||||
|                 "caniuse-lite": "^1.0.30001735", | ||||
|                 "electron-to-chromium": "^1.5.204", | ||||
|                 "node-releases": "^2.0.19", | ||||
|                 "update-browserslist-db": "^1.1.3" | ||||
|             }, | ||||
| @@ -4486,9 +4486,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/caniuse-lite": { | ||||
|             "version": "1.0.30001735", | ||||
|             "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", | ||||
|             "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", | ||||
|             "version": "1.0.30001737", | ||||
|             "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", | ||||
|             "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", | ||||
|             "dev": true, | ||||
|             "funding": [ | ||||
|                 { | ||||
| @@ -4930,13 +4930,13 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/core-js-compat": { | ||||
|             "version": "3.45.0", | ||||
|             "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.0.tgz", | ||||
|             "integrity": "sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==", | ||||
|             "version": "3.45.1", | ||||
|             "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", | ||||
|             "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "browserslist": "^4.25.1" | ||||
|                 "browserslist": "^4.25.3" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
| @@ -5700,9 +5700,9 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/electron-to-chromium": { | ||||
|             "version": "1.5.203", | ||||
|             "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.203.tgz", | ||||
|             "integrity": "sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g==", | ||||
|             "version": "1.5.208", | ||||
|             "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz", | ||||
|             "integrity": "sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==", | ||||
|             "dev": true, | ||||
|             "license": "ISC" | ||||
|         }, | ||||
| @@ -7052,9 +7052,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/i18next": { | ||||
|             "version": "25.3.6", | ||||
|             "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.6.tgz", | ||||
|             "integrity": "sha512-dThZ0CTCM3sUG/qS0ZtQYZQcUI6DtBN8yBHK+SKEqihPcEYmjVWh/YJ4luic73Iq6Uxhp6q7LJJntRK5+1t7jQ==", | ||||
|             "version": "25.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.0.tgz", | ||||
|             "integrity": "sha512-UH5aiamXsO3cfrZFurCHiB6YSs3C+s+XY9UaJllMMSbmaoXILxFgqDEZu4NbfzJFjmUo3BNMa++Rjkr3ofjfLw==", | ||||
|             "funding": [ | ||||
|                 { | ||||
|                     "type": "individual", | ||||
| @@ -7958,13 +7958,13 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/magic-string": { | ||||
|             "version": "0.30.17", | ||||
|             "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", | ||||
|             "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", | ||||
|             "version": "0.30.18", | ||||
|             "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", | ||||
|             "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "@jridgewell/sourcemap-codec": "^1.5.0" | ||||
|                 "@jridgewell/sourcemap-codec": "^1.5.5" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/make-dir": { | ||||
| @@ -10123,9 +10123,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/rollup": { | ||||
|             "version": "4.46.3", | ||||
|             "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.3.tgz", | ||||
|             "integrity": "sha512-RZn2XTjXb8t5g13f5YclGoilU/kwT696DIkY3sywjdZidNSi3+vseaQov7D7BZXVJCPv3pDWUN69C78GGbXsKw==", | ||||
|             "version": "4.48.0", | ||||
|             "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.0.tgz", | ||||
|             "integrity": "sha512-BXHRqK1vyt9XVSEHZ9y7xdYtuYbwVod2mLwOMFP7t/Eqoc1pHRlG/WdV2qNeNvZHRQdLedaFycljaYYM96RqJQ==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
| @@ -10139,26 +10139,26 @@ | ||||
|                 "npm": ">=8.0.0" | ||||
|             }, | ||||
|             "optionalDependencies": { | ||||
|                 "@rollup/rollup-android-arm-eabi": "4.46.3", | ||||
|                 "@rollup/rollup-android-arm64": "4.46.3", | ||||
|                 "@rollup/rollup-darwin-arm64": "4.46.3", | ||||
|                 "@rollup/rollup-darwin-x64": "4.46.3", | ||||
|                 "@rollup/rollup-freebsd-arm64": "4.46.3", | ||||
|                 "@rollup/rollup-freebsd-x64": "4.46.3", | ||||
|                 "@rollup/rollup-linux-arm-gnueabihf": "4.46.3", | ||||
|                 "@rollup/rollup-linux-arm-musleabihf": "4.46.3", | ||||
|                 "@rollup/rollup-linux-arm64-gnu": "4.46.3", | ||||
|                 "@rollup/rollup-linux-arm64-musl": "4.46.3", | ||||
|                 "@rollup/rollup-linux-loongarch64-gnu": "4.46.3", | ||||
|                 "@rollup/rollup-linux-ppc64-gnu": "4.46.3", | ||||
|                 "@rollup/rollup-linux-riscv64-gnu": "4.46.3", | ||||
|                 "@rollup/rollup-linux-riscv64-musl": "4.46.3", | ||||
|                 "@rollup/rollup-linux-s390x-gnu": "4.46.3", | ||||
|                 "@rollup/rollup-linux-x64-gnu": "4.46.3", | ||||
|                 "@rollup/rollup-linux-x64-musl": "4.46.3", | ||||
|                 "@rollup/rollup-win32-arm64-msvc": "4.46.3", | ||||
|                 "@rollup/rollup-win32-ia32-msvc": "4.46.3", | ||||
|                 "@rollup/rollup-win32-x64-msvc": "4.46.3", | ||||
|                 "@rollup/rollup-android-arm-eabi": "4.48.0", | ||||
|                 "@rollup/rollup-android-arm64": "4.48.0", | ||||
|                 "@rollup/rollup-darwin-arm64": "4.48.0", | ||||
|                 "@rollup/rollup-darwin-x64": "4.48.0", | ||||
|                 "@rollup/rollup-freebsd-arm64": "4.48.0", | ||||
|                 "@rollup/rollup-freebsd-x64": "4.48.0", | ||||
|                 "@rollup/rollup-linux-arm-gnueabihf": "4.48.0", | ||||
|                 "@rollup/rollup-linux-arm-musleabihf": "4.48.0", | ||||
|                 "@rollup/rollup-linux-arm64-gnu": "4.48.0", | ||||
|                 "@rollup/rollup-linux-arm64-musl": "4.48.0", | ||||
|                 "@rollup/rollup-linux-loongarch64-gnu": "4.48.0", | ||||
|                 "@rollup/rollup-linux-ppc64-gnu": "4.48.0", | ||||
|                 "@rollup/rollup-linux-riscv64-gnu": "4.48.0", | ||||
|                 "@rollup/rollup-linux-riscv64-musl": "4.48.0", | ||||
|                 "@rollup/rollup-linux-s390x-gnu": "4.48.0", | ||||
|                 "@rollup/rollup-linux-x64-gnu": "4.48.0", | ||||
|                 "@rollup/rollup-linux-x64-musl": "4.48.0", | ||||
|                 "@rollup/rollup-win32-arm64-msvc": "4.48.0", | ||||
|                 "@rollup/rollup-win32-ia32-msvc": "4.48.0", | ||||
|                 "@rollup/rollup-win32-x64-msvc": "4.48.0", | ||||
|                 "fsevents": "~2.3.2" | ||||
|             } | ||||
|         }, | ||||
| @@ -11005,13 +11005,17 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/tapable": { | ||||
|             "version": "2.2.2", | ||||
|             "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", | ||||
|             "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", | ||||
|             "version": "2.2.3", | ||||
|             "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", | ||||
|             "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
|                 "node": ">=6" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
|                 "url": "https://opencollective.com/webpack" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/terser": { | ||||
| @@ -11527,14 +11531,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/vite": { | ||||
|             "version": "7.1.2", | ||||
|             "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", | ||||
|             "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", | ||||
|             "version": "7.1.3", | ||||
|             "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", | ||||
|             "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "esbuild": "^0.25.0", | ||||
|                 "fdir": "^6.4.6", | ||||
|                 "fdir": "^6.5.0", | ||||
|                 "picomatch": "^4.0.3", | ||||
|                 "postcss": "^8.5.6", | ||||
|                 "rollup": "^4.43.0", | ||||
| @@ -11850,9 +11854,9 @@ | ||||
|             "license": "BSD-2-Clause" | ||||
|         }, | ||||
|         "node_modules/webpack": { | ||||
|             "version": "5.101.2", | ||||
|             "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.2.tgz", | ||||
|             "integrity": "sha512-4JLXU0tD6OZNVqlwzm3HGEhAHufSiyv+skb7q0d2367VDMzrU1Q/ZeepvkcHH0rZie6uqEtTQQe0OEOOluH3Mg==", | ||||
|             "version": "5.101.3", | ||||
|             "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", | ||||
|             "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|   | ||||
| @@ -68,7 +68,7 @@ export default { | ||||
|       } | ||||
|     }, | ||||
|     value: { | ||||
|       type: Number, | ||||
|       type: String, | ||||
|       required: true, | ||||
|     } | ||||
|   }, | ||||
| @@ -84,7 +84,7 @@ export default { | ||||
|               } | ||||
|               this.deliveries.push( | ||||
|                   { | ||||
|                       id: response.data.data.value[key], | ||||
|                       id: key, | ||||
|                       name: this.$t('firefly.webhook_delivery_' + key), | ||||
|                   } | ||||
|               ); | ||||
|   | ||||
| @@ -66,7 +66,7 @@ export default { | ||||
|             } | ||||
|         }, | ||||
|         value: { | ||||
|             type: Number, | ||||
|             type: String, | ||||
|             required: true, | ||||
|         } | ||||
|     }, | ||||
| @@ -88,7 +88,7 @@ export default { | ||||
|                 } | ||||
|                 this.responses.push( | ||||
|                     { | ||||
|                         id: response.data.data.value[key], | ||||
|                         id: key, | ||||
|                         name: this.$t('firefly.webhook_response_' + key), | ||||
|                     } | ||||
|                 ); | ||||
|   | ||||
| @@ -29,6 +29,7 @@ | ||||
|             </div> | ||||
|             <select v-if="!loading" | ||||
|                     ref="trigger" | ||||
|                     multiple | ||||
|                     v-model="trigger" | ||||
|                     :title="$t('form.webhook_trigger')" | ||||
|                     class="form-control" | ||||
| @@ -52,7 +53,7 @@ export default { | ||||
|     name: "WebhookTrigger", | ||||
|     data() { | ||||
|         return { | ||||
|             trigger: 0, | ||||
|             trigger: [], | ||||
|             loading: true, | ||||
|             triggers: [], | ||||
|         }; | ||||
| @@ -66,7 +67,7 @@ export default { | ||||
|             } | ||||
|         }, | ||||
|         value: { | ||||
|             type: Number, | ||||
|             type: Array, | ||||
|             required: true, | ||||
|         } | ||||
|     }, | ||||
| @@ -80,11 +81,11 @@ export default { | ||||
|                 } | ||||
|                 this.triggers.push( | ||||
|                     { | ||||
|                         id: response.data.data.value[key], | ||||
|                         id: key, | ||||
|                         name: this.$t('firefly.webhook_trigger_' + key), | ||||
|                     } | ||||
|                 ); | ||||
|                 console.log('webhook trigger: id=' + response.data.data.value[key] + ', name=' + key); | ||||
|                 // console.log('webhook trigger: id=' + response.data.data.value[key] + ', name=' + key); | ||||
|             } | ||||
|             this.loading = false; | ||||
|         }).catch((error) => { | ||||
| @@ -93,6 +94,7 @@ export default { | ||||
|     }, | ||||
|     watch: { | ||||
|         value() { | ||||
|             console.log('Value changed to ' + this.value); | ||||
|             this.trigger = this.value; | ||||
|         }, | ||||
|         trigger(newValue) { | ||||
|   | ||||
| @@ -112,12 +112,10 @@ export default { | ||||
|                 amount: this.$refs.amount.value, | ||||
|                 currency_id: this.$refs.currency_select.value, | ||||
|             }; | ||||
|             // console.log(obj); | ||||
|             this.$emit('input', obj | ||||
|             ); | ||||
|             this.$emit('input', obj); | ||||
|         }, | ||||
|         changeData: function () { | ||||
|             // console.log('ForeignAmountSelect changeData'); | ||||
|             console.log('ForeignAmountSelect changeData'); | ||||
|             this.enabledCurrencies = []; | ||||
|             let destType = this.destination.type ? this.destination.type.toLowerCase() : 'invalid'; | ||||
|             let srcType = this.source.type ? this.source.type.toLowerCase() : 'invalid'; | ||||
| @@ -160,6 +158,7 @@ export default { | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 this.checkSelection(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
| @@ -172,6 +171,7 @@ export default { | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 this.checkSelection(); | ||||
|                 return; | ||||
|             } | ||||
|             for (const key in this.currencies) { | ||||
| @@ -180,6 +180,23 @@ export default { | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         checkSelection: function () { | ||||
|             let selectedCurrency = this.$refs.currency_select.value; | ||||
|             let hasSelected = false; | ||||
|             for(const key in this.enabledCurrencies) { | ||||
|                 if(parseInt(this.enabledCurrencies[key].id) === parseInt(selectedCurrency)) { | ||||
|                     hasSelected = true; | ||||
|                 } | ||||
|             } | ||||
|             if(false === hasSelected) { | ||||
|                 let obj = { | ||||
|                     amount: '', | ||||
|                     currency_id: null, | ||||
|                 }; | ||||
|                 this.$emit('input', obj); | ||||
|                 console.warn('Reset foreign amount.'); | ||||
|             } | ||||
|         }, | ||||
|         loadCurrencies: function () { | ||||
|             // console.log('loadCurrencies'); | ||||
|             // reset list of currencies: | ||||
|   | ||||
| @@ -54,12 +54,12 @@ | ||||
|             <div class="row"> | ||||
|               <div class="col-lg-12"> | ||||
|                 <Title :value=this.title :error="errors.title" v-on:input="title = $event"></Title> | ||||
|                 <WebhookTrigger :value=this.trigger :error="errors.trigger" | ||||
|                                 v-on:input="trigger = $event"></WebhookTrigger> | ||||
|                 <WebhookResponse :value=this.response :error="errors.response" | ||||
|                                  v-on:input="response = $event"></WebhookResponse> | ||||
|                 <WebhookDelivery :value=this.delivery :error="errors.delivery" | ||||
|                                  v-on:input="delivery = $event"></WebhookDelivery> | ||||
|                 <WebhookTrigger :value=this.triggers :error="errors.trigger" | ||||
|                                 v-on:input="triggers = $event"></WebhookTrigger> | ||||
|                 <WebhookResponse :value=this.responses :error="errors.response" | ||||
|                                  v-on:input="responses = $event"></WebhookResponse> | ||||
|                 <WebhookDelivery :value=this.deliveries :error="errors.delivery" | ||||
|                                  v-on:input="deliveries = $event"></WebhookDelivery> | ||||
|                 <URL :value=this.url :error="errors.url" v-on:input="url = $event"></URL> | ||||
|                 <Checkbox :value=this.active :error="errors.active" help="ACTIVE HELP TODO" :title="$t('form.active')" v-on:input="active = $event"></Checkbox> | ||||
|               </div> | ||||
| @@ -96,16 +96,16 @@ export default { | ||||
|       error_message: '', | ||||
|       success_message: '', | ||||
|       title: '', | ||||
|       trigger: 100, | ||||
|       response: 200, | ||||
|       delivery: 300, | ||||
|       triggers: ["STORE_TRANSACTION"], | ||||
|       responses: "RELEVANT", | ||||
|       deliveries: "JSON", | ||||
|       active: true, | ||||
|       url: '', | ||||
|       errors: { | ||||
|         title: [], | ||||
|         trigger: [], | ||||
|         response: [], | ||||
|         delivery: [], | ||||
|         triggers: [], | ||||
|         responses: [], | ||||
|         deliveries: [], | ||||
|         url: [], | ||||
|         active: [] | ||||
|       } | ||||
| @@ -118,9 +118,9 @@ export default { | ||||
|       this.success_message = ''; | ||||
|       this.errors = { | ||||
|         title: [], | ||||
|         trigger: [], | ||||
|         response: [], | ||||
|         delivery: [], | ||||
|         triggers: [], | ||||
|         responses: [], | ||||
|         deliveries: [], | ||||
|         url: [], | ||||
|         active: [], | ||||
|       }; | ||||
| @@ -131,9 +131,9 @@ export default { | ||||
|       // collect data | ||||
|       let data = { | ||||
|         title: this.title, | ||||
|         trigger: this.trigger, | ||||
|         response: this.response, | ||||
|         delivery: this.delivery, | ||||
|         triggers: this.triggers, | ||||
|         responses: [this.responses], | ||||
|         deliveries: [this.deliveries], | ||||
|         url: this.url, | ||||
|         active: this.active, | ||||
|       }; | ||||
| @@ -148,9 +148,9 @@ export default { | ||||
|         //console.log(error.response.data); | ||||
|         this.error_message = error.response.data.message; | ||||
|         this.errors.title = error.response.data.errors.title; | ||||
|         this.errors.trigger = error.response.data.errors.trigger; | ||||
|         this.errors.response = error.response.data.errors.response; | ||||
|         this.errors.delivery = error.response.data.errors.delivery; | ||||
|         this.errors.triggers = error.response.data.errors.triggers; | ||||
|         this.errors.responses = error.response.data.errors.responses; | ||||
|         this.errors.deliveries = error.response.data.errors.deliveries; | ||||
|         this.errors.url = error.response.data.errors.url; | ||||
|  | ||||
|         // enable button again | ||||
|   | ||||
| @@ -54,12 +54,12 @@ | ||||
|             <div class="row"> | ||||
|               <div class="col-lg-12"> | ||||
|                 <Title :value=this.title :error="errors.title" v-on:input="title = $event"></Title> | ||||
|                 <WebhookTrigger :value=this.trigger :error="errors.trigger" | ||||
|                                 v-on:input="trigger = $event"></WebhookTrigger> | ||||
|                 <WebhookResponse :value=this.response :error="errors.response" | ||||
|                                  v-on:input="response = $event"></WebhookResponse> | ||||
|                 <WebhookDelivery :value=this.delivery :error="errors.delivery" | ||||
|                                  v-on:input="delivery = $event"></WebhookDelivery> | ||||
|                 <WebhookTrigger :value=this.triggers :error="errors.triggers" | ||||
|                                 v-on:input="triggers = $event"></WebhookTrigger> | ||||
|                 <WebhookResponse :value=this.responses :error="errors.responses" | ||||
|                                  v-on:input="responses = $event"></WebhookResponse> | ||||
|                 <WebhookDelivery :value=this.deliveries :error="errors.deliveries" | ||||
|                                  v-on:input="deliveries = $event"></WebhookDelivery> | ||||
|                 <URL :value=this.url :error="errors.url" v-on:input="url = $event"></URL> | ||||
|                 <Checkbox :value=this.active :error="errors.active" help="ACTIVE HELP TODO" :title="$t('form.active')" | ||||
|                           v-on:input="active = $event"></Checkbox> | ||||
| @@ -97,17 +97,17 @@ export default { | ||||
|       error_message: '', | ||||
|       success_message: '', | ||||
|       title: '', | ||||
|       trigger: 100, | ||||
|       response: 200, | ||||
|       delivery: 300, | ||||
|       triggers: ["STORE_TRANSACTION"], | ||||
|       responses: "RELEVANT", | ||||
|       deliveries: "JSON", | ||||
|       id: 0, | ||||
|       active: false, | ||||
|       url: '', | ||||
|       errors: { | ||||
|         title: [], | ||||
|         trigger: [], | ||||
|         response: [], | ||||
|         delivery: [], | ||||
|         triggers: [], | ||||
|         responses: [], | ||||
|         deliveries: [], | ||||
|         url: [], | ||||
|         active: [] | ||||
|       } | ||||
| @@ -127,32 +127,9 @@ export default { | ||||
|         // console.log(response.data.data.attributes); | ||||
|         this.title = response.data.data.attributes.title; | ||||
|         this.id = parseInt(response.data.data.id); | ||||
|  | ||||
|         // trigger value on content | ||||
|         if ('STORE_TRANSACTION' === response.data.data.attributes.trigger) { | ||||
|           this.trigger = 100; | ||||
|         } | ||||
|         if ('UPDATE_TRANSACTION' === response.data.data.attributes.trigger) { | ||||
|           this.trigger = 110; | ||||
|         } | ||||
|         if ('DESTROY_TRANSACTION' === response.data.data.attributes.trigger) { | ||||
|           this.trigger = 120; | ||||
|         } | ||||
|  | ||||
|         // response value | ||||
|         if ('TRANSACTIONS' === response.data.data.attributes.response) { | ||||
|           this.response = 200; | ||||
|         } | ||||
|         if ('ACCOUNTS' === response.data.data.attributes.response) { | ||||
|           this.response = 210; | ||||
|         } | ||||
|         if ('NONE' === response.data.data.attributes.response) { | ||||
|           this.response = 220; | ||||
|         } | ||||
|         if ('JSON' === response.data.data.attributes.delivery) { | ||||
|           this.delivery = 300; | ||||
|         } | ||||
|  | ||||
|         this.triggers = response.data.data.attributes.triggers; | ||||
|         this.responses = response.data.data.attributes.responses[0]; | ||||
|         this.deliveries = response.data.data.attributes.deliveries[0]; | ||||
|         this.active = response.data.data.attributes.active; | ||||
|         this.url = response.data.data.attributes.url; | ||||
|       }).catch(error => { | ||||
| @@ -165,9 +142,9 @@ export default { | ||||
|       this.success_message = ''; | ||||
|       this.errors = { | ||||
|         title: [], | ||||
|         trigger: [], | ||||
|         response: [], | ||||
|         delivery: [], | ||||
|         triggers: [], | ||||
|         responses: [], | ||||
|         deliveries: [], | ||||
|         url: [], | ||||
|         active: [], | ||||
|       }; | ||||
| @@ -178,9 +155,9 @@ export default { | ||||
|       // collect data | ||||
|       let data = { | ||||
|         title: this.title, | ||||
|         trigger: this.trigger, | ||||
|         response: this.response, | ||||
|         delivery: this.delivery, | ||||
|         triggers: this.triggers, | ||||
|         responses: [this.responses], | ||||
|         deliveries: [this.deliveries], | ||||
|         url: this.url, | ||||
|         active: this.active, | ||||
|       }; | ||||
| @@ -193,9 +170,9 @@ export default { | ||||
|  | ||||
|         this.error_message = error.response.data.message; | ||||
|         this.errors.title = error.response.data.errors.title; | ||||
|         this.errors.trigger = error.response.data.errors.trigger; | ||||
|         this.errors.response = error.response.data.errors.response; | ||||
|         this.errors.delivery = error.response.data.errors.delivery; | ||||
|         this.errors.triggers = error.response.data.errors.trigger; | ||||
|         this.errors.responses = error.response.data.errors.response; | ||||
|         this.errors.deliveries = error.response.data.errors.deliveries; | ||||
|         this.errors.url = error.response.data.errors.url; | ||||
|  | ||||
|         // enable button again | ||||
|   | ||||
| @@ -50,12 +50,24 @@ | ||||
|                                 <a :href="'webhooks/show/' + webhook.id">{{ webhook.title }}</a> | ||||
|                             </td> | ||||
|                             <td> | ||||
|                                 <span v-if="webhook.active">{{ triggers[webhook.trigger] }}</span> | ||||
|                                 <span v-if="!webhook.active" class="text-muted"><s>{{ triggers[webhook.trigger] }}</s> ({{ | ||||
|                                         $t('firefly.inactive') | ||||
|                                     }})</span> | ||||
|                                 <span v-if="webhook.active"> | ||||
|                                     <ul class="list-unstyled"> | ||||
|                                         <li v-for="trigger in webhook.triggers" :key="trigger"> | ||||
|                                             {{ triggers[trigger] }} | ||||
|                                         </li> | ||||
|                                     </ul> | ||||
|                                 </span> | ||||
|                                 <span v-if="!webhook.active" class="text-muted"> | ||||
|                                     <ul class="list-unstyled"> | ||||
|                                         <li v-for="trigger in webhook.triggers" :key="trigger"> | ||||
|                                             <s>{{ triggers[trigger] }}</s> ({{$t('firefly.inactive') }}) | ||||
|                                         </li> | ||||
|                                     </ul> | ||||
|                                     </span> | ||||
|                             </td> | ||||
|                             <td> | ||||
|                               {{ responses[webhook.responses[0]] }} ({{ deliveries[webhook.deliveries[0]] }}) | ||||
|                             </td> | ||||
|                             <td>{{ responses[webhook.response] }} ({{ deliveries[webhook.delivery] }})</td> | ||||
|                             <td> | ||||
|                                 <em style="cursor:pointer" | ||||
|                                     v-if="webhook.show_secret" class="fa fa-eye" @click="toggleSecret(webhook)"></em> | ||||
| @@ -167,9 +179,9 @@ export default { | ||||
|                             active: current.attributes.active, | ||||
|                             full_url: current.attributes.url, | ||||
|                             secret: current.attributes.secret, | ||||
|                             trigger: current.attributes.trigger, | ||||
|                             response: current.attributes.response, | ||||
|                             delivery: current.attributes.delivery, | ||||
|                             triggers: current.attributes.triggers, | ||||
|                             responses: current.attributes.responses, | ||||
|                             deliveries: current.attributes.deliveries, | ||||
|                             show_secret: false, | ||||
|                         }; | ||||
|                         if (current.attributes.url.length > 20) { | ||||
|   | ||||
| @@ -40,27 +40,39 @@ | ||||
|             <table class="table table-hover" aria-label="A table"> | ||||
|               <tbody> | ||||
|               <tr> | ||||
|                 <th scope="row" style="width:40%;">Title</th> | ||||
|                 <td style="width:40%;"><strong>{{ $t('list.title') }}</strong></td> | ||||
|                 <td>{{ title }}</td> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                 <th scope="row">{{ $t('list.active') }}</th> | ||||
|                   <td style="width:40%;"><strong>{{ $t('list.active') }}</strong></td> | ||||
|                 <td> | ||||
|                   <em class="fa fa-check text-success" v-if="active"></em> | ||||
|                   <em class="fa fa-times text-danger" v-if="!active"></em> | ||||
|                 </td> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                 <th scope="row">{{ $t('list.trigger') }}</th> | ||||
|                 <td> {{ trigger }}</td> | ||||
|                   <td style="width:40%;"><strong>{{ $t('list.trigger') }}</strong></td> | ||||
|                 <td> | ||||
|                     <span v-for="trigger in triggers" :key="trigger"> | ||||
|                         {{ $t('firefly.webhook_trigger_' + trigger) }}<br> | ||||
|                     </span> | ||||
|                 </td> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                 <th scope="row">{{ $t('list.response') }}</th> | ||||
|                 <td> {{ response }}</td> | ||||
|                   <td style="width:40%;"><strong>{{ $t('list.response') }}</strong></td> | ||||
|                 <td> | ||||
|                         <span v-for="response in responses" :key="response"> | ||||
|                             {{ $t('firefly.webhook_response_' + response) }} | ||||
|                         </span> | ||||
|                 </td> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                 <th scope="row">{{ $t('list.delivery') }}</th> | ||||
|                 <td> {{ delivery }}</td> | ||||
|                   <td style="width:40%;"><strong>{{ $t('list.delivery') }}</strong></td> | ||||
|                 <td> | ||||
|                         <span v-for="delivery in deliveries" :key="delivery"> | ||||
|                             {{ $t('firefly.webhook_delivery_' + delivery) }} | ||||
|                         </span> | ||||
|                 </td> | ||||
|               </tr> | ||||
|               </tbody> | ||||
|             </table> | ||||
| @@ -86,12 +98,13 @@ | ||||
|             <table class="table table-hover" aria-label="A table"> | ||||
|               <tbody> | ||||
|               <tr> | ||||
|                 <th scope="row" style="width:40%;">{{ $t('list.url') }}</th> | ||||
|                   <td style="width:40%;"><strong>{{ $t('list.url') }}</strong></td> | ||||
|                 <td><input type="text" readonly class="form-control" :value=url></td> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                 <td> | ||||
|                   <td style="width:40%;"><strong> | ||||
|                   {{ $t('list.secret') }} | ||||
|                   </strong> | ||||
|                 </td> | ||||
|                 <td> | ||||
|                   <em style="cursor:pointer" | ||||
| @@ -260,11 +273,14 @@ export default { | ||||
|       secret: '', | ||||
|       show_secret: false, | ||||
|       trigger: '', | ||||
|     triggers: [], | ||||
|       loading: true, | ||||
|       response: '', | ||||
|     responses: [], | ||||
|       message_content: '', | ||||
|       message_attempts: [], | ||||
|       delivery: '', | ||||
|     deliveries: [], | ||||
|       messages: [], | ||||
|       active: false, | ||||
|       edit_url: '#', | ||||
| @@ -369,9 +385,9 @@ export default { | ||||
|         this.title = response.data.data.attributes.title; | ||||
|         this.url = response.data.data.attributes.url; | ||||
|         this.secret = response.data.data.attributes.secret; | ||||
|         this.trigger = this.$t('firefly.webhook_trigger_' + response.data.data.attributes.trigger); | ||||
|         this.response = this.$t('firefly.webhook_response_' + response.data.data.attributes.response); | ||||
|         this.delivery = this.$t('firefly.webhook_delivery_' + response.data.data.attributes.delivery); | ||||
|         this.triggers = response.data.data.attributes.triggers; | ||||
|         this.responses = response.data.data.attributes.responses; | ||||
|         this.deliveries = response.data.data.attributes.deliveries; | ||||
|  | ||||
|         this.active = response.data.data.attributes.active; | ||||
|         this.url = response.data.data.attributes.url; | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", | ||||
|         "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", | ||||
|         "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "After transaction creation", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Transaction details", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Account details", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "\u0636\u0639 \u0641\u064a \u0627\u0639\u062a\u0628\u0627\u0631\u0643 \u0623\u0646 \u062d\u0633\u0627\u0628 \u0627\u0644\u0645\u0635\u062f\u0631 \u0641\u064a \u0627\u0644\u062a\u0642\u0633\u064a\u0645\u0627\u062a \u0627\u0644\u0644\u0627\u062d\u0642\u0629 \u0633\u064a\u062a\u0645 \u062a\u062c\u0627\u0648\u0632\u0647 \u0628\u0645\u0627 \u0647\u0648 \u0645\u062d\u062f\u062f \u0641\u064a \u0623\u0648\u0644 \u062a\u0642\u0633\u064a\u0645 \u0644\u0644\u0633\u062d\u0628.", | ||||
|         "multi_account_warning_deposit": "\u0636\u0639 \u0641\u064a \u0627\u0639\u062a\u0628\u0627\u0631\u0643 \u0623\u0646 \u062d\u0633\u0627\u0628 \u0627\u0644\u0648\u062c\u0647\u0629 \u0641\u064a \u0627\u0644\u062a\u0642\u0633\u064a\u0645\u0627\u062a \u0627\u0644\u0644\u0627\u062d\u0642\u0629 \u0633\u064a\u062a\u0645 \u062a\u062c\u0627\u0648\u0632\u0647 \u0628\u0645\u0627 \u0647\u0648 \u0645\u062d\u062f\u062f \u0641\u064a \u0623\u0648\u0644 \u062a\u0642\u0633\u064a\u0645 \u0644\u0644\u0625\u064a\u062f\u0627\u0639.", | ||||
|         "multi_account_warning_transfer": "\u0636\u0639 \u0641\u064a \u0627\u0639\u062a\u0628\u0627\u0631\u0643 \u0623\u0646 \u062d\u0633\u0627\u0628 \u0627\u0644\u0645\u0635\u062f\u0631 + \u0627\u0644\u0648\u062c\u0647\u0629 \u0641\u064a \u0627\u0644\u062a\u0642\u0633\u064a\u0645\u0627\u062a \u0627\u0644\u0644\u0627\u062d\u0642\u0629 \u0633\u064a\u062a\u0645 \u062a\u062c\u0627\u0648\u0632\u0647\u0645\u0627 \u0628\u0645\u0627 \u0647\u0648 \u0645\u062d\u062f\u062f \u0641\u064a \u0623\u0648\u0644 \u062a\u0642\u0633\u064a\u0645 \u0644\u0644\u062a\u062d\u0648\u064a\u0644.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "\u0628\u0639\u062f \u0625\u0646\u0634\u0627\u0621 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "\u0628\u0639\u062f \u062a\u062d\u062f\u064a\u062b \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "\u0628\u0639\u062f \u062d\u0630\u0641 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "\u062a\u0641\u0627\u0635\u064a\u0644 \u0627\u0644\u0645\u0639\u0627\u0645\u0644\u0629", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "\u062a\u0641\u0627\u0635\u064a\u0644 \u0627\u0644\u062d\u0633\u0627\u0628", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "\u0418\u043c\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0434\u0432\u0438\u0434, \u0447\u0435 \u0440\u0430\u0437\u0445\u043e\u0434\u043d\u0430 \u0441\u043c\u0435\u0442\u043a\u0430 \u043d\u0430 \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u0438\u0442\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u043d\u0438\u044f \u0449\u0435 \u0431\u044a\u0434\u0435 \u0442\u0430\u0437\u0438 \u043a\u043e\u044f\u0442\u043e \u0435 \u0434\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0430 \u0432 \u043f\u044a\u0440\u0432\u0438\u044f \u0440\u0430\u0437\u0434\u0435\u043b \u043d\u0430 \u0442\u0435\u0433\u043b\u0435\u043d\u0435\u0442\u043e.", | ||||
|         "multi_account_warning_deposit": "\u0418\u043c\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0434\u0432\u0438\u0434, \u0447\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u043d\u0430\u0442\u0430 \u0441\u043c\u0435\u0442\u043a\u0430 \u043d\u0430 \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u0438\u0442\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u043d\u0438\u044f \u0449\u0435 \u0431\u044a\u0434\u0435 \u0442\u0430\u0437\u0438 \u043a\u043e\u044f\u0442\u043e \u0435 \u0434\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0430 \u0432 \u043f\u044a\u0440\u0432\u0438\u044f \u0440\u0430\u0437\u0434\u0435\u043b \u043d\u0430 \u0434\u0435\u043f\u043e\u0437\u0438\u0442\u0430.", | ||||
|         "multi_account_warning_transfer": "\u0418\u043c\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0434\u0432\u0438\u0434, \u0447\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u043d\u0430\u0442\u0430 + \u0440\u0430\u0437\u0445\u043e\u0434\u043d\u0430\u0442\u0430 \u0441\u043c\u0435\u0442\u043a\u0430 \u043d\u0430 \u0441\u043b\u0435\u0434\u0432\u0430\u0449\u0438\u0442\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u043d\u0438\u044f \u0449\u0435 \u0431\u044a\u0434\u0435 \u0442\u0430\u0437\u0438 \u043a\u043e\u044f\u0442\u043e \u0435 \u0434\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0430 \u0432 \u043f\u044a\u0440\u0432\u0438\u044f \u0440\u0430\u0437\u0434\u0435\u043b \u043d\u0430 \u043f\u0440\u0435\u0445\u0432\u044a\u0440\u043b\u044f\u043d\u0435\u0442\u043e.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "After transaction creation", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Transaction details", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Account details", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Tingues en compte que el compte d'origen de divisions posteriors ser\u00e0 anul\u00b7lat pel que es troba definit a la primera divisi\u00f3 de la retirada.", | ||||
|         "multi_account_warning_deposit": "Tingues en compte que el compte de dest\u00ed de divisions posteriors ser\u00e0 anul\u00b7lat pel que es troba definit a la primera divisi\u00f3 del dip\u00f2sit.", | ||||
|         "multi_account_warning_transfer": "Tingues en compte que el compte d'origen + dest\u00ed de divisions posteriors ser\u00e0 anul\u00b7lat pel que es troba definit a la primera divisi\u00f3 de la transfer\u00e8ncia.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "Despr\u00e9s de crear la transacci\u00f3", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "Despr\u00e9s d'actualitzar la transacci\u00f3", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "Despr\u00e9s d'eliminar la transacci\u00f3", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Detalls de la transacci\u00f3", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Detalls del compte", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Zdrojov\u00fd \u00fa\u010det cel\u00e9 transakce je ovl\u00e1dan\u00fd prvn\u00edm rozd\u011blen\u00edm.", | ||||
|         "multi_account_warning_deposit": "C\u00edlov\u00fd \u00fa\u010del v\u0161ech n\u00e1sleduj\u00edc\u00edch rozd\u011blen\u00ed je ovl\u00e1dan\u00fd c\u00edlov\u00fdm \u00fa\u010dtem prvn\u00edho rozd\u011blen\u00ed transakce.", | ||||
|         "multi_account_warning_transfer": "Zdrojov\u00fd i c\u00edlov\u00fd \u00fa\u010det v\u0161ech n\u00e1sleduj\u00edc\u00edch rozd\u011blen\u00ed jsou ovl\u00e1d\u00e1ny zdrojov\u00fdm a c\u00edlov\u00fdm \u00fa\u010dtem prvn\u00edho rozd\u011blen\u00ed transakce.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "Po vytvo\u0159en\u00ed transakce", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "Po aktualizaci transakce", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "Po odstran\u011bn\u00ed transakce", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Podrobnosti transakce", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Podrobnosti \u00fa\u010dtu", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Husk, at kildekontoen for efterf\u00f8lgende opdelinger vil blive overstyret af hvad der er defineret i den f\u00f8rste opdeling af tilbagetr\u00e6kningen.", | ||||
|         "multi_account_warning_deposit": "Husk, at destinationskontoen for efterf\u00f8lgende opdelinger vil blive tilsidesat af hvad der er defineret i den f\u00f8rste opsplitning af depositummet.", | ||||
|         "multi_account_warning_transfer": "Husk p\u00e5, at kilden + destination konto for efterf\u00f8lgende opdelinger vil blive overstyret af hvad der er defineret i den f\u00f8rste opdeling af overf\u00f8rslen.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "Efter oprettelse af transaktion", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "Efter opdatering af transaktion", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "Efter sletning af transaktion", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Transaktionsdetaljer", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Kontodetaljer", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Bedenken Sie, dass das Quellkonto nachfolgender Aufteilungen von dem, was in der ersten Aufteilung der Abhebung definiert ist, au\u00dfer Kraft gesetzt wird.", | ||||
|         "multi_account_warning_deposit": "Bedenken Sie, dass das Zielkonto nachfolgender Aufteilungen von dem, was in der ersten Aufteilung der Einnahmen definiert ist, au\u00dfer Kraft gesetzt wird.", | ||||
|         "multi_account_warning_transfer": "Bedenken Sie, dass das Quell- und Zielkonto nachfolgender Aufteilungen durch das, was in der ersten Aufteilung der \u00dcbertragung definiert ist, au\u00dfer Kraft gesetzt wird.", | ||||
|         "webhook_trigger_ANY": "Nach jedem Ereignis", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "Nach Erstellen einer Buchung", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "Nach Aktualisierung einer Buchung", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "Nach dem L\u00f6schen einer Buchung", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "Nach dem L\u00f6schen des Budgets", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "Nach dem \u00c4ndern des budgetierten Betrags", | ||||
|         "webhook_response_TRANSACTIONS": "Buchungsdetails", | ||||
|         "webhook_response_RELEVANT": "Relevante Details", | ||||
|         "webhook_response_ACCOUNTS": "Kontodetails", | ||||
|         "webhook_response_NONE": "Keine Details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "\u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7\u03c2 \u03c4\u03c9\u03bd \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd \u03b8\u03b1 \u03c5\u03c0\u03b5\u03c1\u03b9\u03c3\u03c7\u03cd\u03c3\u03b5\u03b9 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03c1\u03ce\u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c4\u03b7\u03c2 \u03b1\u03bd\u03ac\u03bb\u03b7\u03c8\u03b7\u03c2.", | ||||
|         "multi_account_warning_deposit": "\u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c4\u03c9\u03bd \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd \u03b8\u03b1 \u03c5\u03c0\u03b5\u03c1\u03b9\u03c3\u03c7\u03cd\u03c3\u03b5\u03b9 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03c1\u03ce\u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c4\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac\u03b8\u03b5\u03c3\u03b7\u03c2.", | ||||
|         "multi_account_warning_transfer": "\u039b\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c5\u03c0\u03cc\u03c8\u03b7 \u03cc\u03c4\u03b9 \u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c4\u03c9\u03bd \u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03ce\u03bd \u03b8\u03b1 \u03c5\u03c0\u03b5\u03c1\u03b9\u03c3\u03c7\u03cd\u03c3\u03b5\u03b9 \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 \u03c0\u03c1\u03ce\u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd \u03c4\u03b7\u03c2 \u03bc\u03b5\u03c4\u03b1\u03c6\u03bf\u03c1\u03ac\u03c2.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "\u039b\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "\u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", | ||||
|         "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", | ||||
|         "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "After transaction creation", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Transaction details", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Account details", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", | ||||
|         "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", | ||||
|         "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "After transaction creation", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Transaction details", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Account details", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Tenga en cuenta que la cuenta de origen de las divisiones posteriores ser\u00e1 anulada por lo que se defina en la primera divisi\u00f3n del gasto.", | ||||
|         "multi_account_warning_deposit": "Tenga en cuenta que la cuenta de destino de las divisiones posteriores ser\u00e1 anulada por lo que se defina en la primera divisi\u00f3n del retiro.", | ||||
|         "multi_account_warning_transfer": "Tenga en cuenta que la cuenta de origen + destino de divisiones posteriores ser\u00e1 anulada por lo que se defina en la primera divisi\u00f3n de la transferencia.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "Despu\u00e9s de crear la transacci\u00f3n", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "Despu\u00e9s de actualizar la transacci\u00f3n", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "Despu\u00e9s de eliminar la transacci\u00f3n", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Detalles de la transacci\u00f3n", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Detalles de la cuenta", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "\u0628\u0647 \u062e\u0627\u0637\u0631 \u062f\u0627\u0634\u062a\u0647 \u0628\u0627\u0634\u06cc\u062f \u06a9\u0647 \u062d\u0633\u0627\u0628 \u0645\u0646\u0628\u0639 \u0627\u0646\u0634\u0639\u0627\u0628\u0627\u062a \u0628\u0639\u062f\u06cc \u0628\u0627 \u0647\u0631 \u0622\u0646\u0686\u0647 \u062f\u0631 \u062a\u0642\u0633\u06cc\u0645 \u0627\u0648\u0644 \u0628\u0631\u062f\u0627\u0634\u062a \u062a\u0639\u0631\u06cc\u0641 \u0634\u062f\u0647 \u0627\u0633\u062a \u0644\u063a\u0648 \u0645\u06cc \u0634\u0648\u062f.\n", | ||||
|         "multi_account_warning_deposit": "\u0628\u0647 \u062e\u0627\u0637\u0631 \u062f\u0627\u0634\u062a\u0647 \u0628\u0627\u0634\u06cc\u062f \u06a9\u0647 \u062d\u0633\u0627\u0628 \u0645\u0642\u0635\u062f \u062a\u0642\u0633\u06cc\u0645 \u0647\u0627\u06cc \u0628\u0639\u062f\u06cc \u0628\u0627 \u0647\u0631 \u0622\u0646\u0686\u0647 \u062f\u0631 \u0627\u0648\u0644\u06cc\u0646 \u062a\u0642\u0633\u06cc\u0645 \u0633\u067e\u0631\u062f\u0647 \u062a\u0639\u0631\u06cc\u0641 \u0634\u062f\u0647 \u0627\u0633\u062a \u0644\u063a\u0648 \u0645\u06cc \u0634\u0648\u062f.\n", | ||||
|         "multi_account_warning_transfer": "\u0628\u0647 \u062e\u0627\u0637\u0631 \u062f\u0627\u0634\u062a\u0647 \u0628\u0627\u0634\u06cc\u062f \u06a9\u0647 \u062d\u0633\u0627\u0628 \u0645\u0628\u062f\u0627 + \u0645\u0642\u0635\u062f \u062a\u0642\u0633\u06cc\u0645\u200c\u0647\u0627\u06cc \u0628\u0639\u062f\u06cc \u0628\u0627 \u0647\u0631 \u0622\u0646\u0686\u0647 \u062f\u0631 \u062a\u0642\u0633\u06cc\u0645 \u0627\u0648\u0644 \u0627\u0646\u062a\u0642\u0627\u0644 \u062a\u0639\u0631\u06cc\u0641 \u0634\u062f\u0647 \u0627\u0633\u062a \u0644\u063a\u0648 \u0645\u06cc\u200c\u0634\u0648\u062f.\n", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "\u067e\u0633 \u0627\u0632 \u0627\u06cc\u062c\u0627\u062f \u062a\u0631\u0627\u06a9\u0646\u0634\n", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "\u067e\u0633 \u0627\u0632 \u0628\u0647 \u0631\u0648\u0632 \u0631\u0633\u0627\u0646\u06cc \u062a\u0631\u0627\u06a9\u0646\u0634\n", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "\u067e\u0633 \u0627\u0632 \u062a\u0631\u0627\u06a9\u0646\u0634 \u062d\u0630\u0641 \u0634\u0648\u062f\n", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "\u062c\u0632\u0626\u06cc\u0627\u062a \u062a\u0631\u0627\u06a9\u0646\u0634", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "\u062c\u0632\u0626\u06cc\u0627\u062a \u062d\u0633\u0627\u0628", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Muista, ett\u00e4 my\u00f6hempien jakojen l\u00e4hdetili m\u00e4\u00e4r\u00e4ytyy noston ensimm\u00e4isen jaon m\u00e4\u00e4ritysten mukaan.", | ||||
|         "multi_account_warning_deposit": "Muista, ett\u00e4 my\u00f6hempien jakojen kohdetili m\u00e4\u00e4r\u00e4ytyy talletuksen ensimm\u00e4isen jaon m\u00e4\u00e4ritysten mukaan.", | ||||
|         "multi_account_warning_transfer": "Muista, ett\u00e4 my\u00f6hempien jakojen l\u00e4hde- ja kohdetili m\u00e4\u00e4r\u00e4ytyv\u00e4t ensimm\u00e4isen jaon m\u00e4\u00e4ritysten mukaan.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "After transaction creation", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Transaction details", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Tilin tiedot", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Gardez en t\u00eate que le compte source des s\u00e9parations suivantes peut \u00eatre remplac\u00e9 par celui de la premi\u00e8re s\u00e9paration de la d\u00e9pense.", | ||||
|         "multi_account_warning_deposit": "Gardez en t\u00eate que le compte de destination des s\u00e9parations suivantes peut \u00eatre remplac\u00e9 par celui de la premi\u00e8re s\u00e9paration du d\u00e9p\u00f4t.", | ||||
|         "multi_account_warning_transfer": "Gardez en t\u00eate que les comptes source et de destination des s\u00e9parations suivantes peuvent \u00eatre remplac\u00e9s par ceux de la premi\u00e8re s\u00e9paration du transfert.", | ||||
|         "webhook_trigger_ANY": "Apr\u00e8s n'importe quel \u00e9v\u00e9nement", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "Apr\u00e8s la cr\u00e9ation de l'op\u00e9ration", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "Apr\u00e8s la mise \u00e0 jour de l'op\u00e9ration", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "Apr\u00e8s la suppression de l'op\u00e9ration", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "Apr\u00e8s la suppression du budget", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "Apr\u00e8s le changement du montant budg\u00e9tis\u00e9", | ||||
|         "webhook_response_TRANSACTIONS": "D\u00e9tails de l'op\u00e9ration", | ||||
|         "webhook_response_RELEVANT": "D\u00e9tails pertinents", | ||||
|         "webhook_response_ACCOUNTS": "D\u00e9tails du compte", | ||||
|         "webhook_response_NONE": "Aucun d\u00e9tail", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", | ||||
|         "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", | ||||
|         "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "Tranzakci\u00f3 l\u00e9trehoz\u00e1sa ut\u00e1n", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "Tranzakci\u00f3 friss\u00edt\u00e9se ut\u00e1n", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "Tranzakci\u00f3 t\u00f6rl\u00e9se ut\u00e1n", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Tranzakci\u00f3 r\u00e9szletei", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Sz\u00e1mlaadatok", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
| @@ -107,6 +107,7 @@ | ||||
|         "multi_account_warning_withdrawal": "Keep in mind that the source account of subsequent splits will be overruled by whatever is defined in the first split of the withdrawal.", | ||||
|         "multi_account_warning_deposit": "Keep in mind that the destination account of subsequent splits will be overruled by whatever is defined in the first split of the deposit.", | ||||
|         "multi_account_warning_transfer": "Keep in mind that the source + destination account of subsequent splits will be overruled by whatever is defined in the first split of the transfer.", | ||||
|         "webhook_trigger_ANY": "After any event", | ||||
|         "webhook_trigger_STORE_TRANSACTION": "After transaction creation", | ||||
|         "webhook_trigger_UPDATE_TRANSACTION": "After transaction update", | ||||
|         "webhook_trigger_DESTROY_TRANSACTION": "After transaction delete", | ||||
| @@ -115,6 +116,7 @@ | ||||
|         "webhook_trigger_DESTROY_BUDGET": "After budget delete", | ||||
|         "webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change", | ||||
|         "webhook_response_TRANSACTIONS": "Transaction details", | ||||
|         "webhook_response_RELEVANT": "Relevant details", | ||||
|         "webhook_response_ACCOUNTS": "Account details", | ||||
|         "webhook_response_NONE": "No details", | ||||
|         "webhook_delivery_JSON": "JSON", | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user