Compare commits

...

5 Commits

Author SHA1 Message Date
github-actions[bot]
20986e6426 Merge pull request #11644 from firefly-iii/release-1770218546
🤖 Automatically merge the PR into the develop branch.
2026-02-04 16:22:34 +01:00
JC5
9cd0ebe37e 🤖 Auto commit for release 'develop' on 2026-02-04 2026-02-04 16:22:26 +01:00
Sander Dorigo
9c2b83a971 Update event handlers 2026-02-04 16:16:27 +01:00
Sander Dorigo
e1d32da409 New event handler object 2026-02-04 08:29:09 +01:00
Sander Dorigo
c51df8cd83 Move events to service and repos 2026-02-04 08:18:35 +01:00
12 changed files with 291 additions and 218 deletions

View File

@@ -27,14 +27,11 @@ namespace FireflyIII\Api\V1\Controllers\Models\Transaction;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Transaction\StoreRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Rules\IsDuplicateTransaction;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\TransactionGroupTransformer;
@@ -88,9 +85,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,22 +106,15 @@ class StoreController extends Controller
throw new ValidationException($validator);
}
Preferences::mark();
$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)
@@ -134,20 +124,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

@@ -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;
use Illuminate\Support\Facades\Log;
@@ -37,8 +36,8 @@ class CreatedSingleTransactionGroup extends Event
* Create a new event instance.
*/
public function __construct(
public TransactionGroup $transactionGroup,
public TransactionGroupEventFlags $flags
public TransactionGroupEventFlags $flags,
public TransactionGroupEventObjects $objects
) {
Log::debug(__METHOD__);
}

View File

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

View File

@@ -24,8 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Transaction;
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,10 +74,6 @@ class CreateController extends Controller
$service = app(GroupCloneService::class);
$newGroup = $service->cloneGroup($group);
// event!
$flags = new TransactionGroupEventFlags();
event(new CreatedSingleTransactionGroup($group, $flags));
Preferences::mark();
$title = $newGroup->title ?? $newGroup->transactionJournals->first()->description;

View File

@@ -25,8 +25,6 @@ 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\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
@@ -336,7 +334,7 @@ class CreateRecurringTransactions implements ShouldQueue
Log::debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d')));
// count created journals on THIS day.
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
$journalCount = $this->repository->getJournalCount($recurrence, $date, $date);
if ($journalCount > 0 && false === $this->force) {
Log::info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d')));
@@ -354,11 +352,11 @@ class CreateRecurringTransactions implements ShouldQueue
}
// create transaction array and send to factory.
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
// #8844, if there is one recurrence transaction, use the first title as the title.
// #9305, if there is one recurrence transaction, group title must be NULL.
$groupTitle = null;
$groupTitle = null;
// #8844, if there are more, use the recurrence transaction itself.
if ($count > 1) {
@@ -371,7 +369,7 @@ class CreateRecurringTransactions implements ShouldQueue
return null;
}
$array = [
$array = [
'user' => $recurrence->user,
'user_group' => $recurrence->user->userGroup,
'group_title' => $groupTitle,
@@ -379,21 +377,13 @@ class CreateRecurringTransactions implements ShouldQueue
];
/** @var TransactionGroup $group */
$group = $this->groupRepository->store($array);
$group = $this->groupRepository->store($array);
++$this->created;
Log::info(sprintf('Created new transaction group #%d', $group->id));
// trigger event:
$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:
$recurrence->latest_date = $date;
$recurrence->latest_date_tz = $date->format('e');
$recurrence->save();
$this->repository->setLatestDate($recurrence, $date);
return $group;
}

View File

