Compare commits

..

58 Commits

Author SHA1 Message Date
github-actions[bot]
1ac6949f95 Merge pull request #12068 from firefly-iii/release-1775021292
🤖 Automatically merge the PR into the develop branch.
2026-04-01 07:28:18 +02:00
JC5
43acafb7a7 🤖 Auto commit for release 'develop' on 2026-04-01 2026-04-01 07:28:12 +02:00
James Cole
ca85a4c034 Catch group order 2026-04-01 07:23:14 +02:00
github-actions[bot]
22752559e1 Merge pull request #12067 from firefly-iii/release-1775015934
🤖 Automatically merge the PR into the develop branch.
2026-04-01 05:59:03 +02:00
JC5
a341cae6bb 🤖 Auto commit for release 'develop' on 2026-04-01 2026-04-01 05:58:54 +02:00
James Cole
64035a71ea Fix validation for piggy banks. 2026-04-01 05:52:29 +02:00
James Cole
074ca1756d Fix https://github.com/firefly-iii/firefly-iii/issues/12066 2026-04-01 05:47:24 +02:00
James Cole
0aa73ccf96 Fix https://github.com/firefly-iii/firefly-iii/issues/12063 2026-03-31 20:13:49 +02:00
James Cole
85c37d8812 Fix https://github.com/firefly-iii/firefly-iii/issues/12056 2026-03-30 18:21:48 +02:00
github-actions[bot]
16eb2ca4ca Merge pull request #12051 from firefly-iii/release-1774843626
🤖 Automatically merge the PR into the develop branch.
2026-03-30 06:07:15 +02:00
JC5
f491155f9b 🤖 Auto commit for release 'develop' on 2026-03-30 2026-03-30 06:07:06 +02:00
github-actions[bot]
c5706e95b7 Merge pull request #12048 from firefly-iii/release-1774809887
🤖 Automatically merge the PR into the develop branch.
2026-03-29 20:44:53 +02:00
JC5
fdabb2c994 🤖 Auto commit for release 'develop' on 2026-03-29 2026-03-29 20:44:47 +02:00
James Cole
615fa733e6 Add extra debug. 2026-03-29 20:39:23 +02:00
github-actions[bot]
d3fc8673d3 Merge pull request #12047 from firefly-iii/release-1774809181
🤖 Automatically merge the PR into the develop branch.
2026-03-29 20:33:10 +02:00
JC5
db156ffcf2 🤖 Auto commit for release 'develop' on 2026-03-29 2026-03-29 20:33:01 +02:00
James Cole
59510a9acc Catch big group correction that does not need a balance correction. 2026-03-29 20:27:08 +02:00
James Cole
7f9640087e Fix https://github.com/firefly-iii/firefly-iii/issues/12043 2026-03-29 16:53:55 +02:00
github-actions[bot]
29c51ad0e2 Merge pull request #12046 from firefly-iii/release-1774795569
🤖 Automatically merge the PR into the develop branch.
2026-03-29 16:46:17 +02:00
JC5
bf8c40d502 🤖 Auto commit for release 'develop' on 2026-03-29 2026-03-29 16:46:09 +02:00
James Cole
99912483de Add some debug for https://github.com/orgs/firefly-iii/discussions/12044 2026-03-29 16:38:49 +02:00
James Cole
a3f4ab9b1b Validate if amount can be added. 2026-03-27 07:05:16 +01:00
James Cole
953fe7d9eb Fix https://github.com/firefly-iii/firefly-iii/issues/12034 2026-03-27 05:23:22 +01:00
github-actions[bot]
41a7890c7a Merge pull request #12040 from firefly-iii/release-1774582734
🤖 Automatically merge the PR into the develop branch.
2026-03-27 04:39:02 +01:00
JC5
b8e07c1df4 🤖 Auto commit for release 'develop' on 2026-03-27 2026-03-27 04:38:55 +01:00
James Cole
2a3d9001d1 Merge branch 'main' into develop 2026-03-27 04:33:18 +01:00
James Cole
bc7453e204 Update changelog template. 2026-03-27 04:33:03 +01:00
James Cole
8c9ad9da83 Improve running balance correction. 2026-03-27 04:32:41 +01:00
James Cole
c8bd8d5113 Fix https://github.com/firefly-iii/firefly-iii/issues/12035 2026-03-26 20:13:14 +01:00
James Cole
8ce5429e06 Merge pull request #12015 from firefly-iii/dependabot/composer/develop/laravel-notification-channels/pushover-5.0.0
Bump laravel-notification-channels/pushover from 4.1.2 to 5.0.0
2026-03-26 10:36:30 +01:00
James Cole
c1f8fb2f45 Merge pull request #12033 from firefly-iii/dependabot/npm_and_yarn/npm_and_yarn-66413a1f6e 2026-03-26 04:19:12 +01:00
dependabot[bot]
5fb4330c20 Bump picomatch in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [picomatch](https://github.com/micromatch/picomatch).


Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-25 22:06:01 +00:00
James Cole
485eb224d2 Fix https://github.com/firefly-iii/firefly-iii/issues/12030 2026-03-25 20:39:12 +01:00
Sander Dorigo
de7033ee63 Fix #12029 2026-03-25 16:56:46 +01:00
James Cole
4c20547863 Merge branch 'main' into develop 2026-03-24 19:21:16 +01:00
James Cole
3881cd3e39 Fix https://github.com/firefly-iii/firefly-iii/issues/12026 2026-03-24 19:20:36 +01:00
github-actions[bot]
a3e7fa008d Merge pull request #12025 from firefly-iii/release-1774347937
🤖 Automatically merge the PR into the develop branch.
2026-03-24 11:25:46 +01:00
JC5
205b5bd3bf 🤖 Auto commit for release 'develop' on 2026-03-24 2026-03-24 11:25:37 +01:00
Sander Dorigo
21c3dc3f56 Some demo user protections 2026-03-24 10:35:35 +01:00
github-actions[bot]
aa4f5d5a2e Merge pull request #12022 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-03-23 08:33:08 +01:00
github-actions[bot]
9c1f79110c Merge pull request #12021 from firefly-iii/release-1774251172
🤖 Automatically merge the PR into the develop branch.
2026-03-23 08:33:01 +01:00
JC5
bd62957908 🤖 Auto commit for release 'v6.5.9' on 2026-03-23 2026-03-23 08:32:53 +01:00
github-actions[bot]
a7e4252b46 Merge pull request #12020 from firefly-iii/release-1774249889
🤖 Automatically merge the PR into the develop branch.
2026-03-23 08:11:38 +01:00
JC5
c18691d6b3 🤖 Auto commit for release 'develop' on 2026-03-23 2026-03-23 08:11:29 +01:00
github-actions[bot]
b4dbcdcfba Merge pull request #12018 from firefly-iii/release-1774249482
🤖 Automatically merge the PR into the develop branch.
2026-03-23 08:04:52 +01:00
JC5
b0a6acb334 🤖 Auto commit for release 'v6.5.9' on 2026-03-23 2026-03-23 08:04:42 +01:00
github-actions[bot]
a0c423b9ed Merge pull request #12017 from firefly-iii/release-1774238341
🤖 Automatically merge the PR into the develop branch.
2026-03-23 04:59:09 +01:00
JC5
2844929351 🤖 Auto commit for release 'develop' on 2026-03-23 2026-03-23 04:59:01 +01:00
dependabot[bot]
7bfba6a239 Bump laravel-notification-channels/pushover from 4.1.2 to 5.0.0
Bumps [laravel-notification-channels/pushover](https://github.com/laravel-notification-channels/pushover) from 4.1.2 to 5.0.0.
- [Release notes](https://github.com/laravel-notification-channels/pushover/releases)
- [Changelog](https://github.com/laravel-notification-channels/pushover/blob/master/CHANGELOG.md)
- [Commits](https://github.com/laravel-notification-channels/pushover/compare/4.1.2...5.0.0)

---
updated-dependencies:
- dependency-name: laravel-notification-channels/pushover
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 03:52:43 +00:00
James Cole
c0d4a70d46 Update changelog for next release. 2026-03-22 17:10:47 +01:00
James Cole
a62f8bbaff Fix https://github.com/firefly-iii/firefly-iii/issues/12014 2026-03-22 17:05:31 +01:00
James Cole
b115b4ad92 Improve PR template. 2026-03-22 13:38:04 +01:00
github-actions[bot]
dc60025097 Merge pull request #12012 from firefly-iii/release-1774166607
🤖 Automatically merge the PR into the develop branch.
2026-03-22 09:03:34 +01:00
JC5
d77769b2f4 🤖 Auto commit for release 'develop' on 2026-03-22 2026-03-22 09:03:27 +01:00
James Cole
c6497960f8 Try updating package lock/ 2026-03-22 08:58:15 +01:00
James Cole
4eee0c79cd Go to vite plugin 3. 2026-03-22 08:36:43 +01:00
James Cole
e333c3254b Merge pull request #11961 from firefly-iii/dependabot/npm_and_yarn/develop/vite-8.0.0
Bump vite from 7.3.1 to 8.0.0
2026-03-22 07:21:07 +01:00
dependabot[bot]
974c84a877 Bump vite from 7.3.1 to 8.0.0
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 8.0.0.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@8.0.0/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 03:52:51 +00:00
32 changed files with 1114 additions and 1212 deletions

View File

@@ -1264,16 +1264,16 @@
},
{
"name": "symfony/console",
"version": "v8.0.7",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a"
"reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
"reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
"url": "https://api.github.com/repos/symfony/console/zipball/5b66d385dc58f69652e56f78a4184615e3f2b7f7",
"reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7",
"shasum": ""
},
"require": {
@@ -1330,7 +1330,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v8.0.7"
"source": "https://github.com/symfony/console/tree/v8.0.8"
},
"funding": [
{
@@ -1350,7 +1350,7 @@
"type": "tidelift"
}
],
"time": "2026-03-06T14:06:22+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -1421,16 +1421,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v8.0.4",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47"
"reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47",
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f662acc6ab22a3d6d716dcb44c381c6002940df6",
"reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6",
"shasum": ""
},
"require": {
@@ -1482,7 +1482,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.4"
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.8"
},
"funding": [
{
@@ -1502,7 +1502,7 @@
"type": "tidelift"
}
],
"time": "2026-01-05T11:45:55+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -1652,16 +1652,16 @@
},
{
"name": "symfony/finder",
"version": "v8.0.6",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c"
"reference": "8da41214757b87d97f181e3d14a4179286151007"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/441404f09a54de6d1bd6ad219e088cdf4c91f97c",
"reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c",
"url": "https://api.github.com/repos/symfony/finder/zipball/8da41214757b87d97f181e3d14a4179286151007",
"reference": "8da41214757b87d97f181e3d14a4179286151007",
"shasum": ""
},
"require": {
@@ -1696,7 +1696,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.6"
"source": "https://github.com/symfony/finder/tree/v8.0.8"
},
"funding": [
{
@@ -1716,20 +1716,20 @@
"type": "tidelift"
}
],
"time": "2026-01-29T09:41:02+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/options-resolver",
"version": "v8.0.0",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7"
"reference": "b48bce0a70b914f6953dafbd10474df232ed4de8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7",
"reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/b48bce0a70b914f6953dafbd10474df232ed4de8",
"reference": "b48bce0a70b914f6953dafbd10474df232ed4de8",
"shasum": ""
},
"require": {
@@ -1767,7 +1767,7 @@
"options"
],
"support": {
"source": "https://github.com/symfony/options-resolver/tree/v8.0.0"
"source": "https://github.com/symfony/options-resolver/tree/v8.0.8"
},
"funding": [
{
@@ -1787,7 +1787,7 @@
"type": "tidelift"
}
],
"time": "2025-11-12T15:55:31+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -2370,16 +2370,16 @@
},
{
"name": "symfony/process",
"version": "v8.0.5",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674"
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
"url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
"reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc",
"shasum": ""
},
"require": {
@@ -2411,7 +2411,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v8.0.5"
"source": "https://github.com/symfony/process/tree/v8.0.8"
},
"funding": [
{
@@ -2431,7 +2431,7 @@
"type": "tidelift"
}
],
"time": "2026-01-26T15:08:38+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2522,16 +2522,16 @@
},
{
"name": "symfony/stopwatch",
"version": "v8.0.0",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
"reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942"
"reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/67df1914c6ccd2d7b52f70d40cf2aea02159d942",
"reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/85954ed72d5440ea4dc9a10b7e49e01df766ffa3",
"reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3",
"shasum": ""
},
"require": {
@@ -2564,7 +2564,7 @@
"description": "Provides a way to profile code",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/stopwatch/tree/v8.0.0"
"source": "https://github.com/symfony/stopwatch/tree/v8.0.8"
},
"funding": [
{
@@ -2584,20 +2584,20 @@
"type": "tidelift"
}
],
"time": "2025-08-04T07:36:47+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/string",
"version": "v8.0.6",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
"reference": "ae9488f874d7603f9d2dfbf120203882b645d963"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
"reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
"url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963",
"reference": "ae9488f874d7603f9d2dfbf120203882b645d963",
"shasum": ""
},
"require": {
@@ -2654,7 +2654,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v8.0.6"
"source": "https://github.com/symfony/string/tree/v8.0.8"
},
"funding": [
{
@@ -2674,7 +2674,7 @@
"type": "tidelift"
}
],
"time": "2026-02-09T10:14:57+00:00"
"time": "2026-03-30T15:14:47+00:00"
}
],
"packages-dev": [],

