Compare commits

..

134 Commits

Author SHA1 Message Date
github-actions[bot]
20986e6426 Merge pull request #11644 from firefly-iii/release-1770218546
🤖 Automatically merge the PR into the develop branch.
2026-02-04 16:22:34 +01:00
JC5
9cd0ebe37e 🤖 Auto commit for release 'develop' on 2026-02-04 2026-02-04 16:22:26 +01:00
Sander Dorigo
9c2b83a971 Update event handlers 2026-02-04 16:16:27 +01:00
Sander Dorigo
e1d32da409 New event handler object 2026-02-04 08:29:09 +01:00
Sander Dorigo
c51df8cd83 Move events to service and repos 2026-02-04 08:18:35 +01:00
github-actions[bot]
9f016aed16 Merge pull request #11643 from firefly-iii/release-1770188788
🤖 Automatically merge the PR into the develop branch.
2026-02-04 08:06:37 +01:00
JC5
27df5ea800 🤖 Auto commit for release 'develop' on 2026-02-04 2026-02-04 08:06:28 +01:00
Sander Dorigo
2d7cdd36f0 Fix null pointer 2026-02-04 08:01:47 +01:00
github-actions[bot]
7888023c1a Merge pull request #11642 from firefly-iii/release-1770187820
🤖 Automatically merge the PR into the develop branch.
2026-02-04 07:50:28 +01:00
JC5
3032118788 🤖 Auto commit for release 'develop' on 2026-02-04 2026-02-04 07:50:20 +01:00
James Cole
89d96ddc17 Remove unused events. 2026-02-04 05:48:51 +01:00
James Cole
97c9937571 Clean up events for groups. 2026-02-04 05:46:19 +01:00
James Cole
c46aca0594 Clean up more events. 2026-02-03 21:04:07 +01:00
James Cole
0b33e1ff09 Refactor recalculation service. 2026-02-03 20:43:52 +01:00
James Cole
d267d2a0b0 Remove unused event handler. 2026-02-03 20:29:58 +01:00
James Cole
f2996dcebe Clean up event handlers and other code. 2026-02-03 20:24:16 +01:00
James Cole
ebb6a186cc Add some event handlers. 2026-02-03 18:58:24 +01:00
James Cole
304fae439a Expand listeners and observers. 2026-02-03 05:42:50 +01:00
James Cole
9ca81cf305 Clean up observer. 2026-02-02 20:15:06 +01:00
James Cole
cad5fb6d6b Clean up budget limit observer. 2026-02-02 20:13:00 +01:00
James Cole
53efafdbb2 Rename observers 2026-02-02 20:09:23 +01:00
James Cole
7922017288 Rename observers 2026-02-02 20:08:37 +01:00
James Cole
4e910a33dd Clean up events. 2026-02-02 20:02:45 +01:00
James Cole
bb031cdeb6 Clean up simple observers. 2026-02-02 19:59:05 +01:00
James Cole
60026bbcba Fix convert to primary amount. 2026-02-02 19:55:18 +01:00
James Cole
e96a1850da Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-02-02 19:38:00 +01:00
James Cole
bd2a746b8a Add new function. 2026-02-02 19:37:53 +01:00
github-actions[bot]
e4a3cbc9da Merge pull request #11640 from firefly-iii/release-1770044183
🤖 Automatically merge the PR into the develop branch.
2026-02-02 15:56:32 +01:00
JC5
18734b0edd 🤖 Auto commit for release 'develop' on 2026-02-02 2026-02-02 15:56:24 +01:00
Sander Dorigo
f52b3bf5f5 Merge branch 'develop' of https://github.com/firefly-iii/firefly-iii into develop 2026-02-02 15:39:06 +01:00
Sander Dorigo
3abba71f8d Fix null pointer 2026-02-02 15:39:03 +01:00
github-actions[bot]
95bdc87ed7 Merge pull request #11639 from firefly-iii/release-1770040333
🤖 Automatically merge the PR into the develop branch.
2026-02-02 14:52:22 +01:00
JC5
9a66b4017b 🤖 Auto commit for release 'develop' on 2026-02-02 2026-02-02 14:52:13 +01:00
Sander Dorigo
610e3f3ae5 Try to fix null pointer 2026-02-02 14:39:03 +01:00
James Cole
a58e70c08b Merge pull request #11632 from mateuszkulapl/main
fix v2 layout dashboard transactions load
2026-02-02 08:42:54 +01:00
mergify[bot]
24c96d40c9 Merge branch 'develop' into main 2026-02-02 03:56:28 +00:00
github-actions[bot]
74ae59910f Merge pull request #11636 from firefly-iii/release-1770004540
🤖 Automatically merge the PR into the develop branch.
2026-02-02 04:55:48 +01:00
JC5
6d49815be9 🤖 Auto commit for release 'develop' on 2026-02-02 2026-02-02 04:55:40 +01:00
mergify[bot]
ae680cd41f Merge branch 'develop' into main 2026-02-01 12:11:25 +00:00
mateuszkulapl
5e9ea1ca10 fix v2 layout dashboard transactions load
update transactions function to accept multiple params (page, start, end)
to match usage in loadAccounts (resources/assets/v2/src/pages/dashboard/accounts.js)
2026-02-01 13:05:52 +01:00
James Cole
6f558f424d Clean up notifications. 2026-01-31 11:51:34 +01:00
James Cole
4387876203 Replace lengthy notification calls, simplifies code. 2026-01-31 11:49:04 +01:00
James Cole
1b1ce3e04e Fix more events #11544 2026-01-31 10:41:48 +01:00
James Cole
ff64675122 Fix stored account event. 2026-01-31 10:34:26 +01:00
James Cole
1c545b7a74 Merge branch 'main' into develop 2026-01-31 10:27:40 +01:00
James Cole
a433ddcd7e Clean up events. 2026-01-31 10:27:12 +01:00
James Cole
b0e1b6fe51 Merge pull request #11624 from firefly-iii/dependabot/composer/composer-2f4529dfab
Bump symfony/process from 7.4.3 to 7.4.5 in the composer group across 1 directory
2026-01-29 05:22:35 +01:00
dependabot[bot]
f5523e60b6 Bump symfony/process in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [symfony/process](https://github.com/symfony/process).


Updates `symfony/process` from 7.4.3 to 7.4.5
- [Release notes](https://github.com/symfony/process/releases)
- [Changelog](https://github.com/symfony/process/blob/8.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/process/compare/v7.4.3...v7.4.5)

---
updated-dependencies:
- dependency-name: symfony/process
  dependency-version: 7.4.5
  dependency-type: indirect
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-29 04:08:55 +00:00
James Cole
e300efe640 Merge pull request #11623 from firefly-iii/dependabot/composer/dot-ci/php-cs-fixer/composer-5ee8f56ee5
Bump symfony/process from 8.0.3 to 8.0.5 in /.ci/php-cs-fixer in the composer group across 1 directory
2026-01-29 05:07:50 +01:00
dependabot[bot]
a97f227ddb Bump symfony/process
Bumps the composer group with 1 update in the /.ci/php-cs-fixer directory: [symfony/process](https://github.com/symfony/process).


Updates `symfony/process` from 8.0.3 to 8.0.5
- [Release notes](https://github.com/symfony/process/releases)
- [Changelog](https://github.com/symfony/process/blob/8.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/process/compare/v8.0.3...v8.0.5)

---
updated-dependencies:
- dependency-name: symfony/process
  dependency-version: 8.0.5
  dependency-type: indirect
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-28 21:29:36 +00:00
github-actions[bot]
5f8d7049b5 Merge pull request #11622 from firefly-iii/release-1769628396
🤖 Automatically merge the PR into the develop branch.
2026-01-28 20:26:45 +01:00
JC5
7e80f78f2e 🤖 Auto commit for release 'develop' on 2026-01-28 2026-01-28 20:26:36 +01:00
James Cole
ad922745c4 Fix #11620 2026-01-28 20:22:11 +01:00
github-actions[bot]
40abe74dc1 Merge pull request #11621 from firefly-iii/release-1769627627
🤖 Automatically merge the PR into the develop branch.
2026-01-28 20:13:55 +01:00
JC5
e5d2c4d163 🤖 Auto commit for release 'develop' on 2026-01-28 2026-01-28 20:13:48 +01:00
James Cole
2851053900 Fix nullpointer. 2026-01-28 20:08:32 +01:00
James Cole
b2f6ce1277 Merge pull request #11615 from nick322/feat/11614 2026-01-28 19:38:31 +01:00
Nick Huang
340b0661ba feat(#11614): Add New Taiwan Dollar to Currency Seeder 2026-01-28 15:57:52 +08:00
github-actions[bot]
be18f11f8c Merge pull request #11613 from firefly-iii/release-1769573331
🤖 Automatically merge the PR into the develop branch.
2026-01-28 05:08:59 +01:00
JC5
2f8ee67b31 🤖 Auto commit for release 'develop' on 2026-01-28 2026-01-28 05:08:52 +01:00
James Cole
1ecf55165e Merge branch 'main' into develop 2026-01-28 05:02:30 +01:00
James Cole
5aceccde4a Fix method call. 2026-01-28 05:02:14 +01:00
James Cole
abfaee5a55 Merge pull request #11612 from firefly-iii/dependabot/composer/composer-63bdf6e023 2026-01-28 04:17:00 +01:00
dependabot[bot]
fa65cc7ee2 Bump phpunit/phpunit in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit).


Updates `phpunit/phpunit` from 12.5.6 to 12.5.8
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/12.5.8/ChangeLog-12.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/12.5.6...12.5.8)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-version: 12.5.8
  dependency-type: direct:development
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 22:40:18 +00:00
James Cole
d64f1d0c18 Do not report ModelNotFoundException 2026-01-27 20:18:04 +01:00
github-actions[bot]
31206ce56c Merge pull request #11608 from firefly-iii/release-1769541194
🤖 Automatically merge the PR into the develop branch.
2026-01-27 20:13:21 +01:00
JC5
e4e9a09522 🤖 Auto commit for release 'develop' on 2026-01-27 2026-01-27 20:13:14 +01:00
James Cole
11303dc6e2 Merge branch 'main' into develop 2026-01-27 20:08:54 +01:00
James Cole
993f5cd292 Add language files. 2026-01-27 20:08:43 +01:00
github-actions[bot]
cc0854c712 Merge pull request #11607 from firefly-iii/release-1769540197
🤖 Automatically merge the PR into the develop branch.
2026-01-27 19:56:46 +01:00
JC5
5c6aee0037 🤖 Auto commit for release 'develop' on 2026-01-27 2026-01-27 19:56:37 +01:00
James Cole
391f8c34cc Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-01-27 19:50:53 +01:00
James Cole
db6ed26d5a Fix bad pointer 2026-01-27 19:50:46 +01:00
github-actions[bot]
eece951036 Merge pull request #11606 from firefly-iii/release-1769539261
🤖 Automatically merge the PR into the develop branch.
2026-01-27 19:41:10 +01:00
JC5
3d7a62293b 🤖 Auto commit for release 'develop' on 2026-01-27 2026-01-27 19:41:01 +01:00
James Cole
2691dbe438 Add flag. 2026-01-27 19:36:35 +01:00
James Cole
fe971ec611 Add new setting. 2026-01-27 19:35:14 +01:00
github-actions[bot]
9e4c5435f0 Merge pull request #11605 from firefly-iii/release-1769538639
🤖 Automatically merge the PR into the develop branch.
2026-01-27 19:30:49 +01:00
JC5
cdb2b91813 🤖 Auto commit for release 'develop' on 2026-01-27 2026-01-27 19:30:39 +01:00
James Cole
f4cf158d21 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	app/Factory/TransactionJournalFactory.php
2026-01-27 19:25:56 +01:00
James Cole
b19f1d0353 Restore use of NullArrayObject 2026-01-27 19:25:23 +01:00
github-actions[bot]
eb2c612476 Merge pull request #11604 from firefly-iii/release-1769534159
🤖 Automatically merge the PR into the develop branch.
2026-01-27 18:16:08 +01:00
JC5
e89eede8a0 🤖 Auto commit for release 'develop' on 2026-01-27 2026-01-27 18:15:59 +01:00
James Cole
00e09c4bd9 Fix #11601 2026-01-27 18:08:52 +01:00
James Cole
33e63434a3 Fix null pointers 2026-01-27 18:04:31 +01:00
James Cole
1b68e5374a Fix nulls 2026-01-27 18:04:16 +01:00
James Cole
f93e55f9b0 Fix null pointer. 2026-01-27 18:03:39 +01:00
James Cole
4a3f62df89 Fix call to old object. 2026-01-27 18:02:47 +01:00
James Cole
0c5ac39d5e Update composer packages 2026-01-27 18:00:17 +01:00
github-actions[bot]
ccb44d6fbd Merge pull request #11595 from firefly-iii/release-1769407906
🤖 Automatically merge the PR into the develop branch.
2026-01-26 07:11:54 +01:00
JC5
b9fe074080 🤖 Auto commit for release 'develop' on 2026-01-26 2026-01-26 07:11:46 +01:00
James Cole
033281ff51 Fix routes and add batch finish command. 2026-01-26 06:56:10 +01:00
github-actions[bot]
5e8d23ba91 Merge pull request #11592 from firefly-iii/release-1769398904
🤖 Automatically merge the PR into the develop branch.
2026-01-26 04:41:52 +01:00
JC5
35509f19ad 🤖 Auto commit for release 'develop' on 2026-01-26 2026-01-26 04:41:44 +01:00
github-actions[bot]
5e56eeb22e Merge pull request #11591 from firefly-iii/release-1769370043
🤖 Automatically merge the PR into the develop branch.
2026-01-25 20:40:53 +01:00
JC5
e921bb3ebe 🤖 Auto commit for release 'develop' on 2026-01-25 2026-01-25 20:40:43 +01:00
James Cole
353cd0f4f1 Try to fix call to trusted proxies 2026-01-25 20:36:25 +01:00
James Cole
1376ed16cf Rename file. 2026-01-25 20:32:10 +01:00
github-actions[bot]
36646b9c05 Merge pull request #11590 from firefly-iii/release-1769368675
🤖 Automatically merge the PR into the develop branch.
2026-01-25 20:18:02 +01:00
JC5
0b20c9d53b 🤖 Auto commit for release 'develop' on 2026-01-25 2026-01-25 20:17:55 +01:00
James Cole
b91d8661bc Clean up authentication. 2026-01-25 20:13:53 +01:00
James Cole
b684f3fc70 Merge pull request #11589 from mateuszkulapl/dark-mode-improvements
apply user-selected light/dark mode to form elements (checkboxes, date picker) #8613 #7620
2026-01-25 19:20:16 +01:00
mergify[bot]
c8a235b0b0 Merge branch 'develop' into dark-mode-improvements 2026-01-25 18:14:52 +00:00
mateuszkulapl
229db34d13 fix: apply user-selected light/dark mode to form elements (checkboxes, date picker) #8613 #7620 2026-01-25 18:45:19 +01:00
github-actions[bot]
744ad968e7 Merge pull request #11587 from firefly-iii/release-1769360073
🤖 Automatically merge the PR into the develop branch.
2026-01-25 17:54:41 +01:00
JC5
9b04d09dfc 🤖 Auto commit for release 'develop' on 2026-01-25 2026-01-25 17:54:33 +01:00
James Cole
66c899af62 Overrule middleware 2026-01-25 17:50:19 +01:00
github-actions[bot]
4779dd8d0d Merge pull request #11586 from firefly-iii/release-1769358688
🤖 Automatically merge the PR into the develop branch.
2026-01-25 17:31:34 +01:00
JC5
c47cc7ddc4 🤖 Auto commit for release 'develop' on 2026-01-25 2026-01-25 17:31:28 +01:00
James Cole
a97d46506b Config from the right place? 2026-01-25 17:27:27 +01:00
github-actions[bot]
2db4daa069 Merge pull request #11585 from firefly-iii/release-1769357347
🤖 Automatically merge the PR into the develop branch.
2026-01-25 17:09:14 +01:00
JC5
7f07d266f1 🤖 Auto commit for release 'develop' on 2026-01-25 2026-01-25 17:09:07 +01:00
github-actions[bot]
158081395b Merge pull request #11583 from firefly-iii/release-1769334926
🤖 Automatically merge the PR into the develop branch.
2026-01-25 10:55:33 +01:00
JC5
34a7fd3ef0 🤖 Auto commit for release 'develop' on 2026-01-25 2026-01-25 10:55:27 +01:00
James Cole
96aafacf43 Fix processing transactions in events. 2026-01-25 10:47:30 +01:00
James Cole
035d599910 Fix processing new transactions. 2026-01-25 10:22:02 +01:00
James Cole
f5a929d72e Clean up a bunch of code and improve transaction store events 2026-01-25 09:02:47 +01:00
James Cole
22b97ce8ef Clean up middleware and kernel files. 2026-01-24 20:36:20 +01:00
James Cole
832019792f Improve listeners 2026-01-24 19:02:18 +01:00
James Cole
76b8ff18b0 Transaction events for #11544 2026-01-24 18:52:07 +01:00
James Cole
8c0a82ac0a Add support for batch submission. 2026-01-24 18:34:49 +01:00
James Cole
7edc386cdd Fix strict types 2026-01-24 16:47:17 +01:00
James Cole
5437d07ec2 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	app/Providers/EventServiceProvider.php
2026-01-24 16:46:08 +01:00
James Cole
ef3a1401dd Remove more events for #11544 2026-01-24 16:45:28 +01:00
github-actions[bot]
aa6169e314 Merge pull request #11582 from firefly-iii/release-1769259448
🤖 Automatically merge the PR into the develop branch.
2026-01-24 13:57:36 +01:00
JC5
4a4f1ff055 🤖 Auto commit for release 'develop' on 2026-01-24 2026-01-24 13:57:28 +01:00
James Cole
844470bf08 Move more events #11544 2026-01-24 13:52:35 +01:00
James Cole
92e985a9b8 Move more events #11544 2026-01-24 13:44:02 +01:00
James Cole
70e11098af Move more events #11544 2026-01-24 13:33:40 +01:00
James Cole
e76ab21091 Fix #11563 2026-01-24 07:17:19 +01:00
github-actions[bot]
87644923cf Merge pull request #11581 from firefly-iii/release-1769199015
🤖 Automatically merge the PR into the develop branch.
2026-01-23 21:10:26 +01:00
JC5
c46e9519c2 🤖 Auto commit for release 'develop' on 2026-01-23 2026-01-23 21:10:15 +01:00
github-actions[bot]
775933c3e8 Merge pull request #11580 from firefly-iii/release-1769183303
🤖 Automatically merge the PR into the develop branch.
2026-01-23 16:48:32 +01:00
JC5
5e316a1f05 🤖 Auto commit for release 'develop' on 2026-01-23 2026-01-23 16:48:23 +01:00
314 changed files with 7677 additions and 7531 deletions

View File

@@ -26,6 +26,7 @@ $paths = [
$current . '/../../config',
$current . '/../../routes',
$current . '/../../tests',
$current . '/../../resources/lang/en_US',
];
$finder = PhpCsFixer\Finder::create()

View File

@@ -402,16 +402,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.92.5",
"version": "v3.93.1",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58"
"reference": "b3546ab487c0762c39f308dc1ec0ea2c461fc21a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58",
"reference": "260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b3546ab487c0762c39f308dc1ec0ea2c461fc21a",
"reference": "b3546ab487c0762c39f308dc1ec0ea2c461fc21a",
"shasum": ""
},
"require": {
@@ -443,14 +443,14 @@
},
"require-dev": {
"facile-it/paraunit": "^1.3.1 || ^2.7",
"infection/infection": "^0.31",
"infection/infection": "^0.32",
"justinrainbow/json-schema": "^6.6",
"keradus/cli-executor": "^2.3",
"mikey179/vfsstream": "^1.6.12",
"php-coveralls/php-coveralls": "^2.9",
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
"phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.46",
"phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.48",
"symfony/polyfill-php85": "^1.33",
"symfony/var-dumper": "^5.4.48 || ^6.4.26 || ^7.4.0 || ^8.0",
"symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0"
@@ -494,7 +494,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.92.5"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.93.1"
},
"funding": [
{
@@ -502,7 +502,7 @@
"type": "github"
}
],
"time": "2026-01-08T21:57:37+00:00"
"time": "2026-01-28T23:50:50+00:00"
},
{
"name": "psr/container",
@@ -1252,16 +1252,16 @@
},
{
"name": "symfony/console",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587"
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587",
"reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587",
"url": "https://api.github.com/repos/symfony/console/zipball/ace03c4cf9805080ff40cbeec69fca180c339a3b",
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b",
"shasum": ""
},
"require": {
@@ -1318,7 +1318,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v8.0.3"
"source": "https://github.com/symfony/console/tree/v8.0.4"
},
"funding": [
{
@@ -1338,7 +1338,7 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:52:06+00:00"
"time": "2026-01-13T13:06:50+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -1409,16 +1409,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v8.0.0",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "573f95783a2ec6e38752979db139f09fec033f03"
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/573f95783a2ec6e38752979db139f09fec033f03",
"reference": "573f95783a2ec6e38752979db139f09fec033f03",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47",
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47",
"shasum": ""
},
"require": {
@@ -1470,7 +1470,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.0"
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4"
},
"funding": [
{
@@ -1490,7 +1490,7 @@
"type": "tidelift"
}
],
"time": "2025-10-30T14:17:19+00:00"
"time": "2026-01-05T11:45:55+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -1640,16 +1640,16 @@
},
{
"name": "symfony/finder",
"version": "v8.0.3",
"version": "v8.0.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12"
"reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/dd3a2953570a283a2ba4e17063bb98c734cf5b12",
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12",
"url": "https://api.github.com/repos/symfony/finder/zipball/8bd576e97c67d45941365bf824e18dc8538e6eb0",
"reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0",
"shasum": ""
},
"require": {
@@ -1684,7 +1684,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v8.0.3"
"source": "https://github.com/symfony/finder/tree/v8.0.5"
},
"funding": [
{
@@ -1704,7 +1704,7 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:52:06+00:00"
"time": "2026-01-26T15:08:38+00:00"
},
{
"name": "symfony/options-resolver",
@@ -2358,16 +2358,16 @@
},
{
"name": "symfony/process",
"version": "v8.0.3",
"version": "v8.0.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "0cbbd88ec836f8757641c651bb995335846abb78"
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78",
"reference": "0cbbd88ec836f8757641c651bb995335846abb78",
"url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
"shasum": ""
},
"require": {
@@ -2399,7 +2399,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v8.0.3"
"source": "https://github.com/symfony/process/tree/v8.0.5"
},
"funding": [
{
@@ -2419,7 +2419,7 @@
"type": "tidelift"
}
],
"time": "2025-12-19T10:01:18+00:00"
"time": "2026-01-26T15:08:38+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2576,16 +2576,16 @@
},
{
"name": "symfony/string",
"version": "v8.0.1",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc"
"reference": "758b372d6882506821ed666032e43020c4f57194"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc",
"reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc",
"url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194",
"reference": "758b372d6882506821ed666032e43020c4f57194",
"shasum": ""
},
"require": {
@@ -2642,7 +2642,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v8.0.1"
"source": "https://github.com/symfony/string/tree/v8.0.4"
},
"funding": [
{
@@ -2662,7 +2662,7 @@
"type": "tidelift"
}
],
"time": "2025-12-01T09:13:36+00:00"
"time": "2026-01-12T12:37:40+00:00"
}
],
"packages-dev": [],

View File

@@ -4,6 +4,8 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
## 2026
- Nick Huang
- mateuszkulapl
- Gianluca Martino
- embedded

View File

@@ -158,7 +158,10 @@ class TagController extends Controller
'currency_id' => (string) $foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
$response[$foreignKey]['difference'] = bcadd(
(string) $response[$foreignKey]['difference'],
Steam::positive($journal['foreign_amount'])
);
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference'];
}
}