@@ -24,198 +24,54 @@ declare(strict_types=1);
namespace FireflyIII\Listeners\Model\TransactionGroup;
use Carbon\Carbon;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\UserRequestedBatchProcessing;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
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
{
use SupportsGroupProcessingTrait;
public function handle(CreatedSingleTransactionGroup|UserRequestedBatchProcessing $event): void
{
$groupId = 0;
$collection = new Collection();
if ($event instanceof CreatedSingleTransactionGroup) {
Log::debug(sprintf('In ProcessesNewTransactionGroup::handle(#%d)', $event->transactionGroup->id));
$groupId = $event->transactionGroup->id;
$collection = $event->transactionGroup->transactionJournals;
}
if ($event instanceof UserRequestedBatchProcessing) {
Log::debug('User called UserRequestedBatchProcessing');
}
Log::debug(sprintf('User called %s', get_class($event)));
$setting = FireflyConfig::get('enable_batch_processing', false)->data;
if (true === $event->flags->batchSubmission && true === $setting) {
Log::debug(sprintf('Will do nothing for group #%d because it is part of a batch.', $groupId));
Log::debug('Will do nothing for event because it is part of a batch.');
return;
}
Log::debug(sprintf('Will (joined with group #%d) collect all open transaction groups and process them.', $groupId));
Log::debug('Will also collect all open transaction groups and process them.');
$repository = app(JournalRepositoryInterface::class);
$set = $collection->merge($repository->getAllUncompletedJournals());
if (0 === $set->count()) {
Log::debug('Set is empty, never mind.');
$journals = $event->objects->transactionJournals->merge($repository->getAllUncompletedJournals());
return;
}
Log::debug(sprintf('Set count is %d', $set->count()));
Log::debug(sprintf('Transaction journal count is %d', $journals->count()));
if (!$event->flags->applyRules) {
Log::debug(sprintf('Will NOT process rules for %d journal(s)', $set->count()));
Log::debug(sprintf('Will NOT process rules for %d journal(s)', $journals->count()));
}
if (!$event->flags->recalculateCredit) {
Log::debug(sprintf('Will NOT recalculate credit for %d journal(s)', $set->count()));
Log::debug(sprintf('Will NOT recalculate credit for %d journal(s)', $journals->count()));
}
if (!$event->flags->fireWebhooks) {
Log::debug(sprintf('Will NOT fire webhooks for %d journal(s)', $set->count()));
Log::debug(sprintf('Will NOT fire webhooks for %d journal(s)', $journals->count()));
}
if ($event->flags->applyRules) {
$this->processRules($set);
$this->processRules($journals, 'store-journal');
}
if ($event->flags->recalculateCredit) {
$this->recalculateCredit($set);
$this->recalculateCredit($event->objects->accounts);
}
if ($event->flags->fireWebhooks) {
$this->fireWebhooks($set);
$this->fireWebhooks($journals, WebhookTrigger::STORE_TRANSACTION);
}
// always remove old relevant statistics.
self::removePeriodStatistics($set);
// recalculate running balance if necessary.
if (true === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
$this->recalculateRunningBalance($set);
}
$repository->markAsCompleted($set);
}
private function getFromInternalDate(array $ids): Carbon
{
$entries = TransactionJournalMeta::whereIn('transaction_journal_id', $ids)->where('name', '_internal_previous_date')->get(['journal_meta.*']);
$array = $entries->toArray();
$return = today()->subDay();
if (count($array) > 0) {
usort($array, function (array $a, array $b) {
return Carbon::parse($a['data'])->gt(Carbon::parse($b['data']));
});
$date = Carbon::parse($array[0]['data']);
$return = $date->lt($return) ? $date : $return;
}
return $return;
}
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();
$fromInternalDate = $this->getFromInternalDate($set->pluck('id')->toArray());
$earliest = $fromInternalDate->lt($earliest) ? $fromInternalDate : $earliest;
Log::debug(sprintf('Found earliest date: %s', $earliest->toW3cString()));
// 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.*'])
;
Log::debug('Found accounts to process', $accounts->pluck('id')->toArray());
AccountBalanceCalculator::optimizedCalculation($accounts, $earliest);
}
public static function removePeriodStatistics(Collection $set): void
{
if (auth()->check()) {
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__);
/** @var TransactionJournal $first */
$first = $set->first();
$user = $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();
/** @var TransactionJournal $first */
$first = $set->first();
$journalIds = implode(',', $array);
$user = $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();
$this->removePeriodStatistics($event->objects);
$this->recalculateRunningBalance($event->objects);
$repository->markAsCompleted($journals);
}
}

View File

