Compare commits

...

6 Commits

Author SHA1 Message Date
github-actions[bot]
d3add7c92b Merge pull request #11647 from firefly-iii/release-1770268490
🤖 Automatically merge the PR into the develop branch.
2026-02-05 06:14:57 +01:00
JC5
a491e4921f 🤖 Auto commit for release 'develop' on 2026-02-05 2026-02-05 06:14:50 +01:00
James Cole
171bc03668 Fix running balance events. 2026-02-05 06:10:25 +01:00
James Cole
dd5476bfc7 Clean up events and filters. 2026-02-05 06:02:32 +01:00
James Cole
bc0769358d Clean up update handlers. 2026-02-05 05:51:44 +01:00
James Cole
ccf33f1db6 Also include delete event in new event triggers. 2026-02-05 05:47:37 +01:00
18 changed files with 87 additions and 148 deletions

View File

@@ -31,7 +31,6 @@ use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Debug\Timer;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\AccountFilter;
@@ -80,7 +79,7 @@ class AccountController extends Controller
*/
public function accounts(AutocompleteApiRequest $request): JsonResponse
{
Log::debug('Before All.');
// Log::debug('Before All.');
['types' => $types, 'query' => $query, 'date' => $date, 'limit' => $limit] = $request->attributes->all();
$date ??= today(config('app.timezone'));
@@ -89,8 +88,6 @@ class AccountController extends Controller
$date->endOfDay();
$return = [];
$timer = Timer::getInstance();
$timer->start(sprintf('AC accounts "%s"', $query));
$result = $this->repository->searchAccount((string) $query, $types, $limit);
$allBalances = Steam::accountsBalancesOptimized($result, $date, $this->primaryCurrency, $this->convertToPrimary);
@@ -136,7 +133,6 @@ class AccountController extends Controller
return $posA - $posB;
});
$timer->stop(sprintf('AC accounts "%s"', $query));
return response()->api($return);
}

View File

@@ -25,9 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\Transaction;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Events\UpdatedAccount;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
@@ -74,31 +71,9 @@ class DestroyController extends Controller
public function destroy(TransactionGroup $transactionGroup): JsonResponse
{
Log::debug(sprintf('Now in %s', __METHOD__));
// grab asset account(s) from group:
$accounts = [];
/** @var TransactionJournal $journal */
foreach ($transactionGroup->transactionJournals as $journal) {
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
$type = $transaction->account->accountType->type;
// if is valid liability, trigger event!
if (in_array($type, config('firefly.valid_liabilities'), true)) {
$accounts[] = $transaction->account;
}
}
}
$this->groupRepository->destroy($transactionGroup);
Preferences::mark();
/** @var Account $account */
foreach ($accounts as $account) {
Log::debug(sprintf('Now going to trigger updated account event for account #%d', $account->id));
event(new UpdatedAccount($account));
}
return response()->json([], 204);
}

View File

@@ -76,7 +76,9 @@ class UpdateController extends Controller
Log::debug('Now in update routine for transaction group');
$data = $request->getAll();
$oldHash = $this->groupRepository->getCompareHash($transactionGroup);
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($transactionGroup);
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
$objects->appendFromTransactionGroup($transactionGroup);
$newHash = $this->groupRepository->getCompareHash($transactionGroup);
$manager = $this->getManager();
@@ -89,7 +91,6 @@ class UpdateController extends Controller
$flags->applyRules = $applyRules;
$flags->fireWebhooks = $fireWebhooks;
$flags->recalculateCredit = $runRecalculations;
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($transactionGroup);
event(new UpdatedSingleTransactionGroup($flags, $objects));
/** @var User $admin */

View File

@@ -53,7 +53,7 @@ abstract class AggregateFormRequest extends ApiRequest
parent::initialize($query, $request, $attributes, $cookies, $files, $server, $content);
// instantiate all subrequests and share current requests' bags with them
Log::debug('Initializing AggregateFormRequest.');
// Log::debug('Initializing AggregateFormRequest.');
/** @var array|string $config */
foreach ($this->getRequests() as $config) {
@@ -62,7 +62,7 @@ abstract class AggregateFormRequest extends ApiRequest
if (!is_a($requestClass, Request::class, true)) {
throw new RuntimeException('getRequests() must return class-strings of subclasses of Request');
}
Log::debug(sprintf('Initializing subrequest %s', $requestClass));
// Log::debug(sprintf('Initializing subrequest %s', $requestClass));
$instance = $this->requests[] = new $requestClass();
$instance->request = $this->request;
@@ -77,7 +77,8 @@ abstract class AggregateFormRequest extends ApiRequest
$instance->handleConfig(is_array($config) ? $config : []);
}
}
Log::debug('Done initializing AggregateFormRequest.');
// Log::debug('Done initializing AggregateFormRequest.');
}
public function rules(): array
@@ -95,7 +96,7 @@ abstract class AggregateFormRequest extends ApiRequest
// register all subrequests' validators
foreach ($this->requests as $request) {
if (method_exists($request, 'withValidator')) {
Log::debug(sprintf('Process withValidator from class %s', $request::class));
// Log::debug(sprintf('Process withValidator from class %s', $request::class));
$request->withValidator($validator);
}
}

