Compare commits

..

26 Commits

Author SHA1 Message Date
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
117 changed files with 1315 additions and 584 deletions

View File

@@ -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.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12"
"reference": "42e48eb02e07d5f3771d194d67da117eb824c8c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/dd3a2953570a283a2ba4e17063bb98c734cf5b12",
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12",
"url": "https://api.github.com/repos/symfony/finder/zipball/42e48eb02e07d5f3771d194d67da117eb824c8c1",
"reference": "42e48eb02e07d5f3771d194d67da117eb824c8c1",
"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.4"
},
"funding": [
{
@@ -1704,7 +1704,7 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:52:06+00:00"
"time": "2026-01-12T12:37:40+00:00"
},
{
"name": "symfony/options-resolver",
@@ -2358,16 +2358,16 @@
},
{
"name": "symfony/process",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "0cbbd88ec836f8757641c651bb995335846abb78"
"reference": "10df72602d88c0a3fa685b822976a052611dd607"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78",
"reference": "0cbbd88ec836f8757641c651bb995335846abb78",
"url": "https://api.github.com/repos/symfony/process/zipball/10df72602d88c0a3fa685b822976a052611dd607",
"reference": "10df72602d88c0a3fa685b822976a052611dd607",
"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.4"
},
"funding": [
{
@@ -2419,7 +2419,7 @@
"type": "tidelift"
}
],
"time": "2025-12-19T10:01:18+00:00"
"time": "2026-01-23T11:07:10+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,7 @@ 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
- 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

@@ -27,7 +27,8 @@ 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\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
@@ -87,9 +88,9 @@ class StoreController extends Controller
public function store(StoreRequest $request): JsonResponse
{
Log::debug('Now in API StoreController::store()');
$data = $request->getAll();
$data['user'] = auth()->user();
$data['user_group'] = $this->userGroup;
$data = $request->getAll();
$data['user'] = auth()->user();
$data['user_group'] = $this->userGroup;
Log::channel('audit')->info('Store new transaction over API.', $data);
@@ -109,18 +110,21 @@ 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));
$flags = new TransactionGroupEventFlags();
$flags->applyRules = $data['apply_rules'] ?? true;
$flags->fireWebhooks = $data['fire_webhooks'] ?? true;
$flags->batchSubmission = $data['batch_submission'] ?? false;
Log::debug('CreatedSingleTransactionGroup');
event(new CreatedSingleTransactionGroup($transactionGroup, $flags));
$manager = $this->getManager();
$manager = $this->getManager();
/** @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)
->setUserGroup($this->userGroup)
@@ -130,20 +134,20 @@ class StoreController extends Controller
->withAPIInformation()
;
$selectedGroup = $collector->getGroups()->first();
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw HttpException::fromStatusCode(410, '200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
}
// 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

@@ -82,6 +82,9 @@ class UpdateController extends Controller
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
$runRecalculations = $oldHash !== $newHash;
// FIXME responds to a single event.
// flags in array?
event(new UpdatedTransactionGroup($transactionGroup, $applyRules, $fireWebhooks, $runRecalculations));
/** @var User $admin */

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

@@ -57,6 +57,7 @@ class CorrectsGroupAccounts extends Command
foreach ($groups as $groupId) {
$group = TransactionGroup::find($groupId);
// TODO in theory the "unifyAccounts" method could lead to the need for run recalculations.
// FIXME needs to be a collection.
$event = new UpdatedTransactionGroup($group, true, true, false);
$handler->unifyAccounts($event);
}

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

@@ -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

@@ -0,0 +1,45 @@
<?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 FireflyIII\Models\TransactionGroup;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class CreatedSingleTransactionGroup extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public TransactionGroup $transactionGroup,
public TransactionGroupEventFlags $flags
) {
Log::debug(__METHOD__);
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* CreatedTransactionGroupBatch.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 CreatedTransactionGroupInBatch extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public Collection $collection,
public array $flags
) {}
}

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,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