View File

@@ -6,6 +6,8 @@
2. If your PR is more than 25 lines, talk to me FIRST.
3. If you fix spelling or code comments, talk to me FIRST.
This is to prevent AI bots, low-effort PRs and spam. Sorry about that.
Wanna talk to me? Open a GitHub Issue, Discussion, or email me: james@firefly-iii.org
👀 Please ensure you have taken a look at the contribution guidelines:
@@ -17,7 +19,9 @@ Remember that your PR may be CLOSED:
2. If you open a PR on the main branch, your PR will be CLOSED.
3. If you only fix a spelling error or code comment, your PR will be CLOSED.
Thanks again, and happy developing!
Again, this is to prevent AI bots, low-effort PRs and spam. I apologize for the harsh tone.
But if you made it this far thanks again for contributing, and happy developing!
-->
@@ -48,3 +52,6 @@ I used AI assistance for:
<!--
Thanks for contributing!
-->
@JC5

View File

@@ -116,6 +116,7 @@ final class PiggyBankController extends Controller
'currency_decimal_places' => $currency->decimal_places,
'object_group_id' => null === $objectGroup ? null : (string) $objectGroup->id,
'object_group_title' => $objectGroup?->title,
'object_group_order' => $objectGroup?->order,
];
}

View File

@@ -30,6 +30,7 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Note;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
@@ -85,6 +86,9 @@ final class PurgeController extends Controller
// rules
Rule::whereUserId($user->id)->onlyTrashed()->forceDelete();
// notes (this will actually purge EVERYBODY's deleted notes)
Note::onlyTrashed()->forceDelete();
// recurring transactions
Recurrence::whereUserId($user->id)->onlyTrashed()->forceDelete();