View File

@@ -155,7 +155,10 @@ class TagController extends Controller
'currency_id' => (string) $foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
$response[$foreignKey]['difference'] = bcadd(
(string) $response[$foreignKey]['difference'],
Steam::positive($journal['foreign_amount'])
);
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference']; // intentional float
}
}

View File

@@ -63,30 +63,28 @@ class TriggerController extends Controller
{
// find recurrence occurrence for this date and trigger it.
// grab the date from the last time the recurrence fired:
$backupDate = $recurrence->latest_date;
$date = $request->getDate();
$backupDate = $recurrence->latest_date;
$date = $request->getDate();
// fire the recurring cron job on the given date, then post-date the created transaction.
Log::info(sprintf('Trigger: will now fire recurring cron job task for date "%s".', $date->format('Y-m-d H:i:s')));
/** @var CreateRecurringTransactions $job */
$job = app(CreateRecurringTransactions::class);
$job = app(CreateRecurringTransactions::class);
$job->setRecurrences(new Collection()->push($recurrence));
$job->setDate($date);
$job->setForce(false);
$job->handle();
Log::debug('Done with recurrence.');
$groups = $job->getGroups();
$groups = $job->getGroups();
$this->repository->markGroupsAsNow($groups);
$recurrence->latest_date = $backupDate;
$recurrence->latest_date_tz = $backupDate?->format('e');
$recurrence->save();
$recurrence = $this->repository->setLatestDate($recurrence, $backupDate);
Preferences::mark();
// enrich groups and return them:
$paginator = new LengthAwarePaginator(new Collection(), 0, 1);
$paginator = new LengthAwarePaginator(new Collection(), 0, 1);
if ($groups->count() > 0) {
/** @var User $admin */
$admin = auth()->user();
@@ -98,20 +96,20 @@ class TriggerController extends Controller
$paginator = $collector->getPaginatedGroups();
}
$manager = $this->getManager();
$manager = $this->getManager();
$paginator->setPath(route('api.v1.recurrences.trigger', [$recurrence->id]).$this->buildParams());
// enrich
$admin = auth()->user();
$enrichment = new TransactionGroupEnrichment();
$admin = auth()->user();
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class);
$transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($transactions, $transformer, 'transactions');
$resource = new FractalCollection($transactions, $transformer, 'transactions');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);

View File

@@ -27,13 +27,11 @@ namespace FireflyIII\Api\V1\Controllers\Models\Transaction;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Transaction\StoreRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Rules\IsDuplicateTransaction;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\TransactionGroupTransformer;
@@ -108,10 +106,6 @@ class StoreController extends Controller
throw new ValidationException($validator);
}
Preferences::mark();
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
event(new StoredTransactionGroup($transactionGroup, $applyRules, $fireWebhooks));
$manager = $this->getManager();

View File

@@ -26,7 +26,8 @@ namespace FireflyIII\Api\V1\Controllers\Models\Transaction;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Transaction\UpdateRequest;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\UpdatedSingleTransactionGroup;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
@@ -72,24 +73,29 @@ class UpdateController extends Controller
public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse
{
Log::debug('Now in update routine for transaction group');
$data = $request->getAll();
$oldHash = $this->groupRepository->getCompareHash($transactionGroup);
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
$newHash = $this->groupRepository->getCompareHash($transactionGroup);
$manager = $this->getManager();
$data = $request->getAll();
$oldHash = $this->groupRepository->getCompareHash($transactionGroup);
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
$newHash = $this->groupRepository->getCompareHash($transactionGroup);
$manager = $this->getManager();
Preferences::mark();
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
$runRecalculations = $oldHash !== $newHash;
event(new UpdatedTransactionGroup($transactionGroup, $applyRules, $fireWebhooks, $runRecalculations));
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
$runRecalculations = $oldHash !== $newHash;
$flags = new TransactionGroupEventFlags();
$flags->applyRules = $applyRules;
$flags->fireWebhooks = $fireWebhooks;
$flags->recalculateCredit = $runRecalculations;
event(new UpdatedSingleTransactionGroup($transactionGroup, $flags));
/** @var User $admin */
$admin = auth()->user();
$admin = auth()->user();
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector
->setUser($admin)
// filter on transaction group.
@@ -98,20 +104,20 @@ class UpdateController extends Controller
->withAPIInformation()
;
$selectedGroup = $collector->getGroups()->first();
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw new NotFoundHttpException();
}
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
/** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class);
$transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new Item($selectedGroup, $transformer, 'transactions');
$resource = new Item($selectedGroup, $transformer, 'transactions');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/*
* BatchController.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Api\V1\Controllers\System;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\UserRequestedBatchProcessing;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class BatchController extends Controller
{
private JournalRepositoryInterface $repository;
/**
* UserController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(function ($request, $next) {
$this->repository = app(JournalRepositoryInterface::class);
$this->repository->setUser(auth()->user()); // should not have to do this.
return $next($request);
});
}
public function finishBatch(Request $request): JsonResponse
{
$journals = $this->repository->getUncompletedJournals();
if (0 === count($journals)) {
return response()->json([], 204);
}
/** @var TransactionJournal $first */
$first = $journals->first();
$group = $first?->transactionGroup;
if (null === $group) {
return response()->json([], 204);
}
$flags = new TransactionGroupEventFlags();
$flags->applyRules = 'true' === $request->get('apply_rules');
event(new UserRequestedBatchProcessing($flags));
// event(new CreatedSingleTransactionGroup($group, $flags));
return response()->json([], 204);
}
}

View File

@@ -58,6 +58,8 @@ class UserController extends Controller
});
}
public function finishBatch(): JsonResponse {}
/**
* This endpoint is documented at:
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/users/deleteUser

View File

@@ -26,7 +26,7 @@ namespace FireflyIII\Api\V1\Controllers\Webhook;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\Webhook;
@@ -176,8 +176,8 @@ class ShowController extends Controller
}
// trigger event to send them:
Log::debug('send event RequestedSendWebhookMessages from ShowController::triggerTransaction()');
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
return response()->json([], 204);
}

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Requests\Models\BudgetLimit;
use Carbon\Carbon;
use FireflyIII\Factory\TransactionCurrencyFactory;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\Rules\IsValidPositiveAmount;
use FireflyIII\Support\Facades\Amount;
@@ -89,24 +90,24 @@ class StoreRequest extends FormRequest
if (0 !== count($validator->failed())) {
return;
}
$data = $validator->getData();
$data = $validator->getData();
// if no currency has been provided, use the user's default currency:
/** @var TransactionCurrencyFactory $factory */
$factory = app(TransactionCurrencyFactory::class);
$currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null);
$factory = app(TransactionCurrencyFactory::class);
$currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null);
if (null === $currency) {
$currency = Amount::getPrimaryCurrency();
}
$currency->enabled = true;
$currency->save();
$repository = app(CurrencyRepositoryInterface::class);
$repository->enable($currency);
// validator already concluded start and end are valid dates:
$start = Carbon::parse($data['start'], config('app.timezone'));
$end = Carbon::parse($data['end'], config('app.timezone'));
$start = Carbon::parse($data['start'], config('app.timezone'));
$end = Carbon::parse($data['end'], config('app.timezone'));
// find limit with same date range and currency.
$limit = $budget
$limit = $budget
->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d'))
->where('budget_limits.end_date', $end->format('Y-m-d'))

View File