@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Listeners\Model\TransactionGroup;
use Carbon\Carbon;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventObjects;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalMeta;
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\Support\Collection;
use Illuminate\Support\Facades\Log;
trait SupportsGroupProcessingTrait
{
private function recalculateCredit(Collection $accounts): void
{
Log::debug(sprintf('Will now recalculateCredit for %d account(s)', $accounts->count()));
/** @var CreditRecalculateService $object */
$object = app(CreditRecalculateService::class);
$object->setAccounts($accounts);
$object->recalculate();
}
private function fireWebhooks(Collection $journals, WebhookTrigger $trigger): void
{
// collect transaction groups by set ids.
$groups = TransactionGroup::whereIn('id', array_unique($journals->pluck('transaction_group_id')->toArray()))->get();
Log::debug(__METHOD__);
/** @var TransactionJournal $first */
$first = $journals->first();
$user = $first->user;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
// tell the generator which trigger it should look for
$engine->setTrigger($trigger);
// tell the generator which objects to process
$engine->setObjects($groups);
// tell the generator to generate the messages
$engine->generateMessages();
}
protected function removePeriodStatistics(TransactionGroupEventObjects $set): void
{
if (auth()->check()) {
Log::debug('Always remove period statistics');
/** @var PeriodStatisticRepositoryInterface $repository */
$repository = app(PeriodStatisticRepositoryInterface::class);
$repository->deleteStatisticsForCollection($set->transactionJournals);
// FIXME extend for categories, accounts, etc.
}
}
protected function processRules(Collection $set, string $type): void
{
Log::debug(sprintf('Will now processRules("%s") for %d journal(s)', $type, $set->count()));
$array = $set->pluck('id')->toArray();
/** @var TransactionJournal $first */
$first = $set->first();
$journalIds = implode(',', $array);
$user = $first->user;
Log::debug(sprintf('Add local operator for journal(s): %s', $journalIds));
// 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(sprintf('Fire processRules with ALL %s rule groups.', $type));
$groups = $ruleGroupRepository->getRuleGroupsWithRules($type);
// create and fire rule engine.
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($user);
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
$newRuleEngine->setRuleGroups($groups);
$newRuleEngine->fire();
}
protected function recalculateRunningBalance(TransactionGroupEventObjects $objects): void
{
if (true === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
return;
}
Log::debug('Now in recalculateRunningBalance');
// find the earliest date in the set, based on date and _internal_previous_date
$earliest = $objects->transactionJournals->pluck('date')->sort()->first();
$fromInternalDate = $this->getFromInternalDate($objects->transactionJournals->pluck('id')->toArray());
$earliest = $fromInternalDate->lt($earliest) ? $fromInternalDate : $earliest;
Log::debug(sprintf('Found earliest date: %s', $earliest->toW3cString()));
$accounts = Account::whereIn('id', $objects->accounts->pluck('id')->toArray())->get(['accounts.*']);
Log::debug('Found accounts to process', $accounts->pluck('id')->toArray());
AccountBalanceCalculator::optimizedCalculation($accounts, $earliest);
}
private function getFromInternalDate(array $ids): Carbon
{
$entries = TransactionJournalMeta::whereIn('transaction_journal_id', $ids)->where('name', '_internal_previous_date')->get(['journal_meta.*']);
$array = $entries->toArray();
$return = today()->subDay();
if (count($array) > 0) {
usort($array, function (array $a, array $b) {
return Carbon::parse($a['data'])->gt(Carbon::parse($b['data']));
});
$date = Carbon::parse($array[0]['data']);
$return = $date->lt($return) ? $date : $return;
}
return $return;
}
}

View File