View File

@@ -28,6 +28,7 @@ use FireflyIII\Models\PiggyBank;
use FireflyIII\Rules\IsValidPositiveAmount;
use FireflyIII\Rules\IsValidZeroOrMoreAmount;
use FireflyIII\Rules\LessThanPiggyTarget;
use FireflyIII\Rules\PiggyBank\IsEnoughInAccounts;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
@@ -84,7 +85,7 @@ class UpdateRequest extends FormRequest
'accounts' => 'array',
'accounts.*' => 'array',
'accounts.*.account_id' => ['required', 'numeric', 'belongsToUser:accounts,id'],
'accounts.*.current_amount' => ['numeric', 'nullable', new IsValidZeroOrMoreAmount(true)],
'accounts.*.current_amount' => ['numeric', 'nullable', new IsValidZeroOrMoreAmount(true), new IsEnoughInAccounts($piggyBank, $this->getAll())],
'object_group_id' => 'numeric|belongsToUser:object_groups,id',
'object_group_title' => ['min:1', 'max:255'],
'transaction_currency_id' => 'exists:transaction_currencies,id|nullable',

View File

@@ -33,6 +33,7 @@ use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CorrectsGroupAccounts extends Command
{
@@ -46,6 +47,7 @@ class CorrectsGroupAccounts extends Command
*/
public function handle(): int
{
Log::debug('Start of correction:group-accounts');
$groups = [];
$res = TransactionJournal::groupBy('transaction_group_id')->get(['transaction_group_id', DB::raw('COUNT(transaction_group_id) as the_count')]);
@@ -59,13 +61,16 @@ class CorrectsGroupAccounts extends Command
$flags->applyRules = false;
$flags->fireWebhooks = false;
$flags->recalculateCredit = true;
$flags->unifyOnly = true;
$objects = new TransactionGroupEventObjects();
foreach ($groups as $groupId) {
$group = TransactionGroup::find($groupId);
$objects->appendFromTransactionGroup($group);
}
Log::debug(sprintf('Fire event for %d transaction group(s)', count($groups)));
event(new UpdatedSingleTransactionGroup($flags, $objects));
event(new WebhookMessagesRequestSending());
Log::debug('End of correction:group-accounts');
return 0;
}

View File

@@ -30,4 +30,5 @@ class TransactionGroupEventFlags
public bool $fireWebhooks = true;
public bool $batchSubmission = false;
public bool $recalculateCredit = true;
public bool $unifyOnly = false;
}