@@ -47,7 +47,7 @@ class StoreRequest extends FormRequest
*/
public function getAll(): array
{
$fields = ['order' => ['order', 'convertInteger']];
$fields = ['order' => ['order', 'convertInteger']];
$data = $this->getAllData($fields);
$data['name'] = $this->convertString('name');
$data['accounts'] = $this->parseAccounts($this->get('accounts'));

View File

@@ -64,6 +64,7 @@ class StoreRequest extends FormRequest
return [
'group_title' => $this->convertString('group_title'),
'error_if_duplicate_hash' => $this->boolean('error_if_duplicate_hash'),
'batch_submission' => $this->boolean('batch_submission'),
'apply_rules' => $this->boolean('apply_rules', true),
'fire_webhooks' => $this->boolean('fire_webhooks', true),
'transactions' => $this->getTransactionData(),

View File

@@ -113,7 +113,7 @@ class UpdateRequest extends FormRequest
];
$this->booleanFields = ['reconciled'];
$this->arrayFields = ['tags'];
$data = [];
$data = ['batch_submission' => false];
if ($this->has('transactions')) {
$data['transactions'] = $this->getTransactionData();
}
@@ -123,6 +123,9 @@ class UpdateRequest extends FormRequest
if ($this->has('fire_webhooks')) {
$data['fire_webhooks'] = $this->boolean('fire_webhooks', true);
}
if ($this->has('batch_submission')) {
$data['batch_submission'] = $this->boolean('batch_submission');
}
if ($this->has('group_title')) {
$data['group_title'] = $this->convertString('group_title');
}

View File

@@ -38,6 +38,8 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Services\Internal\Destroy\GenericDestroyService;
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Console\Command;
@@ -51,9 +53,13 @@ class CorrectsAmounts extends Command
protected $description = 'This command makes sure positive and negative amounts are recorded correctly.';
protected $signature = 'correction:amounts';
private JournalDestroyService $service;
private GenericDestroyService $genericService;
public function handle(): int
{
$this->service = new JournalDestroyService();
$this->genericService = new GenericDestroyService();
// transfers must not have foreign currency info if both accounts have the same currency.
$this->correctTransfers();
// auto budgets must be positive
@@ -177,8 +183,7 @@ class CorrectsAmounts extends Command
private function deleteJournal(TransactionJournal $journal): void
{
$journal->transactionGroup?->delete();
$journal->delete();
$this->service->destroy($journal);
}
private function fixAutoBudgets(): void
@@ -282,7 +287,7 @@ class CorrectsAmounts extends Command
));
$item->rule->active = false;
$item->rule->save();
$item->forceDelete();
$this->genericService->deleteRuleTrigger($item);
return false;
}

View File

@@ -25,8 +25,8 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands\Correction;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Handlers\Events\UpdatedGroupEventHandler;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\UpdatedSingleTransactionGroup;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
@@ -44,8 +44,8 @@ class CorrectsGroupAccounts extends Command
*/
public function handle(): int
{
$groups = [];
$res = TransactionJournal::groupBy('transaction_group_id')->get(['transaction_group_id', DB::raw('COUNT(transaction_group_id) as the_count')]);
$groups = [];
$res = TransactionJournal::groupBy('transaction_group_id')->get(['transaction_group_id', DB::raw('COUNT(transaction_group_id) as the_count')]);
/** @var TransactionJournal $journal */
foreach ($res as $journal) {
@@ -53,12 +53,13 @@ class CorrectsGroupAccounts extends Command
$groups[] = (int) $journal->transaction_group_id;
}
}
$handler = new UpdatedGroupEventHandler();
foreach ($groups as $groupId) {
$group = TransactionGroup::find($groupId);
// TODO in theory the "unifyAccounts" method could lead to the need for run recalculations.
$event = new UpdatedTransactionGroup($group, true, true, false);
$handler->unifyAccounts($event);
$group = TransactionGroup::find($groupId);
$flags = new TransactionGroupEventFlags();
$flags->applyRules = true;
$flags->fireWebhooks = true;
$flags->recalculateCredit = true;
event(new UpdatedSingleTransactionGroup($group, $flags));
}
return 0;

View File

@@ -68,7 +68,7 @@ class CorrectsMetaDataFields extends Command
private function rename(string $original, string $update): void
{
$total = DB::table('journal_meta')->where('name', '=', $original)->update(['name' => $update]);
$total = DB::table('journal_meta')->where('name', '=', $original)->update(['name' => $update]);
$this->count += $total;
}
}

View File

@@ -25,28 +25,9 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands\Correction;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Handlers\Observer\TransactionObserver;
use FireflyIII\Models\Account;
use FireflyIII\Models\AutoBudget;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Services\Internal\Recalculate\PrimaryAmountRecalculationService;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as DatabaseBuilder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CorrectsPrimaryCurrencyAmounts extends Command
@@ -70,186 +51,11 @@ class CorrectsPrimaryCurrencyAmounts extends Command
Log::debug('Will update all primary currency amounts. This may take some time.');
$this->friendlyWarning('Recalculating primary currency amounts for all objects. This may take some time!');
/** @var UserGroupRepositoryInterface $repository */
$repository = app(UserGroupRepositoryInterface::class);
$calculator = new PrimaryAmountRecalculationService();
$calculator->recalculate();
Preferences::mark();
/** @var UserGroup $userGroup */
foreach ($repository->getAll() as $userGroup) {
$this->recalculateForGroup($userGroup);
}
$this->friendlyInfo('Recalculated all primary currency amounts.');
return 0;
}
private function recalculateForGroup(UserGroup $userGroup): void
{
Log::debug(sprintf('Now recalculating for user group #%d', $userGroup->id));
$this->recalculateAccounts($userGroup);
// do a check with the group's currency so we can skip some stuff.
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
$this->recalculatePiggyBanks($userGroup, $currency);
$this->recalculateBudgets($userGroup, $currency);
$this->recalculateAvailableBudgets($userGroup, $currency);
$this->recalculateBills($userGroup, $currency);
$this->calculateTransactions($userGroup, $currency);
}
private function recalculateAccounts(UserGroup $userGroup): void
{
$set = $userGroup
->accounts()
->where(static function (EloquentBuilder $q): void {
$q->whereNotNull('virtual_balance');
// this needs a different piece of code for postgres.
if ('pgsql' === config('database.default')) {
$q->orWhere(DB::raw('CAST(virtual_balance AS TEXT)'), '!=', '');
}
if ('pgsql' !== config('database.default')) {
$q->orWhere('virtual_balance', '!=', '');
}
})
->get()
;
/** @var Account $account */
foreach ($set as $account) {
$account->touch();
}
Log::debug(sprintf('Recalculated %d accounts for user group #%d.', $set->count(), $userGroup->id));
}
private function recalculatePiggyBanks(UserGroup $userGroup, TransactionCurrency $currency): void
{
$converter = new ExchangeRateConverter();
$converter->setUserGroup($userGroup);
$converter->setIgnoreSettings(true);
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$set = $repository->getPiggyBanks();
$set = $set->filter(static fn (PiggyBank $piggyBank): bool => $currency->id !== $piggyBank->transaction_currency_id);
foreach ($set as $piggyBank) {
$piggyBank->encrypted = false;
$piggyBank->save();
foreach ($piggyBank->accounts as $account) {
$account->pivot->native_current_amount = null;
if (0 !== bccomp((string) $account->pivot->current_amount, '0')) {
$account->pivot->native_current_amount = $converter->convert(
$piggyBank->transactionCurrency,
$currency,
today(),
(string) $account->pivot->current_amount
);
}
$account->pivot->save();
}
$this->recalculatePiggyBankEvents($piggyBank);
}
Log::debug(sprintf('Recalculated %d piggy banks for user group #%d.', $set->count(), $userGroup->id));
}
private function recalculatePiggyBankEvents(PiggyBank $piggyBank): void
{
$set = $piggyBank->piggyBankEvents()->get();
$set->each(static function (PiggyBankEvent $event): void { // @phpstan-ignore-line
$event->touch();
});
Log::debug(sprintf('Recalculated %d piggy bank events.', $set->count()));
}
private function recalculateBudgets(UserGroup $userGroup, TransactionCurrency $currency): void
{
$set = $userGroup->budgets()->get();
/** @var Budget $budget */
foreach ($set as $budget) {
$this->recalculateBudgetLimits($budget, $currency);
$this->recalculateAutoBudgets($budget, $currency);
}
Log::debug(sprintf('Recalculated %d budgets.', $set->count()));
}
private function recalculateBudgetLimits(Budget $budget, TransactionCurrency $currency): void
{
$set = $budget->budgetlimits()->where('transaction_currency_id', '!=', $currency->id)->get();
/** @var BudgetLimit $limit */
foreach ($set as $limit) {
Log::debug(sprintf('Will now touch BL #%d', $limit->id));
$limit->touch();
Log::debug(sprintf('Done with touch BL #%d', $limit->id));
}
Log::debug(sprintf('Recalculated %d budget limits for budget #%d.', $set->count(), $budget->id));
}
private function recalculateAutoBudgets(Budget $budget, TransactionCurrency $currency): void
{
$set = $budget->autoBudgets()->where('transaction_currency_id', '!=', $currency->id)->get();
/** @var AutoBudget $autoBudget */
foreach ($set as $autoBudget) {
$autoBudget->touch();
}
Log::debug(sprintf('Recalculated %d auto budgets for budget #%d.', $set->count(), $budget->id));
}
private function recalculateAvailableBudgets(UserGroup $userGroup, TransactionCurrency $currency): void
{
Log::debug('Start with available budgets.');
$set = $userGroup->availableBudgets()->where('transaction_currency_id', '!=', $currency->id)->get();
/** @var AvailableBudget $budget */
foreach ($set as $budget) {
$budget->touch();
}
Log::debug(sprintf('Recalculated %d available budgets.', $set->count()));
}
private function recalculateBills(UserGroup $userGroup, TransactionCurrency $currency): void
{
$set = $userGroup->bills()->where('transaction_currency_id', '!=', $currency->id)->get();
/** @var Bill $bill */
foreach ($set as $bill) {
$bill->touch();
}
Log::debug(sprintf('Recalculated %d bills.', $set->count()));
}
private function calculateTransactions(UserGroup $userGroup, TransactionCurrency $currency): void
{
// custom query because of the potential size of this update.
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
})->orWhere(static function (DatabaseBuilder $q3) use ($currency): void {
$q3->whereNot('transactions.transaction_currency_id', $currency->id)->whereNot('transactions.foreign_currency_id', $currency->id);
});
})
// ->where(static function (DatabaseBuilder $q) use ($currency): void {
// $q->whereNot('transactions.transaction_currency_id', $currency->id)
// ->whereNot('transactions.foreign_currency_id', $currency->id)
// ;
// })
->get(['transactions.id'])
;
TransactionObserver::$recalculate = false;
foreach ($set as $item) {
// here we are.
/** @var null|Transaction $transaction */
$transaction = Transaction::find($item->id);
$transaction?->touch();
}
TransactionObserver::$recalculate = true;
Log::debug(sprintf('Recalculated %d transactions.', $set->count()));
}
}

View File

@@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands\Correction;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Models\AccountBalance;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Bill;
use FireflyIII\Models\BudgetLimit;
@@ -47,7 +46,6 @@ class CorrectsTimezoneInformation extends Command
use ShowsFriendlyMessages;
public static array $models = [
AccountBalance::class => ['date'], // done
AvailableBudget::class => ['start_date', 'end_date'], // done
Bill::class => ['date', 'end_date', 'extension_date'], // done
BudgetLimit::class => ['start_date', 'end_date'], // done

View File

@@ -369,7 +369,7 @@ class CorrectsUnevenAmount extends Command
continue;
}
if (!$this->isBetweenAssetAndLiability($journal)) {
Log::debug('Not between asset and liability, continue.');
// Log::debug('Not between asset and liability, continue.');
continue;
}

View File