View File

@@ -26,7 +26,6 @@ namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class CreatedSingleTransactionGroup extends Event
{
@@ -38,7 +37,5 @@ class CreatedSingleTransactionGroup extends Event
public function __construct(
public TransactionGroupEventFlags $flags,
public TransactionGroupEventObjects $objects
) {
Log::debug(__METHOD__);
}
) {}
}

View File

@@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Queue\SerializesModels;
class DestroyedSingleTransactionGroup extends Event
@@ -36,6 +35,7 @@ class DestroyedSingleTransactionGroup extends Event
* Create a new event instance.
*/
public function __construct(
public TransactionGroup $transactionGroup
public TransactionGroupEventFlags $flags,
public TransactionGroupEventObjects $objects
) {}
}

View File

@@ -62,5 +62,11 @@ class TransactionGroupEventObjects
$this->accounts->push($transaction->account);
}
}
$this->transactionGroups = $this->transactionGroups->unique('id');
$this->transactionJournals = $this->transactionJournals->unique('id');
$this->budgets = $this->budgets->unique('id');
$this->categories = $this->categories->unique('id');
$this->tags = $this->tags->unique('id');
$this->accounts = $this->accounts->unique('id');
}
}

View File

@@ -328,7 +328,7 @@ class TransactionJournalFactory
throw new FireflyException($e->getMessage(), 0, $e);
}
Log::debug(sprintf('Is part of a batch submission? %s', var_export($row['batch_submission'], 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);

View File

@@ -24,12 +24,9 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Transaction;
use FireflyIII\Events\UpdatedAccount;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Facades\Steam;
@@ -112,29 +109,7 @@ class DeleteController extends Controller
}
$objectType = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
session()->flash('success', (string) trans('firefly.deleted_'.strtolower($objectType), ['description' => $group->title ?? $journal->description]));
// grab asset account(s) from group:
$accounts = [];
/** @var TransactionJournal $currentJournal */
foreach ($group->transactionJournals as $currentJournal) {
/** @var Transaction $transaction */
foreach ($currentJournal->transactions as $transaction) {
$type = $transaction->account->accountType->type;
// if is valid liability, trigger event!
if (in_array($type, config('firefly.valid_liabilities'), true)) {
$accounts[] = $transaction->account;
}
}
}
$this->repository->destroy($group);
/** @var Account $account */
foreach ($accounts as $account) {
Log::debug(sprintf('Now going to trigger updated account event for account #%d', $account->id));
event(new UpdatedAccount($account));
}
Preferences::mark();
return redirect($this->getPreviousUrl('transactions.delete.url'));

View File

@@ -26,47 +26,31 @@ namespace FireflyIII\Listeners\Model\TransactionGroup;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\Model\TransactionGroup\DestroyedSingleTransactionGroup;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class ProcessesDestroyedTransactionGroup implements ShouldQueue
{
use SupportsGroupProcessingTrait;
public function handle(DestroyedSingleTransactionGroup $event): void
{
$this->triggerWebhooks($event);
$this->updateRunningBalance($event);
}
Log::debug(sprintf('User called %s', get_class($event)));
private function triggerWebhooks(DestroyedSingleTransactionGroup $destroyedGroupEvent): void
{
Log::debug('DestroyedTransactionGroup:triggerWebhooks');
$group = $destroyedGroupEvent->transactionGroup;
$user = $group->user;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setObjects(new Collection()->push($group));
$engine->setTrigger(WebhookTrigger::DESTROY_TRANSACTION);
$engine->generateMessages();
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
private function updateRunningBalance(DestroyedSingleTransactionGroup $event): void
{
if (false === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
return;
if (!$event->flags->recalculateCredit) {
Log::debug(sprintf('Will NOT recalculate credit for %d journal(s)', $event->objects->transactionJournals->count()));
}
Log::debug(__METHOD__);
$group = $event->transactionGroup;
foreach ($group->transactionJournals as $journal) {
AccountBalanceCalculator::recalculateForJournal($journal);
if (!$event->flags->fireWebhooks) {
Log::debug(sprintf('Will NOT fire webhooks for %d journal(s)', $event->objects->transactionJournals->count()));
}
if ($event->flags->recalculateCredit) {
$this->recalculateCredit($event->objects->accounts);
}
if ($event->flags->fireWebhooks) {
$this->createWebhookMessages($event->objects->transactionGroups, WebhookTrigger::DESTROY_TRANSACTION);
}
$this->removePeriodStatistics($event->objects);
$this->recalculateRunningBalance($event->objects);
}
}

View File

@@ -38,7 +38,7 @@ class ProcessesNewTransactionGroup implements ShouldQueue
public function handle(CreatedSingleTransactionGroup|UserRequestedBatchProcessing $event): void
{
Log::debug(sprintf('User called %s', get_class($event)));
Log::debug(sprintf('Running event handler for %s', get_class($event)));
$setting = FireflyConfig::get('enable_batch_processing', false)->data;
if (true === $event->flags->batchSubmission && true === $setting) {
@@ -46,7 +46,6 @@ class ProcessesNewTransactionGroup implements ShouldQueue
return;
}
Log::debug('Will also collect all open transaction groups and process them as well.');
$repository = app(JournalRepositoryInterface::class);
$journals = $event->objects->transactionJournals->merge($repository->getAllUncompletedJournals());
@@ -68,7 +67,7 @@ class ProcessesNewTransactionGroup implements ShouldQueue
$this->recalculateCredit($event->objects->accounts);
}
if ($event->flags->fireWebhooks) {
$this->fireWebhooks($journals, WebhookTrigger::STORE_TRANSACTION);
$this->createWebhookMessages($event->objects->transactionGroups, WebhookTrigger::STORE_TRANSACTION);
}
$this->removePeriodStatistics($event->objects);
$this->recalculateRunningBalance($event->objects);

View File

@@ -39,7 +39,7 @@ class ProcessesUpdatedTransactionGroup
public function handle(UpdatedSingleTransactionGroup $event): void
{
Log::debug(sprintf('User called %s', get_class($event)));
Log::debug(sprintf('Now handling event %s', get_class($event)));
$this->unifyAccounts($event);
Log::debug(sprintf('Transaction journal count is %d', $event->objects->transactionJournals->count()));
@@ -60,7 +60,7 @@ class ProcessesUpdatedTransactionGroup
$this->recalculateCredit($event->objects->accounts);
}
if ($event->flags->fireWebhooks) {
$this->fireWebhooks($event->objects->transactionJournals, WebhookTrigger::UPDATE_TRANSACTION);
$this->createWebhookMessages($event->objects->transactionGroups, WebhookTrigger::UPDATE_TRANSACTION);
}
$this->removePeriodStatistics($event->objects);
$this->recalculateRunningBalance($event->objects);

View File

@@ -34,17 +34,15 @@ trait SupportsGroupProcessingTrait
$object = app(CreditRecalculateService::class);
$object->setAccounts($accounts);
$object->recalculate();
Log::debug(sprintf('Done with recalculateCredit for %d account(s)', $accounts->count()));
}
private function fireWebhooks(Collection $journals, WebhookTrigger $trigger): void
private function createWebhookMessages(Collection $groups, WebhookTrigger $trigger): void
{
// collect transaction groups by set ids.
$groups = TransactionGroup::whereIn('id', array_unique($journals->pluck('transaction_group_id')->toArray()))->get();
Log::debug(sprintf('Will now create webhook messages for %d group(s)', $groups->count()));
Log::debug(__METHOD__);
/** @var TransactionJournal $first */
$first = $journals->first();
/** @var TransactionGroup $first */
$first = $groups->first();
$user = $first->user;
/** @var MessageGeneratorInterface $engine */
@@ -57,36 +55,42 @@ trait SupportsGroupProcessingTrait
$engine->setObjects($groups);
// tell the generator to generate the messages
$engine->generateMessages();
Log::debug(sprintf('Done with create webhook messages for %d group(s)', $groups->count()));
}
protected function removePeriodStatistics(TransactionGroupEventObjects $objects): void
{
if (auth()->check()) {
// since you get a bunch of journals AND a bunch of
// objects, this needs to be a collection
/** @var PeriodStatisticRepositoryInterface $repository */
$repository = app(PeriodStatisticRepositoryInterface::class);
$dates = $this->collectDatesFromJournals($objects->transactionJournals);
$repository->deleteStatisticsForType(Account::class, $objects->accounts, $dates);
$repository->deleteStatisticsForType(Budget::class, $objects->budgets, $dates);
$repository->deleteStatisticsForType(Category::class, $objects->categories, $dates);
$repository->deleteStatisticsForType(Tag::class, $objects->tags, $dates);
// remove if no stuff present:
// remove for no tag, no cat, etc.
if (0 === $objects->budgets->count()) {
Log::debug('No budgets, delete "no_category" stats.');
$repository->deleteStatisticsForPrefix('no_budget', $dates);
}
if (0 === $objects->categories->count()) {
Log::debug('No categories, delete "no_category" stats.');
$repository->deleteStatisticsForPrefix('no_category', $dates);
}
if (0 === $objects->tags->count()) {
Log::debug('No tags, delete "no_category" stats.');
$repository->deleteStatisticsForPrefix('no_tag', $dates);
}
if (!auth()->check()) {
Log::debug('Will NOT remove period statistics for all objects, because no user detected.');
}
Log::debug('Will now remove period statistics for all objects.');
// since you get a bunch of journals AND a bunch of
// objects, this needs to be a collection
/** @var PeriodStatisticRepositoryInterface $repository */
$repository = app(PeriodStatisticRepositoryInterface::class);
$dates = $this->collectDatesFromJournals($objects->transactionJournals);
$repository->deleteStatisticsForType(Account::class, $objects->accounts, $dates);
$repository->deleteStatisticsForType(Budget::class, $objects->budgets, $dates);
$repository->deleteStatisticsForType(Category::class, $objects->categories, $dates);
$repository->deleteStatisticsForType(Tag::class, $objects->tags, $dates);
// remove if no stuff present:
// remove for no tag, no cat, etc.
if (0 === $objects->budgets->count()) {
Log::debug('No budgets, delete "no_category" stats.');
$repository->deleteStatisticsForPrefix('no_budget', $dates);
}
if (0 === $objects->categories->count()) {
Log::debug('No categories, delete "no_category" stats.');
$repository->deleteStatisticsForPrefix('no_category', $dates);
}
if (0 === $objects->tags->count()) {
Log::debug('No tags, delete "no_category" stats.');
$repository->deleteStatisticsForPrefix('no_tag', $dates);
}
Log::debug('Done with remove period statistics for all objects.');
}
private function collectDatesFromJournals(Collection $journals): Collection
@@ -125,12 +129,13 @@ trait SupportsGroupProcessingTrait
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
$newRuleEngine->setRuleGroups($groups);
$newRuleEngine->fire();
Log::debug(sprintf('Done with processRules("%s") for %d journal(s)', $type, $set->count()));
}
protected function recalculateRunningBalance(TransactionGroupEventObjects $objects): void
{
Log::debug('Now in recalculateRunningBalance');
if (true === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
if (false === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
Log::debug('Running balance is disabled.');
return;

View File

@@ -420,7 +420,6 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
$flags->applyRules = $data['apply_rules'] ?? true;
$flags->fireWebhooks = $data['fire_webhooks'] ?? true;
$flags->batchSubmission = $data['batch_submission'] ?? false;
Log::debug('CreatedSingleTransactionGroup');
event(new CreatedSingleTransactionGroup($flags, $objects));
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());

View File

@@ -25,6 +25,8 @@ declare(strict_types=1);
namespace FireflyIII\Services\Internal\Destroy;
use FireflyIII\Events\Model\TransactionGroup\DestroyedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventObjects;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Support\Facades\Log;
@@ -36,6 +38,7 @@ class TransactionGroupDestroyService
public function destroy(TransactionGroup $transactionGroup): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($transactionGroup);
/** @var JournalDestroyService $service */
$service = app(JournalDestroyService::class);
@@ -44,6 +47,7 @@ class TransactionGroupDestroyService
}
$transactionGroup->delete();
// trigger just after destruction
event(new DestroyedSingleTransactionGroup($transactionGroup));
$flags = new TransactionGroupEventFlags();
event(new DestroyedSingleTransactionGroup($flags, $objects));
}
}

View File

@@ -26,7 +26,6 @@ namespace FireflyIII\Services\Internal\Update;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Events\Model\Account\UpdatedExistingAccount;
use FireflyIII\Events\UpdatedAccount;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
@@ -105,7 +104,7 @@ class AccountUpdateService
// update preferences if inactive:
$this->updatePreferences($account);
event(new UpdatedAccount($account));
event(new UpdatedExistingAccount($account));
return $account;
}
@@ -151,8 +150,6 @@ class AccountUpdateService
$account->save();
event(new UpdatedExistingAccount($account));
return $account;
}

View File

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

6
package-lock.json generated
View File

@@ -7118,9 +7118,9 @@
}
},
"node_modules/i18next": {
"version": "25.8.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.2.tgz",
"integrity": "sha512-7KyJnG9n1nXXVqyOV/TAcp6/4QFgqMoob5y1xTPnWRU5wnrsDYRUvWEmF6RV98EY72ET+nUGkLQsmmO6T1l94Q==",
"version": "25.8.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.3.tgz",
"integrity": "sha512-IC/pp2vkczdu1sBheq1eC92bLavN6fM5jH61c7Xa23PGio5ePEd+EP+re1IkO7KEM9eyeJHUxvIRxsaYTlsSyQ==",
"funding": [
{
"type": "individual",