View File

@@ -134,6 +134,13 @@ class PiggyBankFactory
$previous = $toBeLinked[$account->id]['current_amount'] ?? '0';
$diff = bcsub($info['current_amount'], $previous);
// if money is added, check if we can!
if (1 === bccomp($diff, '0') && !$this->piggyBankRepository->canAddAmount($piggyBank, $account, $diff)) {
Log::debug(sprintf('Cannot add amount %s to piggy bank #%d ("%s")', $diff, $piggyBank->id, $piggyBank->name));
continue;
}
// create event for difference.
if (0 !== bccomp($diff, '0')) {
// 2025-10-01 for issue #10990 disable this event.

View File

@@ -172,8 +172,8 @@ final class BudgetLimitController extends Controller
// return empty array:
return response()->json([]);
}
if ((int) $amount > 268_435_456) { // intentional cast to integer
$amount = '268435456';
if ((int) $amount > 2_147_483_647) { // intentional cast to integer
$amount = '2147483647';
}
if (-1 === bccomp($amount, '0')) {
$amount = bcmul($amount, '-1');
@@ -232,8 +232,8 @@ final class BudgetLimitController extends Controller
if ('' === $amount) {
$amount = '0';
}
if ((int) $amount > 268_435_456) { // 268 million, intentional integer
$amount = '268435456';
if ((int) $amount > 2_147_483_647) { // 268 million, intentional integer
$amount = '2147483647';
}
// sanity check on amount:
if (0 === bccomp($amount, '0')) {

View File

@@ -246,14 +246,14 @@ final class PreferencesController extends Controller
$all = $request->only($keys);
foreach (config('notifications.notifications.user') as $key => $info) {
$key = sprintf('notification_%s', $key);
if (array_key_exists($key, $all)) {
if (array_key_exists($key, $all) && false === auth()->user()->hasRole('demo')) {
Log::debug(sprintf('update notification to true: %s', $key));
Preferences::set($key, true);
continue;
}
if (!array_key_exists($key, $all)) {
Log::debug(sprintf('update notification to false: %s', $key));
Preferences::set($key, false);
}
Log::debug(sprintf('update notification to false: %s', $key));
Preferences::set($key, false);
}
unset($all);
@@ -369,6 +369,12 @@ final class PreferencesController extends Controller
$all = $request->only(['channel']);
$channel = $all['channel'] ?? '';
if (true === auth()->user()->hasRole('demo')) {
session()->flash('error', (string) trans('firefly.not_available_demo_user'));
return redirect(route('preferences.index'));
}
switch ($channel) {
default:
session()->flash('error', (string) trans('firefly.notification_test_failed', ['channel' => $channel]));

View File

@@ -170,7 +170,7 @@ final class MassController extends Controller
*/
public function update(MassEditJournalRequest $request): RedirectResponse
{
$journalIds = $request->get('journals');
$journalIds = $request->input('journals');
if (!is_array($journalIds)) {
// TODO this is a weird error, should be caught.
throw new FireflyException('This is not an array.');
@@ -250,6 +250,8 @@ final class MassController extends Controller
private function updateJournal(int $journalId, MassEditJournalRequest $request): void
{
$journal = $this->repository->find($journalId);
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($journal->transactionGroup);
if (!$journal instanceof TransactionJournal) {
throw new FireflyException(sprintf('Trying to edit non-existent or deleted journal #%d', $journalId));
}
@@ -274,8 +276,9 @@ final class MassController extends Controller
// call service to update.
$service->setData($data);
$service->update();
$updated = $service->getTransactionJournal();
$objects->appendFromTransactionGroup($updated->transactionGroup);
$flags = new TransactionGroupEventFlags();
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($journal->transactionGroup);
event(new UpdatedSingleTransactionGroup($flags, $objects));
event(new WebhookMessagesRequestSending());
}

View File

@@ -40,8 +40,8 @@ class ProcessesUpdatedTransactionGroup
public function handle(UpdatedSingleTransactionGroup $event): void
{
Log::debug(sprintf('Now handling event %s', get_class($event)));
$this->unifyAccounts($event);
$effect = $this->unifyAccounts($event);
Log::debug(sprintf('Effect of unifyAccounts = %d', $effect));
Log::debug(sprintf('Transaction journal count is %d', $event->objects->transactionJournals->count()));
if (!$event->flags->applyRules) {
Log::debug(sprintf('Will NOT process rules for %d journal(s)', $event->objects->transactionJournals->count()));
@@ -63,7 +63,13 @@ class ProcessesUpdatedTransactionGroup
$this->createWebhookMessages($event->objects->transactionGroups, WebhookTrigger::UPDATE_TRANSACTION);
}
$this->removePeriodStatistics($event->objects);
$this->recalculateRunningBalance($event->objects);
if (0 === $effect && true === $event->flags->unifyOnly) {
Log::debug('Effect = 0, will not recalculate running balance.');
}
if (0 !== $effect || false === $event->flags->unifyOnly) {
Log::debug(sprintf('Effect is != 0 (%d) OR unifyOnly = false, will recalc running balance', $effect));
$this->recalculateRunningBalance($event->objects);
}
Log::debug('Done with handle() for UpdatedSingleTransactionGroup');
}
@@ -71,23 +77,26 @@ class ProcessesUpdatedTransactionGroup
/**
* This method will make sure all source / destination accounts are the same.
*/
protected function unifyAccounts(UpdatedSingleTransactionGroup $updatedGroupEvent): void
protected function unifyAccounts(UpdatedSingleTransactionGroup $updatedGroupEvent): int
{
Log::debug('Now in unifyAccounts()');
$effect = 0;
/** @var TransactionGroup $group */
foreach ($updatedGroupEvent->objects->transactionGroups as $group) {
$this->unifyAccountsForGroup($group);
$effect += $this->unifyAccountsForGroup($group);
}
Log::debug('Done with unifyAccounts()');
Log::debug(sprintf('Done with unifyAccounts(%d)', $effect));
return $effect;
}
private function unifyAccountsForGroup(TransactionGroup $group): void
private function unifyAccountsForGroup(TransactionGroup $group): int
{
if (1 === $group->transactionJournals->count()) {
Log::debug('Nothing to do in unifyAccounts()');
return;
return 0;
}
// first journal:
@@ -104,7 +113,7 @@ class ProcessesUpdatedTransactionGroup
if (null === $first) {
Log::warning(sprintf('Group #%d has no transaction journals.', $group->id));
return;
return 0;
}
$all = $group->transactionJournals()->get()->pluck('id')->toArray();
@@ -116,13 +125,30 @@ class ProcessesUpdatedTransactionGroup
$destAccount = $first->transactions()->where('amount', '>', '0')->first()->account;
$type = $first->transactionType->type;
$effect = 0;
if (TransactionTypeEnum::TRANSFER->value === $type || TransactionTypeEnum::WITHDRAWAL->value === $type) {
// set all source transactions to source account:
Transaction::whereIn('transaction_journal_id', $all)->where('amount', '<', 0)->update(['account_id' => $sourceAccount->id]);
$effect += Transaction::whereIn('transaction_journal_id', $all)
->where('account_id', '!=', $sourceAccount->id)
->where('amount', '<', 0)
->update(['account_id' => $sourceAccount->id])
;
}
if (TransactionTypeEnum::TRANSFER->value === $type || TransactionTypeEnum::DEPOSIT->value === $type) {
// set all destination transactions to destination account:
Transaction::whereIn('transaction_journal_id', $all)->where('amount', '>', 0)->update(['account_id' => $destAccount->id]);
$effect += Transaction::whereIn('transaction_journal_id', $all)
->where('account_id', '!=', $destAccount->id)
->where('amount', '>', 0)
->update(['account_id' => $destAccount->id])
;
}
if (0 === $effect) {
Log::debug(sprintf('Had nothing to do in unifyAccounts(#%d)', $group->id));
return 0;
}
Log::debug(sprintf('Updated %d transaction(s) in unifyAccounts(#%d)', $effect, $group->id));
return $effect;
}
}

View File

@@ -34,13 +34,13 @@ trait SupportsGroupProcessingTrait
return;
}
$array = $set->pluck('id')->toArray();
$array = array_unique($set->pluck('id')->toArray());
/** @var TransactionJournal $first */
$first = $set->first();
$journalIds = implode(',', $array);
$user = $first->user;
// Log::debug(sprintf('Add local operator for journal(s): %s', $journalIds));
Log::debug(sprintf('Fire rule engine for journal(s): %s', $journalIds));
// collect rules:
$ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
@@ -56,6 +56,7 @@ trait SupportsGroupProcessingTrait
$newRuleEngine->setUser($user);
$newRuleEngine->setRuleGroups($groups);
foreach ($array as $journalId) {
Log::debug(sprintf('Fire rule engine for journal #%d', $journalId));
$newRuleEngine->removeOperator('journal_id');
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalId]);
$newRuleEngine->fire();

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Notifications;
use Exception;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Notifications\Notification;
@@ -36,8 +37,16 @@ class NotificationSender
{
public static function send(OwnerNotifiable|User $user, Notification $notification): void
{
// ::locale($user->locale))
$lang = config('firefly.default_language');
Log::debug(sprintf('Notification send language defaults to "%s"', $lang));
if ($user instanceof User) {
$lang = Preferences::getForUser($user, 'language', $lang)->data;
Log::debug(sprintf('Notification send language set to "%s"', $lang));
}
try {
NotificationFacade::send($user, $notification);
NotificationFacade::locale($lang)->send($user, $notification);
} catch (ClientException $e) {
Log::error(sprintf('[a] Error sending notification: %s', $e->getMessage()));
} catch (Exception $e) {

View File

@@ -48,9 +48,10 @@ class UserTestNotificationEmail extends Notification
public function toMail(User $notifiable): MailMessage
{
$address = (string) $notifiable->email;
$link = route('index');
return new MailMessage()
->markdown('emails.admin-test', ['email' => $address])
->markdown('emails.admin-test', ['email' => $address, 'link' => $link])
->subject((string) trans('email.admin_test_subject'))
;
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/*
* IsEnoughInAccounts.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\Rules\PiggyBank;
use Closure;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use Illuminate\Contracts\Validation\ValidationRule;
use Override;
class IsEnoughInAccounts implements ValidationRule
{
public function __construct(
private readonly PiggyBank $piggyBank,
private readonly array $data
) {}
#[Override]
public function validate(string $attribute, mixed $value, Closure $fail): void
{
// TODO: Implement validate() method.
if (!array_key_exists('accounts', $this->data)) {
return;
}
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
/** @var PiggyBankRepositoryInterface $piggyRepos */
$piggyRepos = app(PiggyBankRepositoryInterface::class);
$accounts = $this->data['accounts'];
foreach ($accounts as $info) {
$account = $repository->find((int) $info['account_id']);
$amount = $info['current_amount'] ?? '0';
if (null === $account) {
$fail('validation.no_asset_account')->translate();
return;
}
if ('' === $amount || 0 === bccomp($amount, '0')) {
continue;
}
$diff = bcsub($amount, $piggyRepos->getCurrentAmount($this->piggyBank, $account));
if (1 === bccomp($diff, '0') && !$piggyRepos->canAddAmount($this->piggyBank, $account, $amount)) {
$fail('validation.cannot_add_piggy_amount')->translate();
}
}
}
}

View File

@@ -225,7 +225,7 @@ class PrimaryAmountRecalculationService
/** @var Account $account */
foreach ($set as $account) {
$currencyId = (int) $account->accountMeta()->where('name', 'currency_id')->first()->data;
$currencyId = (int) $account->accountMeta()->where('name', 'currency_id')->first()?->data;
if ($groupCurrency->id === $currencyId) {
Log::debug(sprintf('Account "%s" is in group currency %s. Skip.', $account->name, $groupCurrency->code));

View File

@@ -111,6 +111,11 @@ class JournalUpdateService
$this->transactionGroupRepository = app(TransactionGroupRepositoryInterface::class);
}
public function getTransactionJournal(): ?TransactionJournal
{
return $this->transactionJournal;
}
public function isCompareHashChanged(): bool
{
Log::debug(sprintf('Now in %s', __METHOD__));
@@ -774,8 +779,8 @@ class JournalUpdateService
$this->transactionJournal,
'update_foreign_amount',
[
'currency_symbol' => $oldForeignCurrency->symbol,
'decimal_places' => $oldForeignCurrency->decimal_places,
'currency_symbol' => $oldForeignCurrency?->symbol,
'decimal_places' => $oldForeignCurrency?->decimal_places,
'amount' => $originalSourceAmount,
],
[

View File

@@ -32,6 +32,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -60,6 +61,7 @@ trait ChartGeneration
}
Log::debug('Regenerate chart.account.account-balance-chart from scratch.');
$locale = Steam::getLocale();
$converter = new ExchangeRateConverter();
/** @var GeneratorInterface $generator */
$generator = app(GeneratorInterface::class);
@@ -76,10 +78,6 @@ trait ChartGeneration
foreach ($accounts as $account) {
Log::debug(sprintf('Now at account #%d ("%s)', $account->id, $account->name));
$currency = $accountRepos->getAccountCurrency($account) ?? $primary;
$usePrimary = $convertToPrimary && $primary->id !== $currency->id;
$field = $convertToPrimary ? 'pc_balance' : 'balance';
$currency = $usePrimary ? $primary : $currency;
Log::debug(sprintf('Will use field %s', $field));
$currentSet = ['label' => $account->name, 'currency_symbol' => $currency->symbol, 'entries' => []];
$currentStart = clone $start;
@@ -90,9 +88,16 @@ trait ChartGeneration
$format = $currentStart->format('Y-m-d');
$label = trim($currentStart->isoFormat((string) trans('config.month_and_day_js', [], $locale)));
$balance = $range[$format] ?? $previous;
$converted = $balance['balance'] ?? '0';
// convert balance if necessary:
if ($convertToPrimary) {
$converted = $converter->convert($currency, $primary, $currentStart, $balance['balance']);
}
$previous = $balance;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance[$field] ?? '0';
$currentSet['entries'][$label] = $converted;
}
$chartData[] = $currentSet;
}

View File

@@ -474,7 +474,10 @@ trait ConvertsDataTypes
if (!array_key_exists('current_amount', $entry)) {
$amount = null;
}
$return[] = ['account_id' => $this->integerFromValue((string) ($entry['account_id'] ?? '0')), 'current_amount' => $amount];
$return[] = [
'account_id' => $this->integerFromValue((string) ($entry['account_id'] ?? '0')),
'current_amount' => $amount,
];
}
return $return;

View File

@@ -72,7 +72,7 @@ trait ValidatesAutoBudgetRequest
$validator->errors()->add('auto_budget_amount', (string) trans('validation.require_currency_info'));
}
// too big amount
if ((int) $amount > 268_435_456) {
if ((int) $amount > 2_147_483_647) {
$validator->errors()->add('auto_budget_amount', (string) trans('validation.amount_required_for_auto_budget'));
}
}

View File

@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## v6.5.9 - 2026-03-23
<!-- summary: Bug fixes mainly, but also updated dependencies and new wording in the instructions you see when you open a PR. -->
### Fixed
- [Issue 12004](https://github.com/firefly-iii/firefly-iii/issues/12004) (Test notification buttons always generate an error) reported by @IDevJoe
- [Issue 12014](https://github.com/firefly-iii/firefly-iii/issues/12014) (Converting a transaction to a transfer and setting the destination account to one with a different currency breaks the audit log) reported by @avee87
# Changed
- [Issue 12000](https://github.com/firefly-iii/firefly-iii/issues/12000) (Improved transaction pagination for large data sets) reported by @christiaanderidder
## v6.5.8 - 2026-03-22
<!-- summary: This release fixes a regression bug in user registration. -->
@@ -2680,7 +2691,7 @@ problems:
## x.x.x - 20xx-xx-xx
<!-- summary: If you can read this I forgot to update the summary! -->
<!-- summary: This release fixes ... If you can read this I forgot to update the summary! -->
### Added

View File

@@ -87,7 +87,7 @@
"guzzlehttp/guzzle": "^7.9",
"jc5/google2fa-laravel": "^2.0",
"jc5/recovery": "^2",
"laravel-notification-channels/pushover": "^4.0",
"laravel-notification-channels/pushover": "^5.0",
"laravel/framework": "^12",
"laravel/passport": "^12.0",
"laravel/slack-notification-channel": "^3.3",

499
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => (bool)envDefaultWhenEmpty(env('USE_RUNNING_BALANCE'), true), // this is only the default value, is not used.
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2026-03-22',
'build_time' => 1774159352,
'version' => 'develop/2026-04-01',
'build_time' => 1775021114,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.

1473
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -81,6 +81,9 @@ export default {
let currentPiggy = res.data[key];
if (null !== currentPiggy.object_group_id) {
let groupOrder = currentPiggy.object_group_order;
if(0 === groupOrder) {
groupOrder = 1;
}
if (!tempList[groupOrder]) {
tempList[groupOrder] = {
group: {

View File

@@ -189,6 +189,6 @@
},
"config": {
"html_language": "ru",
"date_time_fns": "d MMMM yyyy @ HH:mm:ss"
"date_time_fns": "d MMMM yyyy, HH:mm:ss"
}
}

View File

@@ -9,10 +9,10 @@
},
"devDependencies": {
"axios": "^1",
"laravel-vite-plugin": "^2",
"laravel-vite-plugin": "^3",
"patch-package": "^8",
"sass": "^1",
"vite": "^7",
"vite": "^8",
"vite-plugin-manifest-sri": "^0.2.0"
},
"dependencies": {

View File

@@ -70,6 +70,7 @@ return [
'rule_action_value' => 'This value is invalid for the selected action.',
'file_already_attached' => 'Uploaded file ":name" is already attached to this object.',
'file_attached' => 'Successfully uploaded file ":name".',
'cannot_add_piggy_amount' => 'This amount cannot be added to the piggy bank.',
'file_zero' => 'The file is zero bytes in size.',
'must_exist' => 'The ID in field :attribute does not exist in the database.',
'all_accounts_equal' => 'All accounts in this field must be equal.',

View File

@@ -28,8 +28,8 @@
{% if objectType != 'liabilities' %}
<th class="hidden-sm hidden-xs hidden-md">{{ trans('list.lastActivity') }}</th>
{% endif %}
<th class="fifteen"
class="hidden-sm hidden-xs hidden-md">{{ trans('list.balanceDiff') }}</th>
<th
class="fifteen hidden-sm hidden-xs hidden-md">{{ trans('list.balanceDiff') }}</th>
<th class="hidden-sm hidden-xs">&nbsp;</th>
</tr>
</thead>

View File

@@ -282,7 +282,8 @@
{% if transaction.source_account_id == account.id %}
<span title="Deposit, source">{{ formatAmountBySymbol(transaction.source_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}</span>
{% else %}
<span title="Deposit, dest">{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}</span>
{# changed from normal currency_symbol to foreign_currency_symbol for #12043 #}
<span title="Deposit, dest">{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }}</span>
{% endif %}
{% elseif transaction.transaction_type_type == 'Withdrawal' %}