@@ -295,7 +295,11 @@ class UpgradesTransferCurrencies extends Command
{
if (null === $this->sourceTransaction->transaction_currency_id && $this->sourceCurrency instanceof TransactionCurrency) {
$this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id;
$message = sprintf('Transaction #%d has no currency setting, now set to %s.', $this->sourceTransaction->id, $this->sourceCurrency->code);
$message = sprintf(
'Transaction #%d has no currency setting, now set to %s.',
$this->sourceTransaction->id,
$this->sourceCurrency->code
);
$this->friendlyInfo($message);
++$this->count;
$this->sourceTransaction->save();
@@ -335,7 +339,11 @@ class UpgradesTransferCurrencies extends Command
{
if (null === $this->destinationTransaction->transaction_currency_id && $this->destinationCurrency instanceof TransactionCurrency) {
$this->destinationTransaction->transaction_currency_id = $this->destinationCurrency->id;
$message = sprintf('Transaction #%d has no currency setting, now set to %s.', $this->destinationTransaction->id, $this->destinationCurrency->code);
$message = sprintf(
'Transaction #%d has no currency setting, now set to %s.',
$this->destinationTransaction->id,
$this->destinationCurrency->code
);
$this->friendlyInfo($message);
++$this->count;
$this->destinationTransaction->save();

View File

@@ -1,8 +1,10 @@
<?php
declare(strict_types=1);
/*
* StoredAccount.php
* Copyright (c) 2021 james@firefly-iii.org
* CreatedNewAccount.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,17 +22,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
namespace FireflyIII\Events\Model\Account;
use FireflyIII\Events\Event;
use FireflyIII\Models\Account;
use Illuminate\Queue\SerializesModels;
/**
* Class StoredAccount
*/
class StoredAccount extends Event
class CreatedNewAccount extends Event
{
use SerializesModels;

View File

@@ -1,8 +1,10 @@
<?php
/**
* UpdatedTransactionGroup.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* UpdatedExistingAccount.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,17 +22,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Model\Account;
namespace FireflyIII\Events;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Events\Event;
use FireflyIII\Models\Account;
use Illuminate\Queue\SerializesModels;
/**
* Class UpdatedTransactionGroup.
*/
class UpdatedTransactionGroup extends Event
class UpdatedExistingAccount extends Event
{
use SerializesModels;
@@ -38,9 +36,6 @@ class UpdatedTransactionGroup extends Event
* Create a new event instance.
*/
public function __construct(
public TransactionGroup $transactionGroup,
public bool $applyRules,
public bool $fireWebhooks,
public bool $runRecalculations
public Account $account
) {}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* CreatedSingleTransactionGroup.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class CreatedSingleTransactionGroup extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public TransactionGroupEventFlags $flags,
public TransactionGroupEventObjects $objects
) {
Log::debug(__METHOD__);
}
}

View File

@@ -1,8 +1,10 @@
<?php
/**
* DestroyedTransactionGroup.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* DestroyedSingleTransactionGroup.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,18 +22,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
/**
* Class DestroyedTransactionGroup.
*/
class DestroyedTransactionGroup extends Event
class DestroyedSingleTransactionGroup extends Event
{
use SerializesModels;
@@ -40,7 +37,5 @@ class DestroyedTransactionGroup extends Event
*/
public function __construct(
public TransactionGroup $transactionGroup
) {
Log::debug(sprintf('Now in %s', __METHOD__));
}
) {}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* TransactionGroupEventFlags.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\TransactionGroup;
class TransactionGroupEventFlags
{
public bool $applyRules = true;
public bool $fireWebhooks = true;
public bool $batchSubmission = false;
public bool $recalculateCredit = true;
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Support\Collection;
/**
* This class collects all objects before and after the creation, removal or updating
* of a transaction group. The idea is that this class contains all relevant objects.
* Right now, that means journals, tags, accounts, budgets and categories.
*
* By collecting these objects (in case of an update: before AND after update) there
* is a unified set of objects to manage: update balances, recalculate credits, etc.
*/
class TransactionGroupEventObjects
{
public Collection $accounts;
public Collection $budgets;
public Collection $categories;
public Collection $tags;
public Collection $transactionJournals;
public function __construct()
{
$this->accounts = new Collection();
$this->budgets = new Collection();
$this->categories = new Collection();
$this->tags = new Collection();
$this->transactionJournals = new Collection();
}
public static function collectFromTransactionGroup(TransactionGroup $transactionGroup): self
{
$object = new self();
/** @var TransactionJournal $journal */
foreach ($transactionGroup->transactionJournals as $journal) {
$object->transactionJournals->push($journal);
$object->budgets = $object->tags->merge($journal->budgets);
$object->categories = $object->tags->merge($journal->categories);
$object->tags = $object->tags->merge($journal->tags);
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
$object->accounts->push($transaction->account);
}
}
return $object;
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* TransactionGroupRequestsAuditLogEntry.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Queue\SerializesModels;
class TransactionGroupRequestsAuditLogEntry extends Event
{
use SerializesModels;
public function __construct(
public Model $changer,
public Model $auditable,
public string $field,
public mixed $before,
public mixed $after
) {}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* TransactionGroupsRequestedReporting.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
class TransactionGroupsRequestedReporting extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public int $userId,
public Collection $groups
) {}
}

View File

@@ -29,6 +29,9 @@ use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Queue\SerializesModels;
/**
* @deprecated
*/
class TriggeredStoredTransactionGroup extends Event
{
use SerializesModels;

View File

@@ -1,8 +1,10 @@
<?php
/**
* StoredTransactionGroup.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* UpdatedSingleTransactionGroup.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,17 +22,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Queue\SerializesModels;
/**
* Class StoredTransactionGroup.
*/
class StoredTransactionGroup extends Event
class UpdatedSingleTransactionGroup extends Event
{
use SerializesModels;
@@ -39,7 +37,6 @@ class StoredTransactionGroup extends Event
*/
public function __construct(
public TransactionGroup $transactionGroup,
public bool $applyRules,
public bool $fireWebhooks
public TransactionGroupEventFlags $flags
) {}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/*
* UserRequestedBatchProcessing.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use Illuminate\Support\Facades\Log;
class UserRequestedBatchProcessing extends Event
{
public function __construct(
public TransactionGroupEventFlags $flags
) {
Log::debug(__METHOD__);
}
}

View File

@@ -1,8 +1,10 @@
<?php
declare(strict_types=1);
/*
* RequestedSendWebhookMessages.php
* Copyright (c) 2021 james@firefly-iii.org
* WebhookMessagesRequestSending.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,16 +22,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
namespace FireflyIII\Events\Model\Webhook;
use FireflyIII\Events\Event;
use Illuminate\Queue\SerializesModels;
/**
* Class RequestedSendWebhookMessages
*/
class RequestedSendWebhookMessages extends Event
class WebhookMessagesRequestSending extends Event
{
use SerializesModels;
}

View File

@@ -1,60 +0,0 @@
<?php
/**
* RequestedReportOnJournals.php
* Copyright (c) 2019 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;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class RequestedReportOnJournals
*/
class RequestedReportOnJournals
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public int $userId,
public Collection $groups
) {
Log::debug('In event RequestedReportOnJournals.');
}
/**
* Get the channels the event should broadcast on.
*/
public function broadcastOn(): PrivateChannel
{
return new PrivateChannel('channel-name');
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/*
* NewInvitationCreated.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Security\System;
use FireflyIII\Events\Event;
use FireflyIII\Models\InvitedUser;
use Illuminate\Queue\SerializesModels;
class NewInvitationCreated extends Event
{
use SerializesModels;
public function __construct(
public InvitedUser $invitee
) {}
}

View File

@@ -1,8 +1,10 @@
<?php
/**
* RequestedVersionCheckStatus.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* SystemRequestedVersionCheck.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,24 +22,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
namespace FireflyIII\Events\Security\System;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
/**
* Class RequestedVersionCheckStatus
*/
class RequestedVersionCheckStatus extends Event
class SystemRequestedVersionCheck extends Event
{
use SerializesModels;
/**
* Create a new event instance. This event is triggered when Firefly III wants to know
* what the deal is with the version checker.
*/
public function __construct(
public User $user
) {}

View File

@@ -31,6 +31,7 @@ use FireflyIII\Jobs\MailError;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\JsonResponse;
@@ -71,6 +72,7 @@ class Handler extends ExceptionHandler
AuthenticationException::class,
LaravelValidationException::class,
NotFoundHttpException::class,
ModelNotFoundException::class,
GoneHttpException::class,
OAuthServerException::class,
LaravelOAuthException::class,

View File

@@ -25,7 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Events\StoredAccount;
use FireflyIII\Events\Model\Account\CreatedNewAccount;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
@@ -122,7 +122,7 @@ class AccountFactory
$return = $this->createAccount($type, $data);
event(new StoredAccount($return));
event(new CreatedNewAccount($return));
return $return;
}

View File

@@ -109,23 +109,21 @@ class TransactionJournalFactory
public function create(array $data): Collection
{
Log::debug('Now in TransactionJournalFactory::create()');
// convert to special object.
$dataObject = new NullArrayObject($data);
Log::debug('Start of TransactionJournalFactory::create()');
$collection = new Collection();
$transactions = $dataObject['transactions'] ?? [];
$collection = new Collection();
$transactions = $data['transactions'] ?? [];
if (0 === count($transactions)) {
Log::error('There are no transactions in the array, the TransactionJournalFactory cannot continue.');
return new Collection();
}
$batchSubmission = $data['batch_submission'] ?? false;
try {
/** @var array $row */
foreach ($transactions as $index => $row) {
$row['batch_submission'] = $batchSubmission;
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
$journal = $this->createJournal(new NullArrayObject($row));
$journal = $this->createJournal(new NullArrayObject($row));
if ($journal instanceof TransactionJournal) {
$collection->push($journal);
}
@@ -186,7 +184,6 @@ class TransactionJournalFactory
$carbon->setTimezone(config('app.timezone'));
// 2024-11-19, overrule timezone with UTC and store it as UTC.
if (true === FireflyConfig::get('utc', false)->data) {
$carbon->setTimezone('UTC');
}
@@ -273,7 +270,7 @@ class TransactionJournalFactory
'date_tz' => $carbon->format('e'),
'order' => $order,
'tag_count' => 0,
'completed' => 0,
'completed' => !$row['batch_submission'],
]);
Log::debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
@@ -331,7 +328,7 @@ class TransactionJournalFactory
throw new FireflyException($e->getMessage(), 0, $e);
}
$journal->completed = true;
Log::debug(sprintf('Is part of a batch submission? %s', var_export($row['batch_submission'], true)));
$journal->save();
$this->storeBudget($journal, $row);
$this->storeCategory($journal, $row);
@@ -346,18 +343,16 @@ class TransactionJournalFactory
private function hashArray(NullArrayObject $row): string
{
$dataRow = $row->getArrayCopy();
unset($dataRow['import_hash_v2'], $dataRow['original_source']);
unset($row['import_hash_v2'], $row['original_source']);
try {
$json = json_encode($dataRow, JSON_THROW_ON_ERROR);
$json = json_encode($row, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
Log::error(sprintf('Could not encode dataRow: %s', $e->getMessage()));
$json = microtime();
}
$hash = hash('sha256', $json);
Log::debug(sprintf('The hash is: %s', $hash), $dataRow);
$hash = hash('sha256', $json);
Log::debug(sprintf('The hash is: %s', $hash), $row->getArrayCopy());
return $hash;
}
@@ -595,14 +590,14 @@ class TransactionJournalFactory
private function storeMetaFields(TransactionJournal $journal, NullArrayObject $transaction): void
{
foreach ($this->fields as $field) {
$this->storeMeta($journal, $transaction, $field);
$this->storeMeta($journal, $transaction->getArrayCopy(), $field);
}
}
protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void
protected function storeMeta(TransactionJournal $journal, array $data, string $field): void
{
$set = ['journal' => $journal, 'name' => $field, 'data' => (string) ($data[$field] ?? '')];
if ($data[$field] instanceof Carbon) {
if (array_key_exists($field, $data) && $data[$field] instanceof Carbon) {
$data[$field]->setTimezone(config('app.timezone'));
Log::debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName()));
$set['data'] = $data[$field]->format('Y-m-d H:i:s');

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/*
* WebhookMessageFactory.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Factory;
use FireflyIII\Models\Webhook;
use FireflyIII\Models\WebhookMessage;
use Illuminate\Support\Facades\Log;
class WebhookMessageFactory
{
public function create(Webhook $webhook, array $data): WebhookMessage
{
$webhookMessage = new WebhookMessage();
$webhookMessage->webhook()->associate($webhook);
$webhookMessage->sent = false;
$webhookMessage->errored = false;
$webhookMessage->uuid = $data['uuid'];
$webhookMessage->message = $data;
$webhookMessage->save();
Log::debug(sprintf('Stored new webhook message #%d', $webhookMessage->id));
return $webhookMessage;
}
}

View File

@@ -27,13 +27,13 @@ namespace FireflyIII\Generator\Webhook;
use FireflyIII\Enums\WebhookResponse;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\WebhookMessageFactory;
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;
@@ -254,7 +254,8 @@ class StandardMessageGenerator implements MessageGeneratorInterface
$basicMessage['content'][] = $transformer->transform($account);
}
}
$this->storeMessage($webhook, $basicMessage);
$factory = new WebhookMessageFactory();
$factory->create($webhook, $basicMessage);
}
public function getVersion(): int
@@ -277,18 +278,6 @@ class StandardMessageGenerator implements MessageGeneratorInterface
return $accounts->unique();
}
private function storeMessage(Webhook $webhook, array $message): void
{
$webhookMessage = new WebhookMessage();
$webhookMessage->webhook()->associate($webhook);
$webhookMessage->sent = false;
$webhookMessage->errored = false;
$webhookMessage->uuid = $message['uuid'];
$webhookMessage->message = $message;
$webhookMessage->save();
Log::debug(sprintf('Stored new webhook message #%d', $webhookMessage->id));
}
public function setObjects(Collection $objects): void
{
$this->objects = $objects;

View File

@@ -1,70 +0,0 @@
<?php
/**
* APIEventHandler.php
* Copyright (c) 2019 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;
use Exception;
use FireflyIII\Notifications\User\NewAccessToken;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use Laravel\Passport\Events\AccessTokenCreated;
/**
* Class APIEventHandler
*/
class APIEventHandler
{
/**
* Respond to the creation of an access token.
*/
public function accessTokenCreated(AccessTokenCreated $event): void
{
Log::debug(__METHOD__);
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
$user = $repository->find((int) $event->userId);
if (null !== $user) {
try {
Notification::send($user, new NewAccessToken());
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
}
}

View File

@@ -1,44 +0,0 @@
<?php
/*
* StoredAccountEventHandler.php
* Copyright (c) 2021 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;
use FireflyIII\Events\StoredAccount;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
/**
* Class StoredAccountEventHandler
*/
class StoredAccountEventHandler
{
public function recalculateCredit(StoredAccount $event): void
{
$account = $event->account;
/** @var CreditRecalculateService $object */
$object = app(CreditRecalculateService::class);
$object->setAccount($account);
$object->recalculate();
}
}

View File

@@ -1,187 +0,0 @@
<?php
/**
* StoredGroupEventHandler.php
* Copyright (c) 2019 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;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\Model\TransactionGroup\TriggeredStoredTransactionGroup;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class StoredGroupEventHandler
*
* TODO migrate to observer?
*/
class StoredGroupEventHandler
{
public function runAllHandlers(StoredTransactionGroup $event): void
{
$this->processRules($event, null);
$this->recalculateCredit($event);
$this->triggerWebhooks($event);
$this->removePeriodStatistics($event);
}
public function triggerRulesManually(TriggeredStoredTransactionGroup $event): void
{
$newEvent = new StoredTransactionGroup($event->transactionGroup, true, false);
$this->processRules($newEvent, $event->ruleGroup);
}
/**
* This method grabs all the users rules and processes them.
*/
private function processRules(StoredTransactionGroup $storedGroupEvent, ?RuleGroup $ruleGroup): void
{
if (false === $storedGroupEvent->applyRules) {
Log::info(sprintf('Will not run rules on group #%d', $storedGroupEvent->transactionGroup->id));
return;
}
Log::debug('Now in StoredGroupEventHandler::processRules()');
$journals = $storedGroupEvent->transactionGroup->transactionJournals;
$array = [];
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$array[] = $journal->id;
}
$journalIds = implode(',', $array);
Log::debug(sprintf('Add local operator for journal(s): %s', $journalIds));
// collect rules:
$ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
$ruleGroupRepository->setUser($storedGroupEvent->transactionGroup->user);
// add the groups to the rule engine.
// it should run the rules in the group and cancel the group if necessary.
if (null === $ruleGroup) {
Log::debug('Fire processRules with ALL store-journal rule groups.');
$groups = $ruleGroupRepository->getRuleGroupsWithRules('store-journal');
}
if (null !== $ruleGroup) {
Log::debug(sprintf('Fire processRules with rule group #%d.', $ruleGroup->id));
$groups = new Collection([$ruleGroup]);
}
// create and fire rule engine.
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($storedGroupEvent->transactionGroup->user);
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
$newRuleEngine->setRuleGroups($groups);
$newRuleEngine->fire();
}
private function recalculateCredit(StoredTransactionGroup $event): void
{
$group = $event->transactionGroup;
/** @var CreditRecalculateService $object */
$object = app(CreditRecalculateService::class);
$object->setGroup($group);
$object->recalculate();
}
private function removePeriodStatistics(StoredTransactionGroup $event): void
{
/** @var PeriodStatisticRepositoryInterface $repository */
$repository = app(PeriodStatisticRepositoryInterface::class);
/** @var TransactionJournal $journal */
foreach ($event->transactionGroup->transactionJournals as $journal) {
/** @var null|Transaction $source */
$source = $journal->transactions()->where('amount', '<', '0')->first();
/** @var null|Transaction $dest */
$dest = $journal->transactions()->where('amount', '>', '0')->first();
if (null !== $source) {
$repository->deleteStatisticsForModel($source->account, $journal->date);
}
if (null !== $dest) {
$repository->deleteStatisticsForModel($dest->account, $journal->date);
}
$categories = $journal->categories;
$tags = $journal->tags;
$budgets = $journal->budgets;
foreach ($categories as $category) {
$repository->deleteStatisticsForModel($category, $journal->date);
}
foreach ($tags as $tag) {
$repository->deleteStatisticsForModel($tag, $journal->date);
}
foreach ($budgets as $budget) {
$repository->deleteStatisticsForModel($budget, $journal->date);
}
if (0 === $categories->count()) {
$repository->deleteStatisticsForPrefix($journal->userGroup, 'no_category', $journal->date);
}
if (0 === $budgets->count()) {
$repository->deleteStatisticsForPrefix($journal->userGroup, 'no_budget', $journal->date);
}
}
}
/**
* This method processes all webhooks that respond to the "stored transaction group" trigger (100)
*/
private function triggerWebhooks(StoredTransactionGroup $storedGroupEvent): void
{
Log::debug(__METHOD__);
$group = $storedGroupEvent->transactionGroup;
if (false === $storedGroupEvent->fireWebhooks) {
Log::info(sprintf('Will not fire webhooks for transaction group #%d', $group->id));
return;
}
$user = $group->user;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
// tell the generator which trigger it should look for
$engine->setTrigger(WebhookTrigger::STORE_TRANSACTION);
// tell the generator which objects to process
$engine->setObjects(new Collection()->push($group));
// tell the generator to generate the messages
$engine->generateMessages();
// trigger event to send them:
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
}
}

View File

@@ -1,44 +0,0 @@
<?php
/*
* UpdatedAccountEventHandler.php
* Copyright (c) 2021 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;
use FireflyIII\Events\UpdatedAccount;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
/**
* Class UpdatedAccountEventHandler
*/
class UpdatedAccountEventHandler
{
public function recalculateCredit(UpdatedAccount $event): void
{
$account = $event->account;
/** @var CreditRecalculateService $object */
$object = app(CreditRecalculateService::class);
$object->setAccount($account);
$object->recalculate();
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/*
* ConversionParameters.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Handlers\ExchangeRate;
use Carbon\Carbon;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
class ConversionParameters
{
public User $user;
public Model $model;
public ?TransactionCurrency $originalCurrency = null;
public string $amountField;
public string $primaryAmountField;
public Carbon $date;
public function __construct()
{
$this->date = now();
}
}

View File

@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
/*
* ConvertsAmountToPrimaryAmount.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Handlers\ExchangeRate;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
class ConvertsAmountToPrimaryAmount
{
public static function convert(ConversionParameters $params): void
{
$amountField = $params->amountField;
$primaryAmountField = $params->primaryAmountField;
if (!Amount::convertToPrimary($params->user)) {
Log::debug(sprintf(
'User does not want to do conversion, no need to convert %s and store it in field %s for %s #%d.',
$params->amountField,
$params->primaryAmountField,
get_class($params->model),
$params->model->id
));
$params->model->{$primaryAmountField} = null;
$params->model->saveQuietly();
return;
}
if (null === $params->originalCurrency) {
Log::debug(sprintf(
'Original currency field is empty, no need to convert %s and store it in field %s for %s #%d.',
$params->amountField,
$params->primaryAmountField,
get_class($params->model),
$params->model->id
));
return;
}
$primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($params->user->userGroup);
Log::debug(sprintf(
'Will convert amount in field %s from %s to %s and store it in %s',
$params->originalCurrency->code,
$primaryCurrency->code,
$params->amountField,
$params->primaryAmountField
));
if ($params->originalCurrency->id === $primaryCurrency->id) {
Log::debug('Both currencies are the same, do nothing.');
return;
}
// field is empty or zero, do nothing.
$amount = (string) $params->model->{$amountField};
if ('' === $amount || 0 === bccomp($amount, '0')) {
Log::debug(sprintf('Amount "%s" in field "%s" cannot be used, do nothing.', $amount, $amountField));
$params->model->{$amountField} = null;
$params->model->{$primaryAmountField} = null;
$params->model->saveQuietly();
return;
}
$converter = new ExchangeRateConverter();
$newAmount = $converter->convert($params->originalCurrency, $primaryCurrency, now(), $amount);
$converter->setUserGroup($params->user->userGroup);
$converter->setIgnoreSettings(true);
$params->model->{$primaryAmountField} = $newAmount;
$params->model->saveQuietly();
Log::debug(sprintf(
'Converted field "%s" of %s #%d from %s %s to %s %s (in field "%s")',
$amountField,
get_class($params->model),
$params->model->id,
$params->originalCurrency->code,
$amount,
$primaryCurrency->code,
$newAmount,
$primaryAmountField
));
}
}

View File

@@ -24,9 +24,9 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Handlers\ExchangeRate\ConversionParameters;
use FireflyIII\Handlers\ExchangeRate\ConvertsAmountToPrimaryAmount;
use FireflyIII\Models\AutoBudget;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
class AutoBudgetObserver
@@ -37,26 +37,20 @@ class AutoBudgetObserver
$this->updatePrimaryCurrencyAmount($autoBudget);
}
private function updatePrimaryCurrencyAmount(AutoBudget $autoBudget): void
{
if (!Amount::convertToPrimary($autoBudget->budget->user)) {
return;
}
$userCurrency = Amount::getPrimaryCurrencyByUserGroup($autoBudget->budget->user->userGroup);
$autoBudget->native_amount = null;
if ($autoBudget->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($autoBudget->budget->user->userGroup);
$converter->setIgnoreSettings(true);
$autoBudget->native_amount = $converter->convert($autoBudget->transactionCurrency, $userCurrency, today(), $autoBudget->amount);
}
$autoBudget->saveQuietly();
Log::debug('Auto budget primary currency amount is updated.');
}
public function updated(AutoBudget $autoBudget): void
{
Log::debug('Observe "updated" of an auto budget.');
$this->updatePrimaryCurrencyAmount($autoBudget);
}
private function updatePrimaryCurrencyAmount(AutoBudget $autoBudget): void
{
$params = new ConversionParameters();
$params->user = $autoBudget->budget->user;
$params->model = $autoBudget;
$params->originalCurrency = $autoBudget->transactionCurrency;
$params->amountField = 'amount';
$params->primaryAmountField = 'native_amount';
ConvertsAmountToPrimaryAmount::convert($params);
}
}

View File

@@ -24,41 +24,30 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Handlers\ExchangeRate\ConversionParameters;
use FireflyIII\Handlers\ExchangeRate\ConvertsAmountToPrimaryAmount;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
class AvailableBudgetObserver
{
public function created(AvailableBudget $availableBudget): void
{
// Log::debug('Observe "created" of an available budget.');
$this->updatePrimaryCurrencyAmount($availableBudget);
}
public function updated(AvailableBudget $availableBudget): void
{
$this->updatePrimaryCurrencyAmount($availableBudget);
}
private function updatePrimaryCurrencyAmount(AvailableBudget $availableBudget): void
{
if (!Amount::convertToPrimary($availableBudget->user)) {
// Log::debug('Do not update primary currency available amount of the available budget.');
return;
}
$userCurrency = Amount::getPrimaryCurrencyByUserGroup($availableBudget->user->userGroup);
$availableBudget->native_amount = null;
if ($availableBudget->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($availableBudget->user->userGroup);
$converter->setIgnoreSettings(true);
$availableBudget->native_amount = $converter->convert($availableBudget->transactionCurrency, $userCurrency, today(), $availableBudget->amount);
}
$availableBudget->saveQuietly();
Log::debug('Available budget primary currency amount is updated.');
}
public function updated(AvailableBudget $availableBudget): void
{
// Log::debug('Observe "updated" of an available budget.');
$this->updatePrimaryCurrencyAmount($availableBudget);
$params = new ConversionParameters();
$params->user = $availableBudget->user;
$params->model = $availableBudget;
$params->originalCurrency = $availableBudget->transactionCurrency;
$params->amountField = 'amount';
$params->primaryAmountField = 'native_amount';
ConvertsAmountToPrimaryAmount::convert($params);
}
}

View File

@@ -23,12 +23,11 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Handlers\ExchangeRate\ConversionParameters;
use FireflyIII\Handlers\ExchangeRate\ConvertsAmountToPrimaryAmount;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
/**
* Class BillObserver
@@ -37,35 +36,14 @@ class BillObserver
{
public function created(Bill $bill): void
{
// Log::debug('Observe "created" of a bill.');
$this->updatePrimaryCurrencyAmount($bill);
}
private function updatePrimaryCurrencyAmount(Bill $bill): void
{
if (!Amount::convertToPrimary($bill->user)) {
return;
}
$userCurrency = Amount::getPrimaryCurrencyByUserGroup($bill->user->userGroup);
$bill->native_amount_min = null;
$bill->native_amount_max = null;
if ($bill->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($bill->user->userGroup);
$converter->setIgnoreSettings(true);
$bill->native_amount_min = $converter->convert($bill->transactionCurrency, $userCurrency, today(), $bill->amount_min);
$bill->native_amount_max = $converter->convert($bill->transactionCurrency, $userCurrency, today(), $bill->amount_max);
}
$bill->saveQuietly();
Log::debug('Bill primary currency amounts are updated.');
}
public function deleting(Bill $bill): void
{
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($bill->user);
// Log::debug('Observe "deleting" of a bill.');
/** @var Attachment $attachment */
foreach ($bill->attachments()->get() as $attachment) {
$repository->destroy($attachment);
@@ -78,4 +56,20 @@ class BillObserver
// Log::debug('Observe "updated" of a bill.');
$this->updatePrimaryCurrencyAmount($bill);
}
private function updatePrimaryCurrencyAmount(Bill $bill): void
{
$params = new ConversionParameters();
$params->user = $bill->user;
$params->model = $bill;
$params->originalCurrency = $bill->transactionCurrency;
$params->amountField = 'amount_min';
$params->primaryAmountField = 'native_amount_min';
ConvertsAmountToPrimaryAmount::convert($params);
// and again!
$params->amountField = 'amount_max';
$params->primaryAmountField = 'native_amount_max';
ConvertsAmountToPrimaryAmount::convert($params);
}
}

View File

@@ -25,11 +25,11 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Handlers\ExchangeRate\ConversionParameters;
use FireflyIII\Handlers\ExchangeRate\ConvertsAmountToPrimaryAmount;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Observers\RecalculatesAvailableBudgetsTrait;
use FireflyIII\Support\Singleton\PreferencesSingleton;
use Illuminate\Support\Collection;
@@ -44,42 +44,7 @@ class BudgetLimitObserver
Log::debug('Observe "created" of a budget limit.');
$this->updatePrimaryCurrencyAmount($budgetLimit);
$this->updateAvailableBudget($budgetLimit);
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
if (true === $singleton->getPreference('fire_webhooks_bl_store')) {
$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
{
if (!Amount::convertToPrimary($budgetLimit->budget->user)) {
// Log::debug('Do not update primary currency amount of the budget limit.');
return;
}
$userCurrency = Amount::getPrimaryCurrencyByUserGroup($budgetLimit->budget->user->userGroup);
$budgetLimit->native_amount = null;
if ($budgetLimit->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($budgetLimit->budget->user->userGroup);
$converter->setIgnoreSettings(true);
$budgetLimit->native_amount = $converter->convert($budgetLimit->transactionCurrency, $userCurrency, today(), $budgetLimit->amount);
}
$budgetLimit->saveQuietly();
Log::debug('Budget limit primary currency amounts are updated.');
$this->sendWebhookMessages('fire_webhooks_bl_store', WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT, $budgetLimit);
}
public function updated(BudgetLimit $budgetLimit): void
@@ -87,22 +52,37 @@ class BudgetLimitObserver
Log::debug('Observe "updated" of a budget limit.');
$this->updatePrimaryCurrencyAmount($budgetLimit);
$this->updateAvailableBudget($budgetLimit);
$this->sendWebhookMessages('fire_webhooks_bl_update', WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT, $budgetLimit);
}
private function sendWebhookMessages(string $key, WebhookTrigger $trigger, BudgetLimit $budgetLimit): void
{
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
if (true === $singleton->getPreference('fire_webhooks_bl_update')) {
if (true === $singleton->getPreference($key)) {
$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->setTrigger($trigger);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
}
private function updatePrimaryCurrencyAmount(BudgetLimit $budgetLimit): void
{
$params = new ConversionParameters();
$params->user = $budgetLimit->budget->user;
$params->model = $budgetLimit;
$params->originalCurrency = $budgetLimit->transactionCurrency;
$params->amountField = 'amount';
$params->primaryAmountField = 'native_amount';
ConvertsAmountToPrimaryAmount::convert($params);
}
}

View File

@@ -24,7 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\Budget;
@@ -59,29 +59,8 @@ class BudgetObserver
$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));
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
if (true === $singleton->getPreference('fire_webhooks_budget_update')) {
$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());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
}
@@ -97,8 +76,8 @@ class BudgetObserver
$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());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($budget->user);
@@ -123,4 +102,25 @@ class BudgetObserver
// recalculate available budgets.
}
public function updated(Budget $budget): void
{
Log::debug(sprintf('Observe "updated" of budget #%d ("%s").', $budget->id, $budget->name));
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
if (true === $singleton->getPreference('fire_webhooks_budget_update')) {
$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 WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
}
}

View File

@@ -29,55 +29,12 @@ use FireflyIII\Models\Attachment;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* Class AccountObserver
*/
class AccountObserver
class DeletedAccountObserver
{
public function created(Account $account): void
{
// Log::debug('Observe "created" of an account.');
$this->updatePrimaryCurrencyAmount($account);
}
private function updatePrimaryCurrencyAmount(Account $account): void
{
if (!Amount::convertToPrimary($account->user)) {
return;
}
$userCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
$repository = app(AccountRepositoryInterface::class);
$currency = $repository->getAccountCurrency($account);
if (
null !== $currency
&& $currency->id !== $userCurrency->id
&& '' !== (string) $account->virtual_balance
&& 0 !== bccomp($account->virtual_balance, '0')
) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($account->user->userGroup);
$converter->setIgnoreSettings(true);
$account->native_virtual_balance = $converter->convert($currency, $userCurrency, today(), $account->virtual_balance);
}
if ('' === (string) $account->virtual_balance || 0 === bccomp($account->virtual_balance, '0')) {
$account->virtual_balance = null;
$account->native_virtual_balance = null;
}
$account->saveQuietly();
// Log::debug('Account primary currency virtual balance is updated.');
}
/**
* Also delete related objects.
*/
public function deleting(Account $account): void
{
Log::debug('Observe "deleting" of an account.');
@@ -117,10 +74,4 @@ class AccountObserver
$account->notes()->delete();
$account->locations()->delete();
}
public function updated(Account $account): void
{
// Log::debug('Observe "updated" of an account.');
$this->updatePrimaryCurrencyAmount($account);
}
}

View File

@@ -29,7 +29,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class AttachmentObserver
*/
class AttachmentObserver
class DeletedAttachmentObserver
{
public function deleting(Attachment $attachment): void
{

View File

@@ -31,7 +31,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class CategoryObserver
*/
class CategoryObserver
class DeletedCategoryObserver
{
public function deleting(Category $category): void
{

View File

@@ -31,7 +31,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class RecurrenceObserver
*/
class RecurrenceObserver
class DeletedRecurrenceObserver
{
public function deleting(Recurrence $recurrence): void
{

View File

@@ -29,7 +29,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class RecurrenceTransactionObserver
*/
class RecurrenceTransactionObserver
class DeletedRecurrenceTransactionObserver
{
public function deleting(RecurrenceTransaction $transaction): void
{

View File

@@ -29,7 +29,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class RuleGroupObserver
*/
class RuleGroupObserver
class DeletedRuleGroupObserver
{
public function deleting(RuleGroup $ruleGroup): void
{

View File

@@ -29,7 +29,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class RuleObserver
*/
class RuleObserver
class DeletedRuleObserver
{
public function deleting(Rule $rule): void
{

View File

@@ -31,7 +31,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class TagObserver
*/
class TagObserver
class DeletedTagObserver
{
public function deleting(Tag $tag): void
{

View File

@@ -29,7 +29,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class TransactionGroup
*/
class TransactionGroupObserver
class DeletedTransactionGroupObserver
{
public function deleting(TransactionGroup $transactionGroup): void
{

View File

@@ -33,7 +33,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class TransactionJournalObserver
*/
class TransactionJournalObserver
class DeletedTransactionJournalObserver
{
public function deleting(TransactionJournal $transactionJournal): void
{

View File

@@ -29,7 +29,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class WebhookMessageObserver
*/
class WebhookMessageObserver
class DeletedWebhookMessageObserver
{
public function deleting(WebhookMessage $webhookMessage): void
{

View File

@@ -29,7 +29,7 @@ use Illuminate\Support\Facades\Log;
/**
* Class WebhookObserver
*/
class WebhookObserver
class DeletedWebhookObserver
{
public function deleting(Webhook $webhook): void
{

View File

@@ -24,9 +24,9 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Handlers\ExchangeRate\ConversionParameters;
use FireflyIII\Handlers\ExchangeRate\ConvertsAmountToPrimaryAmount;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
class PiggyBankEventObserver
@@ -37,32 +37,27 @@ class PiggyBankEventObserver
$this->updatePrimaryCurrencyAmount($event);
}
private function updatePrimaryCurrencyAmount(PiggyBankEvent $event): void
{
$user = $event->piggyBank->accounts()->first()?->user;
if (null === $user) {
Log::warning('Piggy bank seems to have no accounts. Break.');
return;
}
if (!Amount::convertToPrimary($user)) {
return;
}
$userCurrency = Amount::getPrimaryCurrencyByUserGroup($event->piggyBank->accounts()->first()->user->userGroup);
$event->native_amount = null;
if ($event->piggyBank->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($event->piggyBank->accounts()->first()->user->userGroup);
$converter->setIgnoreSettings(true);
$event->native_amount = $converter->convert($event->piggyBank->transactionCurrency, $userCurrency, today(), $event->amount);
}
$event->saveQuietly();
Log::debug('Piggy bank event primary currency amount is updated.');
}
public function updated(PiggyBankEvent $event): void
{
Log::debug('Observe "updated" of a piggy bank event.');
$this->updatePrimaryCurrencyAmount($event);
}
private function updatePrimaryCurrencyAmount(PiggyBankEvent $event): void
{
$user = $event->piggyBank->accounts()->first()?->user;
if (null === $user) {
Log::warning('Piggy bank seems to have no accounts. Break.');
return;
}
$params = new ConversionParameters();
$params->user = $user;
$params->model = $event;
$params->originalCurrency = $event->piggyBank->transactionCurrency;
$params->amountField = 'amount';
$params->primaryAmountField = 'native_amount';
ConvertsAmountToPrimaryAmount::convert($params);
}
}

View File

@@ -24,11 +24,11 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Handlers\ExchangeRate\ConversionParameters;
use FireflyIII\Handlers\ExchangeRate\ConvertsAmountToPrimaryAmount;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
/**
@@ -42,26 +42,6 @@ class PiggyBankObserver
$this->updatePrimaryCurrencyAmount($piggyBank);
}
private function updatePrimaryCurrencyAmount(PiggyBank $piggyBank): void
{
$group = $piggyBank->accounts()->first()?->user->userGroup;
if (null === $group) {
Log::debug(sprintf('No account(s) yet for piggy bank #%d.', $piggyBank->id));
return;
}
$userCurrency = Amount::getPrimaryCurrencyByUserGroup($group);
$piggyBank->native_target_amount = null;
if ($piggyBank->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$converter->setUserGroup($group);
$piggyBank->native_target_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $piggyBank->target_amount);
}
$piggyBank->saveQuietly();
Log::debug('Piggy bank primary currency target amount is updated.');
}
/**
* Also delete related objects.
*/
@@ -88,4 +68,22 @@ class PiggyBankObserver
Log::debug('Observe "updated" of a piggy bank.');
$this->updatePrimaryCurrencyAmount($piggyBank);
}
private function updatePrimaryCurrencyAmount(PiggyBank $piggyBank): void
{
$group = $piggyBank->accounts()->first()?->user->userGroup;
if (null === $group) {
Log::debug(sprintf('No account(s) yet for piggy bank #%d.', $piggyBank->id));
return;
}
$params = new ConversionParameters();
$params->user = $piggyBank->accounts()->first()?->user;
$params->model = $piggyBank;
$params->originalCurrency = $piggyBank->transactionCurrency;
$params->amountField = 'target_amount';
$params->primaryAmountField = 'native_target_amount';
ConvertsAmountToPrimaryAmount::convert($params);
}
}

View File

@@ -23,12 +23,9 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Handlers\ExchangeRate\ConversionParameters;
use FireflyIII\Handlers\ExchangeRate\ConvertsAmountToPrimaryAmount;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use Illuminate\Support\Facades\Log;
/**
@@ -40,66 +37,10 @@ class TransactionObserver
public function created(Transaction $transaction): void
{
Log::debug('Observe "created" of a transaction.');
if (
true === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data && (
1 === bccomp($transaction->amount, '0')
&& self::$recalculate
)
) {
Log::debug('Trigger recalculateForJournal');
$journal = $transaction->transactionJournal;
if ($journal instanceof TransactionJournal) {
AccountBalanceCalculator::recalculateForJournal($journal);
}
}
Log::debug(sprintf('Observed creation of Transaction #%d.', $transaction->id));
$this->updatePrimaryCurrencyAmount($transaction);
}
private function updatePrimaryCurrencyAmount(Transaction $transaction): void
{
if (!Amount::convertToPrimary($transaction->transactionJournal->user)) {
return;
}
$userCurrency = Amount::getPrimaryCurrencyByUserGroup($transaction->transactionJournal->user->userGroup);
$transaction->native_amount = null;
$transaction->native_foreign_amount = null;
// first normal amount
if (
$transaction->transactionCurrency->id !== $userCurrency->id
&& (
null === $transaction->foreign_currency_id
|| null !== $transaction->foreign_currency_id
&& $transaction->foreign_currency_id !== $userCurrency->id
)
) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($transaction->transactionJournal->user->userGroup);
$converter->setIgnoreSettings(true);
$transaction->native_amount = $converter->convert(
$transaction->transactionCurrency,
$userCurrency,
$transaction->transactionJournal->date,
$transaction->amount
);
}
// then foreign amount
if ($transaction->foreignCurrency?->id !== $userCurrency->id && null !== $transaction->foreign_amount && null !== $transaction->foreignCurrency) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($transaction->transactionJournal->user->userGroup);
$converter->setIgnoreSettings(true);
$transaction->native_foreign_amount = $converter->convert(
$transaction->foreignCurrency,
$userCurrency,
$transaction->transactionJournal->date,
$transaction->foreign_amount
);
}
$transaction->saveQuietly();
Log::debug(sprintf('Transaction #%d primary currency amounts are updated.', $transaction->id));
}
public function deleting(?Transaction $transaction): void
{
Log::debug('Observe "deleting" of a transaction.');
@@ -108,15 +49,29 @@ class TransactionObserver
public function updated(Transaction $transaction): void
{
// Log::debug('Observe "updated" of a transaction.');
if (
true === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data
&& self::$recalculate
&& 1 === bccomp($transaction->amount, '0')
) {
Log::debug('Trigger recalculateForJournal');
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
}
$this->updatePrimaryCurrencyAmount($transaction);
}
private function updatePrimaryCurrencyAmount(Transaction $transaction): void
{
// convert "amount" to "native_amount"
$params = new ConversionParameters();
$params->user = $transaction->transactionJournal->user;
$params->model = $transaction;
$params->originalCurrency = $transaction->transactionCurrency;
$params->amountField = 'amount';
$params->date = $transaction->transactionJournal->date;
$params->primaryAmountField = 'native_amount';
ConvertsAmountToPrimaryAmount::convert($params);
// convert "foreign_amount" to "native_foreign_amount"
$params = new ConversionParameters();
$params->user = $transaction->transactionJournal->user;
$params->model = $transaction;
$params->originalCurrency = $transaction->foreignCurrency;
$params->date = $transaction->transactionJournal->date;
$params->amountField = 'foreign_amount';
$params->primaryAmountField = 'native_foreign_amount';
ConvertsAmountToPrimaryAmount::convert($params);
}
}

View File

@@ -773,30 +773,22 @@ trait MetaCollection
$this->query->whereNotNull('tag_transaction_journal.tag_id');
// Added this while fixing #10898, not sure why a post filter was ever necessary.
$this->query->whereIn('tag_transaction_journal.tag_id', $tags->pluck('id')->toArray());
// Removed again for #11473 because it breaks multiple tag filters.
// $this->query->whereIn('tag_transaction_journal.tag_id', $tags->pluck('id')->toArray());
// this method adds a "postFilter" to the collector.
$list = $tags->pluck('tag')->toArray();
$list = array_map(strtolower(...), $list);
$filter = static function (array $object) use ($list): bool {
Log::debug(sprintf('Now in setTags(%s) filter', implode(', ', $list)));
Log::debug(sprintf('Now in setTags (any) filter: %s', implode(', ', $list)));
foreach ($object['transactions'] as $transaction) {
$total = count($transaction['tags']);
$matched = 0;
foreach ($transaction['tags'] as $tag) {
Log::debug(sprintf('"%s" versus', strtolower((string) $tag['name'])), $list);
if (in_array(strtolower((string) $tag['name']), $list, true)) {
Log::debug(sprintf('Transaction has tag "%s" so return true.', $tag['name']));
++$matched;
if (1 === count($list)) {
return true;
}
}
}
if (count($list) > 1 && $total === $matched && $matched === count($list)) {
Log::debug(sprintf('All %d searched tags are present.', $total));
return true;
return true;
}
}
}
Log::debug('Transaction has no tags from the list, so return false.');

View File

@@ -433,6 +433,7 @@ class GroupCollector implements GroupCollectorInterface
*/
public function getGroups(): Collection
{
Log::debug('Now in getGroups()');
if ($this->expandGroupSearch) {
// get group ID's for the query:
$groupIds = $this->getCollectedGroupIds();
@@ -440,6 +441,8 @@ class GroupCollector implements GroupCollectorInterface
$this->query->orWhereIn('transaction_journals.transaction_group_id', $groupIds);
}
$result = $this->query->get($this->fields);
// $this->dumpQueryInLogs();
// Log::debug(sprintf('Count of result is %d', $result->count()));
// now to parse this into an array.
$collection = $this->parseArray($result);
@@ -758,6 +761,12 @@ class GroupCollector implements GroupCollectorInterface
count($currentCollection)
));
if (0 === $currentCollection->count()) {
Log::debug('Found nothing anyway, return empty collection.');
return $currentCollection;
}
/**
* @var Closure $function
*/
@@ -775,16 +784,25 @@ class GroupCollector implements GroupCollectorInterface
$result = $function($item);
if (false === $result) {
// skip other filters, continue to next item.
Log::debug('Result is false');
continue;
}
// if the result is a bool, use the unedited results.
if (true === $result) {
Log::debug('Result is true');
$nextCollection->push($item);
continue;
}
// if the result is an array, the filter has changed what's being returned.
if (is_array($result)) {
Log::debug('Result is array');
$nextCollection->push($result);
continue;
}
Log::debug('Result is something else!');
}
$currentCollection = $nextCollection;
Log::debug(sprintf('GroupCollector: postFilterCollection has %d transaction(s) left.', count($currentCollection)));
@@ -827,6 +845,7 @@ class GroupCollector implements GroupCollectorInterface
*/
public function getPaginatedGroups(): LengthAwarePaginator
{
Log::debug('Now in getPaginatedGroups()');
$set = $this->getGroups();
if (0 === $this->limit) {
$this->setLimit(50);

View File

@@ -61,36 +61,38 @@ class ConfigurationController extends Controller
*/
public function index(): Factory|\Illuminate\Contracts\View\View
{
$subTitle = (string) trans('firefly.instance_configuration');
$subTitleIcon = 'fa-wrench';
$subTitle = (string) trans('firefly.instance_configuration');
$subTitleIcon = 'fa-wrench';
Log::channel('audit')->info('User visits admin config index.');
// all available configuration and their default value in case
// they don't exist yet.
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
$siteOwner = config('firefly.site_owner');
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
$siteOwner = config('firefly.site_owner');
$enableExchangeRates = FireflyConfig::get('enable_exchange_rates', config('cer.enabled'))->data;
$useRunningBalance = FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data;
$enableExternalMap = FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
$enableExternalRates = FireflyConfig::get('enable_external_rates', config('cer.download_enabled'))->data;
$allowWebhooks = FireflyConfig::get('allow_webhooks', config('firefly.allow_webhooks'))->data;
$validUrlProtocols = FireflyConfig::get('valid_url_protocols', config('firefly.valid_url_protocols'))->data;
$enableExchangeRates = FireflyConfig::get('enable_exchange_rates', config('cer.enabled'))->data;
$useRunningBalance = FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data;
$enableExternalMap = FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
$enableExternalRates = FireflyConfig::get('enable_external_rates', config('cer.download_enabled'))->data;
$allowWebhooks = FireflyConfig::get('allow_webhooks', config('firefly.allow_webhooks'))->data;
$enableBatchProcessing = FireflyConfig::get('enable_batch_processing', false)->data;
$validUrlProtocols = FireflyConfig::get('valid_url_protocols', config('firefly.valid_url_protocols'))->data;
return view('settings.configuration.index', [
'subTitle' => $subTitle,
'subTitleIcon' => $subTitleIcon,
'singleUserMode' => $singleUserMode,
'isDemoSite' => $isDemoSite,
'siteOwner' => $siteOwner,
'enableExchangeRates' => $enableExchangeRates,
'useRunningBalance' => $useRunningBalance,
'enableExternalMap' => $enableExternalMap,
'enableExternalRates' => $enableExternalRates,
'allowWebhooks' => $allowWebhooks,
'validUrlProtocols' => $validUrlProtocols,
'subTitle' => $subTitle,
'subTitleIcon' => $subTitleIcon,
'singleUserMode' => $singleUserMode,
'isDemoSite' => $isDemoSite,
'siteOwner' => $siteOwner,
'enableExchangeRates' => $enableExchangeRates,
'useRunningBalance' => $useRunningBalance,
'enableExternalMap' => $enableExternalMap,
'enableExternalRates' => $enableExternalRates,
'allowWebhooks' => $allowWebhooks,
'enableBatchProcessing' => $enableBatchProcessing,
'validUrlProtocols' => $validUrlProtocols,
]);
}

View File

@@ -23,7 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\Security\System\NewInvitationCreated;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
@@ -202,7 +202,7 @@ class UserController extends Controller
session()->flash('info', trans('firefly.user_is_invited', ['address' => $address]));
// event!
event(new InvitationCreated($invitee));
event(new NewInvitationCreated($invitee));
return redirect(route('settings.users'));
}

View File

@@ -167,7 +167,7 @@ class LoginController extends Controller
*/
protected function sendFailedLoginResponse(Request $request): void
{
$exception = ValidationException::withMessages([$this->username() => [trans('auth.failed')]]);
$exception = ValidationException::withMessages([$this->username() => [trans('auth.failed')]]);
$exception->redirectTo = route('login');
throw $exception;

View File

@@ -192,7 +192,10 @@ class IndexController extends Controller
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
if ($count > 0) {
$avg = bcdiv(bcadd((string) $bill['amount_min'], (string) $bill['amount_max']), '2');
$avg = bcdiv(
bcadd((string) $bill['amount_min'], (string) $bill['amount_max']),
'2'
);
$avg = bcmul($avg, (string) $count);
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
Log::debug(

View File

@@ -197,7 +197,13 @@ class BudgetLimitController extends Controller
if ($request->expectsJson()) {
$array = $limit->toArray();
// add some extra metadata:
$spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
$spentArr = $this->opsRepository->sumExpenses(
$limit->start_date,
$limit->end_date,
null,
new Collection()->push($budget),
$currency
);
$array['spent'] = $spentArr[$currency->id]['sum'] ?? '0';
$array['left_formatted'] = Amount::formatAnything($limit->transactionCurrency, bcadd($array['spent'], (string) $array['amount']));
$array['amount_formatted'] = Amount::formatAnything($limit->transactionCurrency, $limit['amount']);

View File

@@ -73,7 +73,7 @@ class BillController extends Controller
*/
foreach ($paid as $info) {
$amount = $info['sum'];
$label = (string) trans('firefly.paid_in_currency', ['currency' => $info['name']]);
$label = (string) trans('firefly.paid_in_currency', ['currency' => $info['name']]);
$chartData[$label] = ['amount' => $amount, 'currency_symbol' => $info['symbol'], 'currency_code' => $info['code']];
}
@@ -82,7 +82,7 @@ class BillController extends Controller
*/
foreach ($unpaid as $info) {
$amount = $info['sum'];
$label = (string) trans('firefly.unpaid_in_currency', ['currency' => $info['name']]);
$label = (string) trans('firefly.unpaid_in_currency', ['currency' => $info['name']]);
$chartData[$label] = ['amount' => $amount, 'currency_symbol' => $info['symbol'], 'currency_code' => $info['code']];
}

View File

@@ -539,7 +539,13 @@ class BudgetController extends Controller
}
// get spent amount in this period for this currency.
$sum = $this->opsRepository->sumExpenses($currentStart, $currentEnd, $accounts, new Collection()->push($budget), $currency);
$sum = $this->opsRepository->sumExpenses(
$currentStart,
$currentEnd,
$accounts,
new Collection()->push($budget),
$currency
);
$amount = Steam::positive($sum[$currency->id]['sum'] ?? '0');
$chartData[0]['entries'][$title] = Steam::bcround($amount, $currency->decimal_places);

View File

@@ -76,7 +76,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$budget = $journal['budget_name'] ?? (string) trans('firefly.no_budget');
$title = sprintf('%s (%s)', $budget, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);
@@ -122,7 +126,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$category = $journal['category_name'] ?? (string) trans('firefly.no_category');
$title = sprintf('%s (%s)', $category, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);
@@ -168,7 +176,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$name = $journal['destination_account_name'];
$title = sprintf('%s (%s)', $name, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);
@@ -214,7 +226,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$name = $journal['source_account_name'];
$title = sprintf('%s (%s)', $name, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);

View File

@@ -23,7 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\FireflyConfig;
@@ -152,10 +152,10 @@ abstract class Controller extends BaseController
View::share('original_route_name', Route::currentRouteName());
// lottery to send any remaining webhooks:
if (7 === random_int(1, 10)) {
if (7 === random_int(1, 30)) {
// trigger event to send them:
Log::debug('send event RequestedSendWebhookMessages through lottery');
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
}
View::share('darkMode', $darkMode);

View File

@@ -27,7 +27,7 @@ use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use Exception;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Events\Security\System\SystemRequestedVersionCheck;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Middleware\Installer;
@@ -178,7 +178,7 @@ class HomeController extends Controller
/** @var User $user */
$user = auth()->user();
event(new RequestedVersionCheckStatus($user));
event(new SystemRequestedVersionCheck($user));
return view('index', [
'count' => $count,
@@ -202,7 +202,7 @@ class HomeController extends Controller
/** @var User $user */
$user = auth()->user();
event(new RequestedVersionCheckStatus($user));
event(new SystemRequestedVersionCheck($user));
return view('index', ['subTitle' => $subTitle, 'start' => $start, 'end' => $end, 'pageTitle' => $pageTitle]);
}

View File

@@ -95,7 +95,11 @@ class CategoryController extends Controller
'categories' => [],
];
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']] ??= ['spent' => '0', 'earned' => '0', 'sum' => '0'];
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']] ??= [
'spent' => '0',
'earned' => '0',
'sum' => '0',
];
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]['spent'] = bcadd(
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]['spent'],
(string) $journal['amount']
@@ -122,7 +126,11 @@ class CategoryController extends Controller
'currency_decimal_places' => $currency['currency_decimal_places'],
'categories' => [],
];
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']] ??= ['spent' => '0', 'earned' => '0', 'sum' => '0'];
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']] ??= [
'spent' => '0',
'earned' => '0',
'sum' => '0',
];
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]['earned'] = bcadd(
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]['earned'],
(string) $journal['amount']

View File

@@ -24,7 +24,8 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Transaction;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\UpdatedSingleTransactionGroup;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\BulkEditJournalRequest;
use FireflyIII\Models\TransactionJournal;
@@ -115,10 +116,12 @@ class BulkController extends Controller
}
}
$flags = new TransactionGroupEventFlags();
// run rules on changed journals:
/** @var TransactionJournal $journal */
foreach ($collection as $journal) { // @phpstan-ignore-line
event(new UpdatedTransactionGroup($journal->transactionGroup, true, true, false));
foreach ($collection as $journal) {
event(new UpdatedSingleTransactionGroup($journal->transactionGroup, $flags));
}
Preferences::mark();

View File

@@ -26,7 +26,8 @@ namespace FireflyIII\Http\Controllers\Transaction;
use Exception;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\UpdatedSingleTransactionGroup;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
@@ -305,7 +306,8 @@ class ConvertController extends Controller
$group->refresh();
session()->flash('success', (string) trans('firefly.converted_to_'.$destinationType->type));
event(new UpdatedTransactionGroup($group, true, true, true));
$flags = new TransactionGroupEventFlags();
event(new UpdatedSingleTransactionGroup($group, $flags));
return redirect(route('transactions.show', [$group->id]));
}

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Transaction;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@@ -75,9 +74,6 @@ class CreateController extends Controller
$service = app(GroupCloneService::class);
$newGroup = $service->cloneGroup($group);
// event!
event(new StoredTransactionGroup($newGroup, true, true));
Preferences::mark();
$title = $newGroup->title ?? $newGroup->transactionJournals->first()->description;
@@ -107,29 +103,29 @@ class CreateController extends Controller
{
Preferences::mark();
$sourceId = (int) request()->get('source');
$destinationId = (int) request()->get('destination');
$sourceId = (int) request()->get('source');
$destinationId = (int) request()->get('destination');
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$cash = $accountRepository->getCashAccount();
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
$subTitle = (string) trans(sprintf('breadcrumbs.create_%s', strtolower((string) $objectType)));
$subTitleIcon = 'fa-plus';
$accountRepository = app(AccountRepositoryInterface::class);
$cash = $accountRepository->getCashAccount();
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
$subTitle = (string) trans(sprintf('breadcrumbs.create_%s', strtolower((string) $objectType)));
$subTitleIcon = 'fa-plus';
/** @var null|array $optionalFields */
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$previousUrl = $this->rememberPreviousUrl('transactions.create.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$previousUrl = $this->rememberPreviousUrl('transactions.create.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
if (!is_array($optionalFields)) {
$optionalFields = [];
}
// not really a fan of this, but meh.
$optionalDateFields = [
$optionalDateFields = [
'interest_date' => $optionalFields['interest_date'] ?? false,
'book_date' => $optionalFields['book_date'] ?? false,
'process_date' => $optionalFields['process_date'] ?? false,
@@ -139,13 +135,13 @@ class CreateController extends Controller
];
$optionalFields['external_url'] ??= false;
$optionalFields['location'] ??= false;
$optionalFields['location']
= $optionalFields['location'] && true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
$optionalFields['location'] = $optionalFields['location']
&& true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
// map info:
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
session()->put('preFilled', $preFilled);

View File

@@ -83,29 +83,29 @@ class EditController extends Controller
}
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$expectedSourceTypes = config('firefly.expected_source_types');
$allowedSourceDests = config('firefly.source_dests');
$title = $transactionGroup->transactionJournals()->count() > 1
$repository = app(AccountRepositoryInterface::class);
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$expectedSourceTypes = config('firefly.expected_source_types');
$allowedSourceDests = config('firefly.source_dests');
$title = $transactionGroup->transactionJournals()->count() > 1
? $transactionGroup->title
: $transactionGroup->transactionJournals()->first()->description;
$subTitle = (string) trans('firefly.edit_transaction_title', ['description' => $title]);
$subTitleIcon = 'fa-plus';
$cash = $repository->getCashAccount();
$previousUrl = $this->rememberPreviousUrl('transactions.edit.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
$subTitle = (string) trans('firefly.edit_transaction_title', ['description' => $title]);
$subTitleIcon = 'fa-plus';
$cash = $repository->getCashAccount();
$previousUrl = $this->rememberPreviousUrl('transactions.edit.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
// settings necessary for v2
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
if (!is_array($optionalFields)) {
$optionalFields = [];
}
// not really a fan of this, but meh.
$optionalDateFields = [
$optionalDateFields = [
'interest_date' => $optionalFields['interest_date'] ?? false,
'book_date' => $optionalFields['book_date'] ?? false,
'process_date' => $optionalFields['process_date'] ?? false,
@@ -115,13 +115,13 @@ class EditController extends Controller
];
$optionalFields['external_url'] ??= false;
$optionalFields['location'] ??= false;
$optionalFields['location']
= $optionalFields['location'] && true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
$optionalFields['location'] = $optionalFields['location']
&& true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
// map info voor v2:
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
return view('transactions.edit', [
'cash' => $cash,

View File

@@ -26,7 +26,8 @@ namespace FireflyIII\Http\Controllers\Transaction;
use Carbon\Carbon;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\UpdatedSingleTransactionGroup;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\MassDeleteJournalRequest;
@@ -194,15 +195,15 @@ class MassController extends Controller
*/
private function updateJournal(int $journalId, MassEditJournalRequest $request): void
{
$journal = $this->repository->find($journalId);
$journal = $this->repository->find($journalId);
if (!$journal instanceof TransactionJournal) {
throw new FireflyException(sprintf('Trying to edit non-existent or deleted journal #%d', $journalId));
}
$service = app(JournalUpdateService::class);
$service = app(JournalUpdateService::class);
// for each field, call the update service.
$service->setTransactionJournal($journal);
$data = [
$data = [
'date' => $this->getDateFromRequest($request, $journal->id, 'date'),
'description' => $this->getStringFromRequest($request, $journal->id, 'description'),
'source_id' => $this->getIntFromRequest($request, $journal->id, 'source_id'),
@@ -220,8 +221,10 @@ class MassController extends Controller
$service->setData($data);
$service->update();
// trigger rules
$runRecalculations = $service->isCompareHashChanged();
event(new UpdatedTransactionGroup($journal->transactionGroup, true, true, $runRecalculations));
$runRecalculations = $service->isCompareHashChanged();
$flags = new TransactionGroupEventFlags();
$flags->recalculateCredit = $runRecalculations;
event(new UpdatedSingleTransactionGroup($journal->transactionGroup, $flags));
}
private function getDateFromRequest(MassEditJournalRequest $request, int $journalId, string $key): ?Carbon

View File

@@ -78,7 +78,7 @@ class EditController extends Controller
}
$subTitleIcon = 'fa-pencil';
$subTitle = (string) trans('breadcrumbs.edit_currency', ['name' => $currency->name]);
$subTitle = (string) trans('breadcrumbs.edit_currency', ['name' => $currency->name]);
$currency->symbol = htmlentities($currency->symbol);
// is currently enabled (for this user?)

View File

@@ -23,34 +23,21 @@ declare(strict_types=1);
namespace FireflyIII\Http;
use FireflyIII\Http\Middleware\AcceptHeaders;
use FireflyIII\Http\Middleware\Authenticate;
use FireflyIII\Http\Middleware\Binder;
use FireflyIII\Http\Middleware\EncryptCookies;
use FireflyIII\Http\Middleware\InstallationId;
use FireflyIII\Http\Middleware\Installer;
use FireflyIII\Http\Middleware\InterestingMessage;
use FireflyIII\Http\Middleware\IsAdmin;
use FireflyIII\Http\Middleware\Range;
use FireflyIII\Http\Middleware\RedirectIfAuthenticated;
use FireflyIII\Http\Middleware\SecureHeaders;
use FireflyIII\Http\Middleware\StartFireflySession;
use FireflyIII\Http\Middleware\TrimStrings;
use FireflyIII\Http\Middleware\TrustProxies;
use FireflyIII\Http\Middleware\VerifyCsrfToken;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
use PragmaRX\Google2FALaravel\Middleware as MFAMiddleware;
/**
* Class Kernel
@@ -58,7 +45,7 @@ use PragmaRX\Google2FALaravel\Middleware as MFAMiddleware;
class Kernel extends HttpKernel
{
protected $middleware = [
SecureHeaders::class,
// SecureHeaders::class,
CheckForMaintenanceMode::class,
ValidatePostSize::class,
TrimStrings::class,
@@ -74,102 +61,5 @@ class Kernel extends HttpKernel
'guest' => RedirectIfAuthenticated::class,
'throttle' => ThrottleRequests::class,
];
protected $middlewareGroups = [
// does not check login
// does not check 2fa
// does not check activation
'web' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
AuthenticateSession::class,
CreateFreshApiToken::class,
],
// only the basic variable binders.
'binders-only' => [Installer::class, EncryptCookies::class, AddQueuedCookiesToResponse::class, Binder::class],
// MUST NOT be logged in. Does not care about 2FA or confirmation.
'user-not-logged-in' => [
Installer::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
Binder::class,
RedirectIfAuthenticated::class,
],
// MUST be logged in.
// MUST NOT have 2FA
// don't care about confirmation:
'user-logged-in-no-2fa' => [
Installer::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
Binder::class,
Authenticate::class,
// RedirectIfTwoFactorAuthenticated::class,
],
// MUST be logged in
// don't care about 2fa
// don't care about confirmation.
'user-simple-auth' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
Binder::class,
Authenticate::class,
],
// MUST be logged in
// MUST have 2fa
// MUST be confirmed.
// (this group includes the other Firefly III middleware)
'user-full-auth' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
Authenticate::class,
MFAMiddleware::class,
Range::class,
Binder::class,
InterestingMessage::class,
CreateFreshApiToken::class,
],
// MUST be logged in
// MUST have 2fa
// MUST be confirmed.
// MUST have owner role
// (this group includes the other Firefly III middleware)
'admin' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
Authenticate::class,
// AuthenticateTwoFactor::class,
IsAdmin::class,
Range::class,
Binder::class,
CreateFreshApiToken::class,
],
// full API authentication
'api' => [AcceptHeaders::class, EnsureFrontendRequestsAreStateful::class, 'auth:api,sanctum', 'bindings'],
// do only bindings, no auth
'api_basic' => [AcceptHeaders::class, 'bindings'],
];
protected $middlewarePriority = [StartFireflySession::class, ShareErrorsFromSession::class, Authenticate::class, Binder::class, Authorize::class];
}

View File

@@ -75,7 +75,7 @@ class Binder
*
* @return mixed
*/
private function performBinding(string $key, string $value, Route $route)
private function performBinding(string $key, object|string $value, Route $route)
{
$class = $this->binders[$key];

View File

@@ -45,6 +45,6 @@ class TrustProxies extends Middleware
*/
public function __construct()
{
$this->proxies = (string) config('firefly.trusted_proxies');
$this->proxies = (string) config('trustedproxy.proxies');
}
}

View File

@@ -41,14 +41,15 @@ class ConfigurationRequest extends FormRequest
public function getConfigurationData(): array
{
return [
'single_user_mode' => $this->boolean('single_user_mode'),
'enable_exchange_rates' => $this->boolean('enable_exchange_rates'),
'use_running_balance' => $this->boolean('use_running_balance'),
'enable_external_map' => $this->boolean('enable_external_map'),
'enable_external_rates' => $this->boolean('enable_external_rates'),
'allow_webhooks' => $this->boolean('allow_webhooks'),
'valid_url_protocols' => $this->string('valid_url_protocols'),
'is_demo_site' => $this->boolean('is_demo_site'),
'single_user_mode' => $this->boolean('single_user_mode'),
'enable_exchange_rates' => $this->boolean('enable_exchange_rates'),
'use_running_balance' => $this->boolean('use_running_balance'),
'enable_external_map' => $this->boolean('enable_external_map'),
'enable_external_rates' => $this->boolean('enable_external_rates'),
'allow_webhooks' => $this->boolean('allow_webhooks'),
'valid_url_protocols' => $this->string('valid_url_protocols'),
'is_demo_site' => $this->boolean('is_demo_site'),
'enable_batch_processing' => $this->boolean('enable_batch_processing'),
];
}
@@ -59,14 +60,15 @@ class ConfigurationRequest extends FormRequest
{
// fixed
return [
'single_user_mode' => 'min:0|max:1|numeric',
'enable_exchange_rates' => 'min:0|max:1|numeric',
'use_running_balance' => 'min:0|max:1|numeric',
'enable_external_map' => 'min:0|max:1|numeric',
'enable_external_rates' => 'min:0|max:1|numeric',
'allow_webhooks' => 'min:0|max:1|numeric',
'valid_url_protocols' => 'min:0|max:255',
'is_demo_site' => 'min:0|max:1|numeric',
'single_user_mode' => 'min:0|max:1|numeric',
'enable_exchange_rates' => 'min:0|max:1|numeric',
'use_running_balance' => 'min:0|max:1|numeric',
'enable_external_map' => 'min:0|max:1|numeric',
'enable_external_rates' => 'min:0|max:1|numeric',
'allow_webhooks' => 'min:0|max:1|numeric',
'enable_batch_processing' => 'min:0|max:1|numeric',
'valid_url_protocols' => 'min:0|max:255',
'is_demo_site' => 'min:0|max:1|numeric',
];
}

View File

@@ -25,8 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Events\RequestedReportOnJournals;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupsRequestedReporting;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Recurrence;
@@ -145,7 +144,7 @@ class CreateRecurringTransactions implements ShouldQueue
Log::debug('Now running report thing.');
// will now send email to users.
foreach ($result as $userId => $journals) {
event(new RequestedReportOnJournals($userId, $journals));
event(new TransactionGroupsRequestedReporting($userId, $journals));
}
Log::debug('Done with handle()');
@@ -335,7 +334,7 @@ class CreateRecurringTransactions implements ShouldQueue
Log::debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d')));
// count created journals on THIS day.
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
if ($journalCount > 0 && false === $this->force) {
Log::info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d')));
@@ -353,11 +352,11 @@ class CreateRecurringTransactions implements ShouldQueue
}
// create transaction array and send to factory.
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
// #8844, if there is one recurrence transaction, use the first title as the title.
// #9305, if there is one recurrence transaction, group title must be NULL.
$groupTitle = null;
$groupTitle = null;
// #8844, if there are more, use the recurrence transaction itself.
if ($count > 1) {
@@ -370,7 +369,7 @@ class CreateRecurringTransactions implements ShouldQueue
return null;
}
$array = [
$array = [
'user' => $recurrence->user,
'user_group' => $recurrence->user->userGroup,
'group_title' => $groupTitle,
@@ -378,18 +377,13 @@ class CreateRecurringTransactions implements ShouldQueue
];
/** @var TransactionGroup $group */
$group = $this->groupRepository->store($array);
$group = $this->groupRepository->store($array);
++$this->created;
Log::info(sprintf('Created new transaction group #%d', $group->id));
// trigger event:
event(new StoredTransactionGroup($group, $recurrence->apply_rules, true));
$this->groups->push($group);
// update recurring thing:
$recurrence->latest_date = $date;
$recurrence->latest_date_tz = $date->format('e');
$recurrence->save();
$this->repository->setLatestDate($recurrence, $date);
return $group;
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/*
* TriggersCreditRecalculation.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Model\Account;
use FireflyIII\Events\Model\Account\CreatedNewAccount;
use FireflyIII\Events\Model\Account\UpdatedExistingAccount;
use FireflyIII\Handlers\ExchangeRate\ConversionParameters;
use FireflyIII\Handlers\ExchangeRate\ConvertsAmountToPrimaryAmount;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
class UpdatesAccountInformation implements ShouldQueue
{
public function handle(CreatedNewAccount|UpdatedExistingAccount $event): void
{
$this->recalculateCredit($event->account);
$this->updateVirtualBalance($event->account);
}
private function recalculateCredit(Account $account): void
{
Log::debug('Will call CreditRecalculateService because a new account was created or updated.');
/** @var CreditRecalculateService $object */
$object = app(CreditRecalculateService::class);
$object->setAccount($account);
$object->recalculate();
}
private function updateVirtualBalance(Account $account): void
{
Log::debug('Will updateVirtualBalance');
$repository = app(AccountRepositoryInterface::class);
$currency = $repository->getAccountCurrency($account);
if (null !== $currency) {
// only when the account has a currency, because that is the only way for the
// account to have a virtual balance.
$params = new ConversionParameters();
$params->user = $account->user;
$params->model = $account;
$params->originalCurrency = $currency;
$params->amountField = 'virtual_balance';
$params->primaryAmountField = 'native_virtual_balance';
ConvertsAmountToPrimaryAmount::convert($params);
Log::debug('Account primary currency virtual balance is updated.');
}
}
}

View File

@@ -26,12 +26,11 @@ namespace FireflyIII\Listeners\Model\Rule;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnObject;
use FireflyIII\Notifications\NotificationSender;
use FireflyIII\Notifications\User\RuleActionFailed;
use FireflyIII\Support\Facades\Preferences;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutFailedRuleAction implements ShouldQueue
{
@@ -58,11 +57,6 @@ class NotifiesUserAboutFailedRuleAction implements ShouldQueue
$ruleTitle = $rule->title;
$ruleLink = route('rules.edit', [$rule->id]);
$params = [$mainMessage, $groupTitle, $groupLink, $ruleTitle, $ruleLink];
try {
Notification::send($user, new RuleActionFailed($params));
} catch (ClientException $e) {
Log::error(sprintf('[a] Error sending notification that the rule action failed: %s', $e->getMessage()));
}
NotificationSender::send($user, new RuleActionFailed($params));
}
}

View File

@@ -24,13 +24,12 @@ declare(strict_types=1);
namespace FireflyIII\Listeners\Model\Subscription;
use Exception;
use FireflyIII\Events\Model\Subscription\SubscriptionNeedsExtensionOrRenewal;
use FireflyIII\Notifications\NotificationSender;
use FireflyIII\Notifications\User\BillReminder;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesAboutExtensionOrRenewal implements ShouldQueue
{
@@ -44,24 +43,7 @@ class NotifiesAboutExtensionOrRenewal implements ShouldQueue
if (true === $preference) {
Log::debug('Subscription reminder is true!');
try {
Notification::send($subscription->user, new BillReminder($subscription, $event->field, $event->diff));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
NotificationSender::send($subscription->user, new BillReminder($subscription, $event->field, $event->diff));
return;
}

View File

@@ -24,14 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Listeners\Model\Subscription;
use Exception;
use FireflyIII\Events\Model\Subscription\SubscriptionsAreOverdueForPayment;
use FireflyIII\Models\Bill;
use FireflyIII\Notifications\NotificationSender;
use FireflyIII\Notifications\User\SubscriptionsOverdueReminder;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesAboutOverdueSubscriptions implements ShouldQueue
{
@@ -78,23 +77,6 @@ class NotifiesAboutOverdueSubscriptions implements ShouldQueue
Preferences::setForUser($bill->user, $key, true);
}
Log::warning('should hit this ONCE');
try {
Notification::send($user, new SubscriptionsOverdueReminder($toBeWarned));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
NotificationSender::send($user, new SubscriptionsOverdueReminder($toBeWarned));
}
}

View File

@@ -1,8 +1,10 @@
<?php
/**
* AutomationHandler.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* MailsNewTransactionsReport.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,34 +22,23 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Listeners\Model\TransactionGroup;
namespace FireflyIII\Handlers\Events;
use Exception;
use FireflyIII\Events\RequestedReportOnJournals;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupsRequestedReporting;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Notifications\NotificationSender;
use FireflyIII\Notifications\User\TransactionCreation;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Transformers\TransactionGroupTransformer;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
/**
* Class AutomationHandler
*/
class AutomationHandler
class MailsNewTransactionsReport implements ShouldQueue
{
/**
* Respond to the creation of X journals.
*
* @throws FireflyException
*/
public function reportJournals(RequestedReportOnJournals $event): void
public function handle(TransactionGroupsRequestedReporting $event): void
{
Log::debug('In reportJournals.');
Log::debug('In MailsNewTransactionsReport.');
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
@@ -79,23 +70,7 @@ class AutomationHandler
$groups[] = $transformer->transformObject($group);
}
try {
Notification::send($user, new TransactionCreation($groups));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
if (str_contains($message, 'RFC 2822')) {
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
return;
}
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
NotificationSender::send($user, new TransactionCreation($groups));
Log::debug('If there is no error above this line, message was sent.');
}
}

View File

@@ -1,8 +1,10 @@
<?php
declare(strict_types=1);
/*
* DestroyedGroupEventHandler.php
* Copyright (c) 2021 james@firefly-iii.org
* ProcessesDestroyedTransactionGroup.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,31 +22,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
namespace FireflyIII\Listeners\Model\TransactionGroup;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\DestroyedTransactionGroup;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\TransactionGroup\DestroyedSingleTransactionGroup;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class DestroyedGroupEventHandler
*/
class DestroyedGroupEventHandler
class ProcessesDestroyedTransactionGroup implements ShouldQueue
{
public function runAllHandlers(DestroyedTransactionGroup $event): void
public function handle(DestroyedSingleTransactionGroup $event): void
{
$this->triggerWebhooks($event);
$this->updateRunningBalance($event);
}
private function triggerWebhooks(DestroyedTransactionGroup $destroyedGroupEvent): void
private function triggerWebhooks(DestroyedSingleTransactionGroup $destroyedGroupEvent): void
{
Log::debug('DestroyedTransactionGroup:triggerWebhooks');
$group = $destroyedGroupEvent->transactionGroup;
@@ -56,11 +54,11 @@ class DestroyedGroupEventHandler
$engine->setObjects(new Collection()->push($group));
$engine->setTrigger(WebhookTrigger::DESTROY_TRANSACTION);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
private function updateRunningBalance(DestroyedTransactionGroup $event): void
private function updateRunningBalance(DestroyedSingleTransactionGroup $event): void
{
if (false === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
return;

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
/*
* ProcessesNewTransactionGroup.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Model\TransactionGroup;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\UserRequestedBatchProcessing;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
class ProcessesNewTransactionGroup implements ShouldQueue
{
use SupportsGroupProcessingTrait;
public function handle(CreatedSingleTransactionGroup|UserRequestedBatchProcessing $event): void
{
Log::debug(sprintf('User called %s', get_class($event)));
$setting = FireflyConfig::get('enable_batch_processing', false)->data;
if (true === $event->flags->batchSubmission && true === $setting) {
Log::debug('Will do nothing for event because it is part of a batch.');
return;
}
Log::debug('Will also collect all open transaction groups and process them.');
$repository = app(JournalRepositoryInterface::class);
$journals = $event->objects->transactionJournals->merge($repository->getAllUncompletedJournals());
Log::debug(sprintf('Transaction journal count is %d', $journals->count()));
if (!$event->flags->applyRules) {
Log::debug(sprintf('Will NOT process rules for %d journal(s)', $journals->count()));
}
if (!$event->flags->recalculateCredit) {
Log::debug(sprintf('Will NOT recalculate credit for %d journal(s)', $journals->count()));
}
if (!$event->flags->fireWebhooks) {
Log::debug(sprintf('Will NOT fire webhooks for %d journal(s)', $journals->count()));
}
if ($event->flags->applyRules) {
$this->processRules($journals, 'store-journal');
}
if ($event->flags->recalculateCredit) {
$this->recalculateCredit($event->objects->accounts);
}
if ($event->flags->fireWebhooks) {
$this->fireWebhooks($journals, WebhookTrigger::STORE_TRANSACTION);
}
$this->removePeriodStatistics($event->objects);
$this->recalculateRunningBalance($event->objects);
$repository->markAsCompleted($journals);
}
}

View File

@@ -1,8 +1,10 @@
<?php
/**
* UpdatedGroupEventHandler.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* ProcessesUpdatedTransactionGroup.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -19,19 +21,17 @@
* 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;
namespace FireflyIII\Listeners\Model\TransactionGroup;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\UpdatedSingleTransactionGroup;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
use FireflyIII\Support\Facades\FireflyConfig;
@@ -40,71 +40,31 @@ use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class UpdatedGroupEventHandler
*/
class UpdatedGroupEventHandler
class ProcessesUpdatedTransactionGroup
{
public function runAllHandlers(UpdatedTransactionGroup $event): void
public function handle(UpdatedSingleTransactionGroup $event): void
{
Log::debug('Now in handle() for UpdatedSingleTransactionGroup');
$this->unifyAccounts($event);
$this->processRules($event);
$this->recalculateCredit($event);
$this->triggerWebhooks($event);
$this->removePeriodStatistics($event);
if ($event->runRecalculations) {
$this->updateRunningBalance($event);
}
}
ProcessesNewTransactionGroup::removePeriodStatistics($event->transactionGroup->transactionJournals);
$this->updateRunningBalance($event);
/**
* TODO duplicate
*/
private function removePeriodStatistics(UpdatedTransactionGroup $event): void
{
/** @var PeriodStatisticRepositoryInterface $repository */
$repository = app(PeriodStatisticRepositoryInterface::class);
/** @var TransactionJournal $journal */
foreach ($event->transactionGroup->transactionJournals as $journal) {
$source = $journal->transactions()->where('amount', '<', '0')->first();
$dest = $journal->transactions()->where('amount', '>', '0')->first();
if (null !== $source) {
$repository->deleteStatisticsForModel($source->account, $journal->date);
}
if (null !== $dest) {
$repository->deleteStatisticsForModel($dest->account, $journal->date);
}
$categories = $journal->categories;
$tags = $journal->tags;
$budgets = $journal->budgets;
foreach ($categories as $category) {
$repository->deleteStatisticsForModel($category, $journal->date);
}
foreach ($tags as $tag) {
$repository->deleteStatisticsForModel($tag, $journal->date);
}
foreach ($budgets as $budget) {
$repository->deleteStatisticsForModel($budget, $journal->date);
}
if (0 === $categories->count()) {
$repository->deleteStatisticsForPrefix($journal->userGroup, 'no_category', $journal->date);
}
if (0 === $budgets->count()) {
$repository->deleteStatisticsForPrefix($journal->userGroup, 'no_budget', $journal->date);
}
}
Log::debug('Done with handle() for UpdatedSingleTransactionGroup');
}
/**
* This method will make sure all source / destination accounts are the same.
*/
public function unifyAccounts(UpdatedTransactionGroup $updatedGroupEvent): void
public function unifyAccounts(UpdatedSingleTransactionGroup $updatedGroupEvent): void
{
Log::debug('Now in unifyAccounts()');
$group = $updatedGroupEvent->transactionGroup;
if (1 === $group->transactionJournals->count()) {
Log::debug('Nothing to do in unifyAccounts()');
return;
}
@@ -142,14 +102,16 @@ class UpdatedGroupEventHandler
// set all destination transactions to destination account:
Transaction::whereIn('transaction_journal_id', $all)->where('amount', '>', 0)->update(['account_id' => $destAccount->id]);
}
Log::debug('Done with unifyAccounts()');
}
/**
* This method will check all the rules when a journal is updated.
*/
private function processRules(UpdatedTransactionGroup $updatedGroupEvent): void
private function processRules(UpdatedSingleTransactionGroup $updatedGroupEvent): void
{
if (false === $updatedGroupEvent->applyRules) {
Log::debug('Now in processRules()');
if (false === $updatedGroupEvent->flags->applyRules) {
Log::info(sprintf('Will not run rules on group #%d', $updatedGroupEvent->transactionGroup->id));
return;
@@ -177,23 +139,26 @@ class UpdatedGroupEventHandler
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
$newRuleEngine->setRuleGroups($groups);
$newRuleEngine->fire();
Log::debug('Done with processRules()');
}
private function recalculateCredit(UpdatedTransactionGroup $event): void
private function recalculateCredit(UpdatedSingleTransactionGroup $event): void
{
Log::debug('Now in recalculateCredit()');
$group = $event->transactionGroup;
/** @var CreditRecalculateService $object */
$object = app(CreditRecalculateService::class);
$object->setGroup($group);
$object->recalculate();
Log::debug('Done with recalculateCredit()');
}
private function triggerWebhooks(UpdatedTransactionGroup $updatedGroupEvent): void
private function triggerWebhooks(UpdatedSingleTransactionGroup $updatedGroupEvent): void
{
Log::debug(__METHOD__);
Log::debug('Now in triggerWebhooks()');
$group = $updatedGroupEvent->transactionGroup;
if (false === $updatedGroupEvent->fireWebhooks) {
if (false === $updatedGroupEvent->flags->fireWebhooks) {
Log::info(sprintf('Will not fire webhooks for transaction group #%d', $group->id));
return;
@@ -207,12 +172,14 @@ class UpdatedGroupEventHandler
$engine->setTrigger(WebhookTrigger::UPDATE_TRANSACTION);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
Log::debug('End of triggerWebhooks()');
}
private function updateRunningBalance(UpdatedTransactionGroup $event): void
private function updateRunningBalance(UpdatedSingleTransactionGroup $event): void
{
Log::debug('Now in updateRunningBalance()');
if (false === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
return;
}
@@ -221,5 +188,6 @@ class UpdatedGroupEventHandler
foreach ($group->transactionJournals as $journal) {
AccountBalanceCalculator::recalculateForJournal($journal);
}
Log::debug('Done with updateRunningBalance()');
}
}

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