@@ -50,7 +50,6 @@ use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
use FireflyIII\Validation\AccountValidator;
use Illuminate\Support\Collection;
@@ -110,22 +109,24 @@ class TransactionJournalFactory
{
Log::debug('Now in TransactionJournalFactory::create()');
// convert to special object.
$dataObject = new NullArrayObject($data);
// $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($row);
if ($journal instanceof TransactionJournal) {
$collection->push($journal);
}
@@ -161,7 +162,7 @@ class TransactionJournalFactory
*
* @SuppressWarnings("PHPMD.ExcessiveMethodLength")
*/
private function createJournal(NullArrayObject $row): ?TransactionJournal
private function createJournal(array $row): ?TransactionJournal
{
Log::debug('Now in TransactionJournalFactory::createJournal()');
$row['import_hash_v2'] = $this->hashArray($row);
@@ -273,7 +274,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 +332,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);
@@ -344,20 +345,18 @@ class TransactionJournalFactory
return $journal;
}
private function hashArray(NullArrayObject $row): string
private function hashArray(array $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);
return $hash;
}
@@ -399,7 +398,7 @@ class TransactionJournalFactory
/**
* @throws FireflyException
*/
private function validateAccounts(NullArrayObject $data): void
private function validateAccounts(array $data): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$transactionType = $data['type'] ?? 'invalid';
@@ -577,7 +576,7 @@ class TransactionJournalFactory
/**
* Link a piggy bank to this journal.
*/
private function storePiggyEvent(TransactionJournal $journal, NullArrayObject $data): void
private function storePiggyEvent(TransactionJournal $journal, array $data): void
{
Log::debug('Will now store piggy event.');
@@ -592,17 +591,17 @@ class TransactionJournalFactory
Log::debug('Create no piggy event');
}
private function storeMetaFields(TransactionJournal $journal, NullArrayObject $transaction): void
private function storeMetaFields(TransactionJournal $journal, array $transaction): void
{
foreach ($this->fields as $field) {
$this->storeMeta($journal, $transaction, $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');
@@ -615,7 +614,7 @@ class TransactionJournalFactory
$factory->updateOrCreate($set);
}
private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void
private function storeLocation(TransactionJournal $journal, array $data): void
{
if (!in_array(null, [$data['longitude'], $data['latitude'], $data['zoom_level']], true)) {
$location = new Location();

View File

@@ -47,16 +47,16 @@ class StoredGroupEventHandler
{
public function runAllHandlers(StoredTransactionGroup $event): void
{
$this->processRules($event, null);
$this->recalculateCredit($event);
$this->triggerWebhooks($event);
// $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);
// $newEvent = new StoredTransactionGroup($event->transactionGroup, true, false);
// $this->processRules($newEvent, $event->ruleGroup);
}
/**

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
@@ -40,19 +39,8 @@ 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);
}
}
return;
$this->updatePrimaryCurrencyAmount($transaction);
}

View File

@@ -441,8 +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()));
// $this->dumpQueryInLogs();
// Log::debug(sprintf('Count of result is %d', $result->count()));
// now to parse this into an array.
$collection = $this->parseArray($result);

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

@@ -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\StoredTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@@ -76,7 +77,9 @@ class CreateController extends Controller
$newGroup = $service->cloneGroup($group);
// event!
event(new StoredTransactionGroup($newGroup, true, true));
$flags = new TransactionGroupEventFlags();
event(new CreatedSingleTransactionGroup($group, $flags));
// event(new StoredTransactionGroup($newGroup, true, true));
Preferences::mark();
@@ -107,29 +110,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 +142,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

@@ -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

@@ -25,8 +25,9 @@ declare(strict_types=1);
namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupsRequestedReporting;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Recurrence;
@@ -383,7 +384,10 @@ class CreateRecurringTransactions implements ShouldQueue
Log::info(sprintf('Created new transaction group #%d', $group->id));
// trigger event:
event(new StoredTransactionGroup($group, $recurrence->apply_rules, true));
$flags = new TransactionGroupEventFlags();
$flags->applyRules = $recurrence->apply_rules;
event(new CreatedSingleTransactionGroup($group, $flags));
// event(new StoredTransactionGroup($group, $recurrence->apply_rules, true));
$this->groups->push($group);
// update recurring thing:

View File

@@ -31,10 +31,11 @@ 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 MailsNewTransactionsReport
class MailsNewTransactionsReport implements ShouldQueue
{
public function handle(TransactionGroupsRequestedReporting $event): void
{

View File

@@ -0,0 +1,193 @@
<?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 Carbon\Carbon;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournalMeta;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class ProcessesNewTransactionGroup implements ShouldQueue
{
public function handle(CreatedSingleTransactionGroup $event): void
{
Log::debug(sprintf('In ProcessesNewTransactionGroup::handle(#%d)', $event->transactionGroup->id));
if (true === $event->flags->batchSubmission) {
Log::debug(sprintf('Will do nothing for group #%d because it is part of a batch.', $event->transactionGroup->id));
return;
}
Log::debug(sprintf('Will join group #%d with all other open transaction groups and process them.', $event->transactionGroup->id));
$collection = $event->transactionGroup->transactionJournals;
$repository = app(JournalRepositoryInterface::class);
$set = $collection->merge($repository->getUncompletedJournals());
if (0 === $set->count()) {
Log::debug('Set is empty, never mind.');
return;
}
if (!$event->flags->applyRules) {
Log::debug(sprintf('Will NOT process rules for %d journal(s)', $set->count()));
}
if (!$event->flags->recalculateCredit) {
Log::debug(sprintf('Will NOT recalculate credit for %d journal(s)', $set->count()));
}
if (!$event->flags->fireWebhooks) {
Log::debug(sprintf('Will NOT fire webhooks for %d journal(s)', $set->count()));
}
if ($event->flags->applyRules) {
$this->processRules($set);
}
if ($event->flags->recalculateCredit) {
$this->recalculateCredit($set);
}
if ($event->flags->fireWebhooks) {
$this->fireWebhooks($set);
}
// always remove old statistics.
$this->removePeriodStatistics($set);
// recalculate running balance if necessary.
Log::debug('Observe "created" of a transaction.');
if (true === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
$this->recalculateRunningBalance($set);
}
$repository->markAsCompleted($set);
}
private function recalculateRunningBalance(Collection $set): void
{
Log::debug('Now in recalculateRunningBalance');
// find the earliest date in the set, based on date and _internal_previous_date
$earliest = $set->pluck('date')->sort()->first();
$entries = TransactionJournalMeta::whereIn('transaction_journal_id', $set->pluck('id')->toArray())->where('name', '_internal_previous_date')->get([
'journal_meta.*',
]);
$array = $entries->toArray();
if (count($array) > 0) {
usort($array, function (array $a, array $b) {
return Carbon::parse($a['data'])->gt(Carbon::parse($b['data']));
});
/** @var Carbon $date */
$date = Carbon::parse($array[0]['data']);
$earliest = $date->lt($earliest) ? $date : $earliest;
}
// get accounts
$accounts = Account::leftJoin('transactions', 'transactions.account_id', 'accounts.id')
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
->whereIn('transaction_journals.id', $set->pluck('id')->toArray())
->get(['accounts.*'])
;
AccountBalanceCalculator::optimizedCalculation($accounts, $earliest);
}
private function removePeriodStatistics(Collection $set): void
{
Log::debug('Always remove period statistics');
/** @var PeriodStatisticRepositoryInterface $repository */
$repository = app(PeriodStatisticRepositoryInterface::class);
$repository->deleteStatisticsForCollection($set);
}
private function fireWebhooks(Collection $set): void
{
// collect transaction groups by set ids.
$groups = TransactionGroup::whereIn('id', array_unique($set->pluck('transaction_group_id')->toArray()))->get();
Log::debug(__METHOD__);
$user = $set->first()->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($groups);
// tell the generator to generate the messages
$engine->generateMessages();
// trigger event to send them:
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
private function recalculateCredit(Collection $set): void
{
Log::debug(sprintf('Will now recalculateCredit for %d journal(s)', $set->count()));
/** @var CreditRecalculateService $object */
$object = app(CreditRecalculateService::class);
$object->setJournals($set);
$object->recalculate();
}
private function processRules(Collection $set): void
{
Log::debug(sprintf('Will now processRules for %d journal(s)', $set->count()));
$array = $set->pluck('id')->toArray();
$journalIds = implode(',', $array);
$user = $set->first()->user;
Log::debug(sprintf('Add local operator for journal(s): %s', $journalIds));
// collect rules:
$ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
$ruleGroupRepository->setUser($user);
// add the groups to the rule engine.
// it should run the rules in the group and cancel the group if necessary.
Log::debug('Fire processRules with ALL store-journal rule groups.');
$groups = $ruleGroupRepository->getRuleGroupsWithRules('store-journal');
// create and fire rule engine.
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($user);
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
$newRuleEngine->setRuleGroups($groups);
$newRuleEngine->fire();
}
}

View File

@@ -27,9 +27,10 @@ namespace FireflyIII\Listeners\Model\TransactionGroup;
use Carbon\Carbon;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupRequestsAuditLogEntry;
use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
class StoresAuditLogEntry
class StoresAuditLogEntry implements ShouldQueue
{
public function handle(TransactionGroupRequestsAuditLogEntry $event): void
{

View File

@@ -28,9 +28,10 @@ use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Jobs\SendWebhookMessage;
use FireflyIII\Models\WebhookMessage;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
class SendsWebhookMessages
class SendsWebhookMessages implements ShouldQueue
{
public function handle(WebhookMessagesRequestSending $event): void
{

View File

@@ -31,11 +31,12 @@ use FireflyIII\Helpers\Update\UpdateTrait;
use FireflyIII\Models\Configuration;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class ChecksForNewVersion
class ChecksForNewVersion implements ShouldQueue
{
use UpdateTrait;

View File

@@ -38,10 +38,11 @@ use FireflyIII\Notifications\User\UserRegistration as UserRegistrationNotificati
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\User;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class HandlesNewUserRegistration
class HandlesNewUserRegistration implements ShouldQueue
{
public function handle(NewUserRegistered $event): void
{

View File

@@ -1,8 +1,10 @@
<?php
/**
* AdminEventHandler.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* NotifiesAboutNewInvitation.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -19,24 +21,47 @@
* 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\Security\System;
use Exception;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\Security\System\NewInvitationCreated;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Mail\InvitationMail;
use FireflyIII\Models\InvitedUser;
use FireflyIII\Notifications\Admin\UserInvitation;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
/**
* Class AdminEventHandler.
*/
class AdminEventHandler
class NotifiesAboutNewInvitation implements ShouldQueue
{
public function sendInvitationNotification(InvitationCreated $event): void
public function handle(NewInvitationCreated $event): void
{
$this->sendInvitationNotification($event->invitee);
$this->sendRegistrationInvite($event->invitee);
}
private function sendRegistrationInvite(InvitedUser $invitee): void
{
$email = $invitee->email;
$admin = $invitee->user->email;
$url = route('invite', [$invitee->invite_code]);
try {
Mail::to($email)->send(new InvitationMail($invitee, $admin, $url));
} catch (Exception $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage(), 0, $e);
}
}
private function sendInvitationNotification(InvitedUser $invitee): void
{
$sendMail = FireflyConfig::get('notification_invite_created', true)->data;
if (false === $sendMail) {
@@ -44,7 +69,7 @@ class AdminEventHandler
}
try {
Notification::send(new OwnerNotifiable(), new UserInvitation($event->invitee));
Notification::send(new OwnerNotifiable(), new UserInvitation($invitee));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
@@ -61,6 +86,4 @@ class AdminEventHandler
Log::error($e->getTraceAsString());
}
}
// Send new version message to admin.
}

View File

@@ -29,10 +29,11 @@ use FireflyIII\Events\Security\System\SystemFoundNewVersionOnline;
use FireflyIII\Notifications\Admin\VersionCheckResult;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesOwnerAboutNewVersion
class NotifiesOwnerAboutNewVersion implements ShouldQueue
{
public function handle(SystemFoundNewVersionOnline $event): void
{

View File

@@ -28,10 +28,11 @@ use Exception;
use FireflyIII\Events\Security\System\UnknownUserTriedLogin;
use FireflyIII\Notifications\Admin\UnknownUserLoginAttempt;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesOwnerAboutUnknownUser
class NotifiesOwnerAboutUnknownUser implements ShouldQueue
{
public function handle(UnknownUserTriedLogin $event): void
{

View File

@@ -30,10 +30,11 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Mail\ConfirmEmailChangeMail;
use FireflyIII\Mail\UndoEmailChangeMail;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
class HandlesChangeOfUserEmailAddress
class HandlesChangeOfUserEmailAddress implements ShouldQueue
{
public function handle(UserChangedEmailAddress $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasEnabledMFA;
use FireflyIII\Notifications\Security\EnabledMFANotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutEnabledMFA
class NotifiesUserAboutEnabledMFA implements ShouldQueue
{
public function handle(UserHasEnabledMFA $event): void
{

View File

@@ -26,10 +26,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserFailedLoginAttempt;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutFailedLogin
class NotifiesUserAboutFailedLogin implements ShouldQueue
{
public function handle(UserFailedLoginAttempt $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasFewMFABackupCodesLeft;
use FireflyIII\Notifications\Security\MFABackupFewLeftNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutFewCodesLeft
class NotifiesUserAboutFewCodesLeft implements ShouldQueue
{
public function handle(UserHasFewMFABackupCodesLeft $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasGeneratedNewBackupCodes;
use FireflyIII\Notifications\Security\NewBackupCodesNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutNewBackupCodes
class NotifiesUserAboutNewBackupCodes implements ShouldQueue
{
public function handle(UserHasGeneratedNewBackupCodes $event): void
{

View File

@@ -28,10 +28,11 @@ use Exception;
use FireflyIII\Events\Security\User\UserLoggedInFromNewIpAddress;
use FireflyIII\Notifications\User\UserLogin;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutNewIpAddress
class NotifiesUserAboutNewIpAddress implements ShouldQueue
{
public function handle(UserLoggedInFromNewIpAddress $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasNoMFABackupCodesLeft;
use FireflyIII\Notifications\Security\MFABackupNoLeftNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutNoCodesLeft
class NotifiesUserAboutNoCodesLeft implements ShouldQueue
{
public function handle(UserHasNoMFABackupCodesLeft $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserKeepsFailingMFA;
use FireflyIII\Notifications\Security\MFAManyFailedAttemptsNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutRepeatedMFAFailures
class NotifiesUserAboutRepeatedMFAFailures implements ShouldQueue
{
public function handle(UserKeepsFailingMFA $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasUsedBackupCode;
use FireflyIII\Notifications\Security\MFAUsedBackupCodeNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutUsedBackupCode
class NotifiesUserAboutUsedBackupCode implements ShouldQueue
{
public function handle(UserHasUsedBackupCode $event): void
{

View File

@@ -1,8 +1,10 @@
<?php
/**
* UserEventHandler.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* RespondsToNewLogin.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -19,43 +21,40 @@
* 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\Security\User;
use Exception;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Mail\InvitationMail;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User;
use Illuminate\Auth\Events\Login;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use InvalidArgumentException;
/**
* Class UserEventHandler.
*
* This class responds to any events that have anything to do with the User object.
*
* The method name reflects what is being done. This is in the present tense.
*/
class UserEventHandler
class RespondsToNewLogin implements ShouldQueue
{
public function handle(Login $event): void
{
$user = $event->user;
if (!$user instanceof User) {
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
$this->checkSingleUserIsAdmin($user);
$this->demoUserBackToEnglish($user);
}
/**
* Fires to see if a user is admin.
*/
public function checkSingleUserIsAdmin(Login $event): void
private function checkSingleUserIsAdmin(User $user): void
{
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
/** @var User $user */
$user = $event->user;
$count = $repository->count();
// only act when there is 1 user in the system and he has no admin rights.
// only act when there is 1 user in the system, and he has no admin rights.
if (1 === $count && !$repository->hasRole($user, 'owner')) {
// user is the only user but does not have role "owner".
$role = $repository->getRole('owner');
@@ -74,13 +73,10 @@ class UserEventHandler
/**
* Set the demo user back to English.
*/
public function demoUserBackToEnglish(Login $event): void
private function demoUserBackToEnglish(User $user): void
{
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
/** @var User $user */
$user = $event->user;
if ($repository->hasRole($user, 'demo')) {
// set user back to English.
Preferences::setForUser($user, 'language', 'en_US');
@@ -89,23 +85,4 @@ class UserEventHandler
Preferences::mark();
}
}
/**
* @throws FireflyException
*/
public function sendRegistrationInvite(InvitationCreated $event): void
{
$invitee = $event->invitee->email;
$admin = $event->invitee->user->email;
$url = route('invite', [$event->invitee->invite_code]);
try {
Mail::to($invitee)->send(new InvitationMail($invitee, $admin, $url));
} catch (Exception $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage(), 0, $e);
}
}
}

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserRequestedNewPassword;
use FireflyIII\Notifications\User\UserNewPassword;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class SendsUserNewPassword
class SendsUserNewPassword implements ShouldQueue
{
public function handle(UserRequestedNewPassword $event): void
{

View File

@@ -28,9 +28,10 @@ use Carbon\Carbon;
use FireflyIII\Events\Security\User\UserLoggedInFromNewIpAddress;
use FireflyIII\Events\Security\User\UserSuccessfullyLoggedIn;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
class StoresNewIpAddress
class StoresNewIpAddress implements ShouldQueue
{
public function handle(UserSuccessfullyLoggedIn $event): void
{

View File

@@ -60,8 +60,11 @@ class Account extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$accountId = (int) $value;

View File

@@ -62,8 +62,11 @@ class Attachment extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$attachmentId = (int) $value;

View File

@@ -59,8 +59,11 @@ class AvailableBudget extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$availableBudgetId = (int) $value;

View File

@@ -74,8 +74,11 @@ class Bill extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$billId = (int) $value;

View File

@@ -53,8 +53,11 @@ class Budget extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$budgetId = (int) $value;

View File

@@ -53,8 +53,11 @@ class BudgetLimit extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$budgetLimitId = (int) $value;
$budgetLimit = self::where('budget_limits.id', $budgetLimitId)

View File

@@ -52,8 +52,11 @@ class Category extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$categoryId = (int) $value;

View File

@@ -42,8 +42,11 @@ class InvitedUser extends Model
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$attemptId = (int) $value;

View File

@@ -41,8 +41,11 @@ class LinkType extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$linkTypeId = (int) $value;
$linkType = self::find($linkTypeId);

View File

@@ -45,8 +45,11 @@ class ObjectGroup extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$objectGroupId = (int) $value;

View File

@@ -60,8 +60,11 @@ class PiggyBank extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$piggyBankId = (int) $value;
$piggyBank = self::where('piggy_banks.id', $piggyBankId)

View File

@@ -42,8 +42,11 @@ class Preference extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();

View File

@@ -74,8 +74,11 @@ class Recurrence extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$recurrenceId = (int) $value;

View File

@@ -52,8 +52,11 @@ class Rule extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$ruleId = (int) $value;

View File

@@ -54,8 +54,11 @@ class RuleGroup extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$ruleGroupId = (int) $value;

View File

@@ -52,8 +52,11 @@ class Tag extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$tagId = (int) $value;

View File

@@ -48,8 +48,11 @@ class TransactionCurrency extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$currencyId = (int) $value;
$currency = self::find($currencyId);

View File

@@ -55,8 +55,11 @@ class TransactionGroup extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
Log::debug(sprintf('Now in %s("%s")', __METHOD__, $value));
if (auth()->check()) {
$groupId = (int) $value;

View File

@@ -79,8 +79,11 @@ class TransactionJournal extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$journalId = (int) $value;

View File

@@ -41,8 +41,12 @@ class TransactionJournalLink extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$linkId = (int) $value;
$link = self::where('journal_links.id', $linkId)

View File

@@ -72,12 +72,15 @@ class TransactionType extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $type): self
public static function routeBinder(self|string $value): self
{
if (!auth()->check()) {
throw new NotFoundHttpException();
}
$transactionType = self::where('type', ucfirst($type))->first();
if ($value instanceof self) {
$value = (string) $value->type;
}
$transactionType = self::where('type', ucfirst($value))->first();
if (null !== $transactionType) {
return $transactionType;
}

View File

@@ -44,9 +44,13 @@ class UserGroup extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if (auth()->check()) {
if ($value instanceof self) {
$value = (int) $value->id;
}
$userGroupId = (int) $value;
/** @var User $user */

View File

@@ -130,9 +130,12 @@ class Webhook extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if (auth()->check()) {
if ($value instanceof self) {
$value = (int) $value->id;
}
$webhookId = (int) $value;
/** @var User $user */

View File

@@ -42,9 +42,12 @@ class WebhookAttempt extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if (auth()->check()) {
if ($value instanceof self) {
$value = (int) $value->id;
}
$attemptId = (int) $value;
/** @var User $user */

View File

@@ -44,9 +44,12 @@ class WebhookMessage extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if (auth()->check()) {
if ($value instanceof self) {
$value = (int) $value->id;
}
$messageId = (int) $value;
/** @var User $user */

View File

@@ -58,7 +58,7 @@ class SubscriptionsOverdueReminder extends Notification
$info = [];
$count = 0;
foreach ($this->overdue as $item) {
$current = ['bill' => $item['bill']];
$current = ['bill' => $item['bill']];
$current['pay_dates'] = array_map(static fn (string $date): string => new Carbon($date)->isoFormat((string) trans(
'config.month_and_day_moment_js'
)), $item['dates']['pay_dates']);

View File

@@ -23,15 +23,12 @@ declare(strict_types=1);
namespace FireflyIII\Providers;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\DestroyedTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TriggeredStoredTransactionGroup;
use FireflyIII\Events\Preferences\UserGroupChangedPrimaryCurrency;
use FireflyIII\Events\StoredAccount;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\UpdatedAccount;
use FireflyIII\Events\UpdatedTransactionGroup;
use Illuminate\Auth\Events\Login;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Laravel\Passport\Events\AccessTokenCreated;
use Override;
@@ -44,31 +41,19 @@ use Override;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
Login::class => [
'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin',
'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish',
],
// is a User related event.
InvitationCreated::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification',
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite',
],
// is a Transaction Journal related event.
StoredTransactionGroup::class => ['FireflyIII\Handlers\Events\StoredGroupEventHandler@runAllHandlers'],
TriggeredStoredTransactionGroup::class => ['FireflyIII\Handlers\Events\StoredGroupEventHandler@triggerRulesManually'],
// StoredTransactionGroup::class => ['FireflyIII\Handlers\Events\StoredGroupEventHandler@runAllHandlers'],
// TriggeredStoredTransactionGroup::class => ['FireflyIII\Handlers\Events\StoredGroupEventHandler@triggerRulesManually'],
// is a Transaction Journal related event.
UpdatedTransactionGroup::class => ['FireflyIII\Handlers\Events\UpdatedGroupEventHandler@runAllHandlers'],
DestroyedTransactionGroup::class => ['FireflyIII\Handlers\Events\DestroyedGroupEventHandler@runAllHandlers'],
// UpdatedTransactionGroup::class => ['FireflyIII\Handlers\Events\UpdatedGroupEventHandler@runAllHandlers'],
// DestroyedTransactionGroup::class => ['FireflyIII\Handlers\Events\DestroyedGroupEventHandler@runAllHandlers'],
// API related events:
AccessTokenCreated::class => ['FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated'],
// AccessTokenCreated::class => ['FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated'],
// account related events:
StoredAccount::class => ['FireflyIII\Handlers\Events\StoredAccountEventHandler@recalculateCredit'],
UpdatedAccount::class => ['FireflyIII\Handlers\Events\UpdatedAccountEventHandler@recalculateCredit'],
// StoredAccount::class => ['FireflyIII\Handlers\Events\StoredAccountEventHandler@recalculateCredit'],
// UpdatedAccount::class => ['FireflyIII\Handlers\Events\UpdatedAccountEventHandler@recalculateCredit'],
// preferences
UserGroupChangedPrimaryCurrency::class => ['FireflyIII\Handlers\Events\PreferencesEventHandler@resetPrimaryCurrencyAmounts'],
// UserGroupChangedPrimaryCurrency::class => ['FireflyIII\Handlers\Events\PreferencesEventHandler@resetPrimaryCurrencyAmounts'],
];
/**

View File

@@ -39,6 +39,7 @@ use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
use Override;
/**
* Class JournalRepository.
@@ -250,4 +251,20 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
return $journal;
}
#[Override]
public function getUncompletedJournals(): Collection
{
return $this->userGroup
->transactionJournals()
->where('completed', false)
->get(['transaction_journals.*'])
;
}
#[Override]
public function markAsCompleted(Collection $set): void
{
TransactionJournal::whereIn('id', $set->pluck('id')->toArray())->update(['completed' => true]);
}
}

View File

@@ -52,6 +52,10 @@ interface JournalRepositoryInterface
*/
public function destroyGroup(TransactionGroup $transactionGroup): void;
public function getUncompletedJournals(): Collection;
public function markAsCompleted(Collection $set): void;
/**
* Deletes a journal.
*/

View File

@@ -25,12 +25,18 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\PeriodStatistic;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Models\UserGroup;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Override;
@@ -137,8 +143,98 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
}
#[Override]
public function deleteStatisticsForPrefix(UserGroup $userGroup, string $prefix, Carbon $date): void
public function deleteStatisticsForPrefix(string $prefix, Collection $dates): void
{
$userGroup->periodStatistics()->where('start', '<=', $date)->where('end', '>=', $date)->where('type', 'LIKE', sprintf('%s%%', $prefix))->delete();
$count = $this->userGroup
->periodStatistics()
->where(function (Builder $q) use ($dates): void {
foreach ($dates as $date) {
$q->where(function (Builder $q1) use ($date): void {
$q1->where('start', '<=', $date)->where('end', '>=', $date);
});
}
})
->where('type', 'LIKE', sprintf('%s%%', $prefix))
->delete()
;
Log::debug(sprintf('Deleted %d entries for prefix "%s"', $count, $prefix));
}
public function deleteStatisticsForType(string $class, Collection $objects, Collection $dates): void
{
if (0 === count($objects)) {
Log::debug(sprintf('Nothing to delete in deleteStatisticsForType("%s")', $class));
return;
}
$count = PeriodStatistic::where('primary_statable_type', $class)
->whereIn('primary_statable_id', $objects->pluck('id')->toArray())
->where(function (Builder $q) use ($dates): void {
foreach ($dates as $date) {
$q->where(function (Builder $q1) use ($date): void {
$q1->where('start', '<=', $date)->where('end', '>=', $date);
});
}
})
->delete()
;
Log::debug(sprintf('Delete %d statistics for %dx %s', $count, $objects->count(), $class));
}
#[Override]
public function deleteStatisticsForCollection(Collection $set): void
{
Log::debug(sprintf('Delete statistics for %d transaction journals.', count($set)));
// collect all transactions:
$transactions = Transaction::whereIn('transaction_journal_id', $set->pluck('id')->toArray())->get(['transactions.*']);
// collect all accounts and delete stats:
$accounts = Account::whereIn('id', $transactions->pluck('account_id')->toArray())->get(['accounts.*']);
$dates = $set->pluck('date');
$this->deleteStatisticsForType(Account::class, $accounts, $dates);
// collect all categories, and remove stats.
$categories = Category::whereIn(
'id',
DB::table('category_transaction_journal')
->whereIn('transaction_journal_id', $set->pluck('id')->toArray())
->get(['category_transaction_journal.category_id'])
->pluck('category_id')
->toArray()
)->get(['categories.*']);
$this->deleteStatisticsForType(Category::class, $categories, $dates);
// budgets, same thing
$budgets = Budget::whereIn(
'id',
DB::table('budget_transaction_journal')
->whereIn('transaction_journal_id', $set->pluck('id')->toArray())
->get(['budget_transaction_journal.budget_id'])
->pluck('budget_id')
->toArray()
)->get(['budgets.*']);
$this->deleteStatisticsForType(Budget::class, $budgets, $dates);
// tags
$tags = Tag::whereIn(
'id',
DB::table('tag_transaction_journal')
->whereIn('transaction_journal_id', $set->pluck('id')->toArray())
->get(['tag_transaction_journal.tag_id'])
->pluck('tag_id')
->toArray()
)->get(['tags.*']);
$this->deleteStatisticsForType(Tag::class, $tags, $dates);
// remove for no tag, no cat, etc.
if (0 === $categories->count()) {
$this->deleteStatisticsForPrefix('no_category', $dates);
}
if (0 === $budgets->count()) {
$this->deleteStatisticsForPrefix('no_budget', $dates);
}
if (0 === $tags->count()) {
$this->deleteStatisticsForPrefix('no_tag', $dates);
}
}
}

View File

@@ -26,12 +26,13 @@ namespace FireflyIII\Repositories\PeriodStatistic;
use Carbon\Carbon;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Models\UserGroup;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
interface PeriodStatisticRepositoryInterface
{
public function deleteStatisticsForCollection(Collection $set);
public function findPeriodStatistics(Model $model, Carbon $start, Carbon $end, array $types): Collection;
public function findPeriodStatistic(Model $model, Carbon $start, Carbon $end, string $type): Collection;
@@ -54,5 +55,5 @@ interface PeriodStatisticRepositoryInterface
public function deleteStatisticsForModel(Model $model, Carbon $date): void;
public function deleteStatisticsForPrefix(UserGroup $userGroup, string $prefix, Carbon $date): void;
public function deleteStatisticsForPrefix(string $prefix, Collection $dates): void;
}

View File

@@ -172,7 +172,11 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
$listedJournals[] = $journalId;
$array[$currencyId]['tags'][$tagId] ??= ['id' => $tagId, 'name' => $tagName, 'transaction_journals' => []];
$array[$currencyId]['tags'][$tagId] ??= [
'id' => $tagId,
'name' => $tagName,
'transaction_journals' => [],
];
$journalId = (int) $journal['transaction_journal_id'];
$array[$currencyId]['tags'][$tagId]['transaction_journals'][$journalId] = [
'amount' => Steam::positive($journal['amount']),

View File

@@ -34,6 +34,7 @@ use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
@@ -43,9 +44,15 @@ class CreditRecalculateService
{
private ?Account $account = null;
private ?TransactionGroup $group = null;
private Collection $journals;
private AccountRepositoryInterface $repository;
private array $work = [];
public function __construct()
{
$this->journals = new Collection();
}
public function recalculate(): void
{
if (true !== config('firefly.feature_flags.handle_debts')) {
@@ -58,25 +65,46 @@ class CreditRecalculateService
// work based on account.
$this->processAccount();
}
if ($this->journals->count() > 0) {
$this->processJournals();
}
if (0 === count($this->work)) {
Log::debug('No work found for recalculate() to do.');
return;
}
$this->processWork();
}
private function processGroup(): void
private function collectFromJournals(Collection $transactionJournals): void
{
/** @var TransactionJournal $journal */
foreach ($this->group->transactionJournals as $journal) {
try {
$this->findByJournal($journal);
} catch (FireflyException $e) {
Log::error($e->getTraceAsString());
Log::error(sprintf('Could not find work account for transaction group #%d.', $this->group->id));
Log::debug('Now in collectFromJournals()');
$valid = config('firefly.valid_liabilities');
$accounts = Account::leftJoin('transactions', 'transactions.account_id', 'accounts.id')
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
->whereIn('transaction_journals.id', $transactionJournals->pluck('id')->toArray())
->whereIn('account_types.type', $valid)
->get(['accounts.*'])
;
if ($accounts->count() > 0) {
Log::debug(sprintf('Found %d account(s) to process.', $accounts->count()));
foreach ($accounts as $account) {
$this->work[] = $account;
}
}
}
private function processGroup(): void
{
$this->collectFromJournals($this->group->transactionJournals);
}
private function processJournals(): void
{
$this->collectFromJournals($this->journals);
}
/**
* @throws FireflyException
*/
@@ -460,4 +488,9 @@ class CreditRecalculateService
{
$this->group = $group;
}
public function setJournals(Collection $journals): void
{
$this->journals = $journals;
}
}

View File

@@ -36,7 +36,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Rules\UniqueIban;
use FireflyIII\Support\NullArrayObject;
use Illuminate\Support\Facades\Log;
use Safe\Exceptions\JsonException;
@@ -378,7 +377,7 @@ trait JournalServiceTrait
return $amount;
}
protected function storeBudget(TransactionJournal $journal, NullArrayObject $data): void
protected function storeBudget(TransactionJournal $journal, array $data): void
{
if (TransactionTypeEnum::WITHDRAWAL->value !== $journal->transactionType->type) {
$journal->budgets()->sync([]);
@@ -396,7 +395,7 @@ trait JournalServiceTrait
$journal->budgets()->sync([]);
}
protected function storeCategory(TransactionJournal $journal, NullArrayObject $data): void
protected function storeCategory(TransactionJournal $journal, array $data): void
{
$category = $this->categoryRepository->findCategory($data['category_id'], $data['category_name']);
if (null !== $category) {

View File

@@ -170,7 +170,7 @@ class AccountBalanceGrouped
{
$this->primary = $primary;
$primaryCurrencyId = $primary->id;
$this->currencies = [$primary->id => $primary]; // currency cache
$this->currencies = [$primary->id => $primary]; // currency cache
$this->data[$primaryCurrencyId] = [
'currency_id' => (string) $primaryCurrencyId,
'currency_symbol' => $primary->symbol,

View File

@@ -222,7 +222,14 @@ trait AugumentData
$currentEnd->addMonth();
}
// primary currency amount.
$expenses = $opsRepository->sumExpenses($currentStart, $currentEnd, null, $budgetCollection, $entry->transactionCurrency, $this->convertToPrimary);
$expenses = $opsRepository->sumExpenses(
$currentStart,
$currentEnd,
null,
$budgetCollection,
$entry->transactionCurrency,
$this->convertToPrimary
);
$spent = $expenses[$currency->id]['sum'] ?? '0';
$entry->pc_spent = $spent;

View File

@@ -157,7 +157,7 @@ trait GetConfigurationData
// previous year:
$yearBegin = today(config('app.timezone'))->subYear()->startOfYear();
$index = (string) trans('firefly.previous_year', ['year' => $yearBegin->year]);
$index = (string) trans('firefly.previous_year', ['year' => $yearBegin->year]);
$ranges[$index] = [$yearBegin, $yearBegin->clone()->endOfYear()];
// everything

View File

@@ -223,10 +223,7 @@ class AccountEnrichment implements EnrichmentInterface
// $finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary);
$finalBalance = $this->balances[$id];
$balanceDifference = $this->getBalanceDifference($id, $currency);
Log::debug(
sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()),
$finalBalance
);
// Log::debug(sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()), $finalBalance);
// collect current balances:
$currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places);
@@ -329,7 +326,8 @@ class AccountEnrichment implements EnrichmentInterface
'zoom_level' => (int) $location['zoom_level'],
];
}
Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
// Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
}
private function collectMetaData(): void
@@ -385,7 +383,8 @@ class AccountEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void

View File

@@ -183,7 +183,8 @@ class BudgetEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void

View File

@@ -152,7 +152,10 @@ class BudgetLimitEnrichment implements EnrichmentInterface
private function collectCurrencies(): void
{
$this->currencies[$this->primaryCurrency->id] = $this->primaryCurrency;
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->whereNot('id', $this->primaryCurrency->id)->get();
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->whereNot(
'id',
$this->primaryCurrency->id
)->get();
foreach ($currencies as $currency) {
$this->currencies[(int) $currency->id] = $currency;
}
@@ -191,7 +194,8 @@ class BudgetLimitEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function filterToBudget(array $expenses, int $budget): array

View File

@@ -132,7 +132,8 @@ class CategoryEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectTransactions(): void

View File

@@ -262,7 +262,8 @@ class PiggyBankEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void

View File

@@ -327,7 +327,8 @@ class RecurringEnrichment implements EnrichmentInterface
$this->notes[$notableId] = (string) $note['text'];
Log::debug(sprintf('Collected note #%d for recurrence #%d', $note['id'], $notableId));
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectPiggyBankInfo(array $piggyBankIds): void
@@ -357,7 +358,10 @@ class RecurringEnrichment implements EnrichmentInterface
/** @var RecurrenceRepetition $repetition */
foreach ($set as $repetition) {
$recurrence = $this->collection->filter(static fn (Recurrence $item): bool => (int) $item->id === (int) $repetition->recurrence_id)->first();
$recurrence = $this->collection
->filter(static fn (Recurrence $item): bool => (int) $item->id === (int) $repetition->recurrence_id)
->first()
;
$fromDate = clone ($recurrence->latest_date ?? $recurrence->first_date);
$recurrenceId = (int) $repetition->recurrence_id;
$repId = (int) $repetition->id;

View File

@@ -205,7 +205,8 @@ class SubscriptionEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void

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