@@ -27,6 +27,10 @@ namespace FireflyIII\Repositories\TransactionGroup;
use Carbon\Carbon;
use Exception;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventObjects;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionGroupFactory;
@@ -43,6 +47,7 @@ use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Services\Internal\Destroy\TransactionGroupDestroyService;
use FireflyIII\Services\Internal\Update\GroupUpdateService;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
@@ -391,12 +396,12 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
public function store(array $data): TransactionGroup
{
/** @var TransactionGroupFactory $factory */
$factory = app(TransactionGroupFactory::class);
$factory = app(TransactionGroupFactory::class);
$factory->setUser($data['user']);
$factory->setUserGroup($data['user_group']);
try {
return $factory->create($data);
$transactionGroup = $factory->create($data);
} catch (DuplicateTransactionException $e) {
Log::warning('Group repository caught group factory with a duplicate exception!');
@@ -408,6 +413,19 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
throw new FireflyException($e->getMessage(), 0, $e);
}
Preferences::mark();
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($transactionGroup);
$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($flags, $objects));
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
return $transactionGroup;
}
/**

View File

@@ -45,12 +45,14 @@ class CreditRecalculateService
private ?Account $account = null;
private ?TransactionGroup $group = null;
private Collection $journals;
private Collection $accounts;
private AccountRepositoryInterface $repository;
private array $work = [];
public function __construct()
{
$this->journals = new Collection();
$this->accounts = new Collection();
}
public function recalculate(): void
@@ -65,6 +67,9 @@ class CreditRecalculateService
// work based on account.
$this->processAccount();
}
if ($this->accounts->count() > 0) {
$this->processAccounts();
}
if ($this->journals->count() > 0) {
$this->processJournals();
}
@@ -206,7 +211,7 @@ class CreditRecalculateService
->orderBy('transaction_journals.date', 'ASC')
->get(['transactions.*'])
;
$transactions->count();
// $transactions->count();
// Log::debug(sprintf('Found %d transaction(s) to process.', $total));
/** @var Transaction $transaction */
@@ -493,4 +498,21 @@ class CreditRecalculateService
{
$this->journals = $journals;
}
private function processAccounts(): void
{
$valid = config('firefly.valid_liabilities');
/** @var Account $account */
foreach ($this->accounts as $account) {
if (in_array($account->accountType->type, $valid, true)) {
$this->work[] = $account;
}
}
}
public function setAccounts(Collection $accounts): void
{
$this->accounts = $accounts;
}
}

View File

@@ -24,6 +24,10 @@ declare(strict_types=1);
namespace FireflyIII\Services\Internal\Update;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventObjects;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Factory\PiggyBankEventFactory;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
@@ -34,6 +38,7 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalMeta;
use Illuminate\Support\Facades\Log;
/**
* Class GroupCloneService
@@ -48,6 +53,14 @@ class GroupCloneService
$this->cloneJournal($journal, $newGroup, $group->id);
}
// event!
$flags = new TransactionGroupEventFlags();
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($newGroup);
event(new CreatedSingleTransactionGroup($flags, $objects));
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
return $newGroup;
}

12
composer.lock generated
View File

@@ -10530,16 +10530,16 @@
},
{
"name": "fruitcake/laravel-debugbar",
"version": "v4.0.5",
"version": "v4.0.6",
"source": {
"type": "git",
"url": "https://github.com/fruitcake/laravel-debugbar.git",
"reference": "1da86437d28f36baf3bb9841d77e74cb639372a9"
"reference": "0cbf2986de59f66870cee565491b81eb89f8d25e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/1da86437d28f36baf3bb9841d77e74cb639372a9",
"reference": "1da86437d28f36baf3bb9841d77e74cb639372a9",
"url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/0cbf2986de59f66870cee565491b81eb89f8d25e",
"reference": "0cbf2986de59f66870cee565491b81eb89f8d25e",
"shasum": ""
},
"require": {
@@ -10616,7 +10616,7 @@
],
"support": {
"issues": "https://github.com/fruitcake/laravel-debugbar/issues",
"source": "https://github.com/fruitcake/laravel-debugbar/tree/v4.0.5"
"source": "https://github.com/fruitcake/laravel-debugbar/tree/v4.0.6"
},
"funding": [
{
@@ -10628,7 +10628,7 @@
"type": "github"
}
],
"time": "2026-01-29T19:18:02+00:00"
"time": "2026-02-04T11:48:53+00:00"
},
{
"name": "hamcrest/hamcrest-php",

View File

@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2026-02-04',
'build_time' => 1770188659,
'build_time' => 1770218409,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.