Make sure running balance also updates when transactions are removed.

This commit is contained in:
James Cole
2025-05-02 06:45:34 +02:00
parent b99a6a9fc9
commit 3924781797
5 changed files with 105 additions and 79 deletions

View File

@@ -27,15 +27,24 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Enums\WebhookTrigger; use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\DestroyedTransactionGroup; use FireflyIII\Events\DestroyedTransactionGroup;
use FireflyIII\Events\RequestedSendWebhookMessages; use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface; use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class DestroyedGroupEventHandler * Class DestroyedGroupEventHandler
*/ */
class DestroyedGroupEventHandler class DestroyedGroupEventHandler
{ {
public function triggerWebhooks(DestroyedTransactionGroup $destroyedGroupEvent): void public function runAllHandlers(DestroyedTransactionGroup $event): void
{
$this->triggerWebhooks($event);
$this->updateRunningBalance($event);
}
private function triggerWebhooks(DestroyedTransactionGroup $destroyedGroupEvent): void
{ {
app('log')->debug('DestroyedTransactionGroup:triggerWebhooks'); app('log')->debug('DestroyedTransactionGroup:triggerWebhooks');
$group = $destroyedGroupEvent->transactionGroup; $group = $destroyedGroupEvent->transactionGroup;
@@ -50,4 +59,13 @@ class DestroyedGroupEventHandler
event(new RequestedSendWebhookMessages()); event(new RequestedSendWebhookMessages());
} }
private function updateRunningBalance(DestroyedTransactionGroup $event): void
{
Log::debug(__METHOD__);
$group = $event->transactionGroup;
foreach ($group->transactionJournals as $journal) {
AccountBalanceCalculator::recalculateForJournal($journal);
}
}
} }

View File

@@ -33,8 +33,10 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Services\Internal\Support\CreditRecalculateService; use FireflyIII\Services\Internal\Support\CreditRecalculateService;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface; use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class UpdatedGroupEventHandler * Class UpdatedGroupEventHandler
@@ -47,6 +49,7 @@ class UpdatedGroupEventHandler
$this->processRules($event); $this->processRules($event);
$this->recalculateCredit($event); $this->recalculateCredit($event);
$this->triggerWebhooks($event); $this->triggerWebhooks($event);
$this->updateRunningBalance($event);
} }
@@ -56,7 +59,7 @@ class UpdatedGroupEventHandler
private function processRules(UpdatedTransactionGroup $updatedGroupEvent): void private function processRules(UpdatedTransactionGroup $updatedGroupEvent): void
{ {
if (false === $updatedGroupEvent->applyRules) { if (false === $updatedGroupEvent->applyRules) {
app('log')->info(sprintf('Will not run rules on group #%d', $updatedGroupEvent->transactionGroup->id)); Log::info(sprintf('Will not run rules on group #%d', $updatedGroupEvent->transactionGroup->id));
return; return;
} }
@@ -69,7 +72,7 @@ class UpdatedGroupEventHandler
$array[] = $journal->id; $array[] = $journal->id;
} }
$journalIds = implode(',', $array); $journalIds = implode(',', $array);
app('log')->debug(sprintf('Add local operator for journal(s): %s', $journalIds)); Log::debug(sprintf('Add local operator for journal(s): %s', $journalIds));
// collect rules: // collect rules:
$ruleGroupRepository = app(RuleGroupRepositoryInterface::class); $ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
@@ -97,10 +100,10 @@ class UpdatedGroupEventHandler
private function triggerWebhooks(UpdatedTransactionGroup $updatedGroupEvent): void private function triggerWebhooks(UpdatedTransactionGroup $updatedGroupEvent): void
{ {
app('log')->debug(__METHOD__); Log::debug(__METHOD__);
$group = $updatedGroupEvent->transactionGroup; $group = $updatedGroupEvent->transactionGroup;
if (false === $updatedGroupEvent->fireWebhooks) { if (false === $updatedGroupEvent->fireWebhooks) {
app('log')->info(sprintf('Will not fire webhooks for transaction group #%d', $group->id)); Log::info(sprintf('Will not fire webhooks for transaction group #%d', $group->id));
return; return;
} }
@@ -133,11 +136,10 @@ class UpdatedGroupEventHandler
->orderBy('transaction_journals.order', 'ASC') ->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC') ->orderBy('transaction_journals.id', 'DESC')
->orderBy('transaction_journals.description', 'DESC') ->orderBy('transaction_journals.description', 'DESC')
->first() ->first();
;
if (null === $first) { if (null === $first) {
app('log')->warning(sprintf('Group #%d has no transaction journals.', $group->id)); Log::warning(sprintf('Group #%d has no transaction journals.', $group->id));
return; return;
} }
@@ -154,14 +156,21 @@ class UpdatedGroupEventHandler
if (TransactionTypeEnum::TRANSFER->value === $type || TransactionTypeEnum::WITHDRAWAL->value === $type) { if (TransactionTypeEnum::TRANSFER->value === $type || TransactionTypeEnum::WITHDRAWAL->value === $type) {
// set all source transactions to source account: // set all source transactions to source account:
Transaction::whereIn('transaction_journal_id', $all) Transaction::whereIn('transaction_journal_id', $all)
->where('amount', '<', 0)->update(['account_id' => $sourceAccount->id]) ->where('amount', '<', 0)->update(['account_id' => $sourceAccount->id]);
;
} }
if (TransactionTypeEnum::TRANSFER->value === $type || TransactionTypeEnum::DEPOSIT->value === $type) { if (TransactionTypeEnum::TRANSFER->value === $type || TransactionTypeEnum::DEPOSIT->value === $type) {
// set all destination transactions to destination account: // set all destination transactions to destination account:
Transaction::whereIn('transaction_journal_id', $all) Transaction::whereIn('transaction_journal_id', $all)
->where('amount', '>', 0)->update(['account_id' => $destAccount->id]) ->where('amount', '>', 0)->update(['account_id' => $destAccount->id]);
; }
}
private function updateRunningBalance(UpdatedTransactionGroup $event): void
{
Log::debug(__METHOD__);
$group = $event->transactionGroup;
foreach ($group->transactionJournals as $journal) {
AccountBalanceCalculator::recalculateForJournal($journal);
} }
} }
} }

View File

@@ -176,7 +176,7 @@ class EventServiceProvider extends ServiceProvider
'FireflyIII\Handlers\Events\UpdatedGroupEventHandler@runAllHandlers', 'FireflyIII\Handlers\Events\UpdatedGroupEventHandler@runAllHandlers',
], ],
DestroyedTransactionGroup::class => [ DestroyedTransactionGroup::class => [
'FireflyIII\Handlers\Events\DestroyedGroupEventHandler@triggerWebhooks', 'FireflyIII\Handlers\Events\DestroyedGroupEventHandler@runAllHandlers',
], ],
// API related events: // API related events:
AccessTokenCreated::class => [ AccessTokenCreated::class => [

View File

@@ -31,6 +31,7 @@ use FireflyIII\Factory\TransactionJournalFactory;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Services\Internal\Destroy\JournalDestroyService; use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use Illuminate\Support\Facades\Log;
/** /**
* Class GroupUpdateService * Class GroupUpdateService
@@ -45,14 +46,14 @@ class GroupUpdateService
*/ */
public function update(TransactionGroup $transactionGroup, array $data): TransactionGroup public function update(TransactionGroup $transactionGroup, array $data): TransactionGroup
{ {
app('log')->debug(sprintf('Now in %s', __METHOD__)); Log::debug(sprintf('Now in %s', __METHOD__));
app('log')->debug('Now in group update service', $data); Log::debug('Now in group update service', $data);
/** @var array $transactions */ /** @var array $transactions */
$transactions = $data['transactions'] ?? []; $transactions = $data['transactions'] ?? [];
// update group name. // update group name.
if (array_key_exists('group_title', $data)) { if (array_key_exists('group_title', $data)) {
app('log')->debug(sprintf('Update transaction group #%d title.', $transactionGroup->id)); Log::debug(sprintf('Update transaction group #%d title.', $transactionGroup->id));
$oldTitle = $transactionGroup->title; $oldTitle = $transactionGroup->title;
$transactionGroup->title = $data['group_title']; $transactionGroup->title = $data['group_title'];
$transactionGroup->save(); $transactionGroup->save();
@@ -68,7 +69,7 @@ class GroupUpdateService
} }
if (0 === count($transactions)) { if (0 === count($transactions)) {
app('log')->debug('No transactions submitted, do nothing.'); Log::debug('No transactions submitted, do nothing.');
return $transactionGroup; return $transactionGroup;
} }
@@ -76,7 +77,7 @@ class GroupUpdateService
if (1 === count($transactions) && 1 === $transactionGroup->transactionJournals()->count()) { if (1 === count($transactions) && 1 === $transactionGroup->transactionJournals()->count()) {
/** @var TransactionJournal $first */ /** @var TransactionJournal $first */
$first = $transactionGroup->transactionJournals()->first(); $first = $transactionGroup->transactionJournals()->first();
app('log')->debug( Log::debug(
sprintf('Will now update journal #%d (only journal in group #%d)', $first->id, $transactionGroup->id) sprintf('Will now update journal #%d (only journal in group #%d)', $first->id, $transactionGroup->id)
); );
$this->updateTransactionJournal($transactionGroup, $first, reset($transactions)); $this->updateTransactionJournal($transactionGroup, $first, reset($transactions));
@@ -87,14 +88,14 @@ class GroupUpdateService
return $transactionGroup; return $transactionGroup;
} }
app('log')->debug('Going to update split group.'); Log::debug('Going to update split group.');
$existing = $transactionGroup->transactionJournals->pluck('id')->toArray(); $existing = $transactionGroup->transactionJournals->pluck('id')->toArray();
$updated = $this->updateTransactions($transactionGroup, $transactions); $updated = $this->updateTransactions($transactionGroup, $transactions);
app('log')->debug('Array of updated IDs: ', $updated); Log::debug('Array of updated IDs: ', $updated);
if (0 === count($updated)) { if (0 === count($updated)) {
app('log')->error('There were no transactions updated or created. Will not delete anything.'); Log::error('There were no transactions updated or created. Will not delete anything.');
$transactionGroup->touch(); $transactionGroup->touch();
$transactionGroup->refresh(); $transactionGroup->refresh();
app('preferences')->mark(); app('preferences')->mark();
@@ -103,7 +104,7 @@ class GroupUpdateService
} }
$result = array_diff($existing, $updated); $result = array_diff($existing, $updated);
app('log')->debug('Result of DIFF: ', $result); Log::debug('Result of DIFF: ', $result);
if (count($result) > 0) { if (count($result) > 0) {
/** @var string $deletedId */ /** @var string $deletedId */
foreach ($result as $deletedId) { foreach ($result as $deletedId) {
@@ -131,7 +132,7 @@ class GroupUpdateService
TransactionJournal $journal, TransactionJournal $journal,
array $data array $data
): void { ): void {
app('log')->debug(sprintf('Now in %s', __METHOD__)); Log::debug(sprintf('Now in %s', __METHOD__));
if (0 === count($data)) { if (0 === count($data)) {
return; return;
} }
@@ -153,7 +154,7 @@ class GroupUpdateService
*/ */
private function updateTransactions(TransactionGroup $transactionGroup, array $transactions): array private function updateTransactions(TransactionGroup $transactionGroup, array $transactions): array
{ {
app('log')->debug(sprintf('Now in %s', __METHOD__)); Log::debug(sprintf('Now in %s', __METHOD__));
// updated or created transaction journals: // updated or created transaction journals:
$updated = []; $updated = [];
@@ -162,17 +163,17 @@ class GroupUpdateService
* @var array $transaction * @var array $transaction
*/ */
foreach ($transactions as $index => $transaction) { foreach ($transactions as $index => $transaction) {
app('log')->debug(sprintf('Now at #%d of %d', $index + 1, count($transactions)), $transaction); Log::debug(sprintf('Now at #%d of %d', $index + 1, count($transactions)), $transaction);
$journalId = (int) ($transaction['transaction_journal_id'] ?? 0); $journalId = (int) ($transaction['transaction_journal_id'] ?? 0);
/** @var null|TransactionJournal $journal */ /** @var null|TransactionJournal $journal */
$journal = $transactionGroup->transactionJournals()->find($journalId); $journal = $transactionGroup->transactionJournals()->find($journalId);
if (null === $journal) { if (null === $journal) {
app('log')->debug('This entry has no existing journal: make a new split.'); Log::debug('This entry has no existing journal: make a new split.');
// force the transaction type on the transaction data. // force the transaction type on the transaction data.
// by plucking it from another journal in the group: // by plucking it from another journal in the group:
if (!array_key_exists('type', $transaction)) { if (!array_key_exists('type', $transaction)) {
app('log')->debug('No transaction type is indicated.'); Log::debug('No transaction type is indicated.');
/** @var null|TransactionJournal $randomJournal */ /** @var null|TransactionJournal $randomJournal */
$randomJournal = $transactionGroup->transactionJournals()->inRandomOrder()->with( $randomJournal = $transactionGroup->transactionJournals()->inRandomOrder()->with(
@@ -180,24 +181,24 @@ class GroupUpdateService
)->first(); )->first();
if (null !== $randomJournal) { if (null !== $randomJournal) {
$transaction['type'] = $randomJournal->transactionType->type; $transaction['type'] = $randomJournal->transactionType->type;
app('log')->debug(sprintf('Transaction type set to %s.', $transaction['type'])); Log::debug(sprintf('Transaction type set to %s.', $transaction['type']));
} }
} }
app('log')->debug('Call createTransactionJournal'); Log::debug('Call createTransactionJournal');
$newJournal = $this->createTransactionJournal($transactionGroup, $transaction); $newJournal = $this->createTransactionJournal($transactionGroup, $transaction);
app('log')->debug('Done calling createTransactionJournal'); Log::debug('Done calling createTransactionJournal');
if (null !== $newJournal) { if (null !== $newJournal) {
$updated[] = $newJournal->id; $updated[] = $newJournal->id;
} }
if (null === $newJournal) { if (null === $newJournal) {
app('log')->error('createTransactionJournal returned NULL, indicating something went wrong.'); Log::error('createTransactionJournal returned NULL, indicating something went wrong.');
} }
} }
if (null !== $journal) { if (null !== $journal) {
app('log')->debug('Call updateTransactionJournal'); Log::debug('Call updateTransactionJournal');
$this->updateTransactionJournal($transactionGroup, $journal, $transaction); $this->updateTransactionJournal($transactionGroup, $journal, $transaction);
$updated[] = $journal->id; $updated[] = $journal->id;
app('log')->debug('Done calling updateTransactionJournal'); Log::debug('Done calling updateTransactionJournal');
} }
} }
@@ -223,8 +224,8 @@ class GroupUpdateService
try { try {
$collection = $factory->create($submission); $collection = $factory->create($submission);
} catch (FireflyException $e) { } catch (FireflyException $e) {
app('log')->error($e->getMessage()); Log::error($e->getMessage());
app('log')->error($e->getTraceAsString()); Log::error($e->getTraceAsString());
throw new FireflyException( throw new FireflyException(
sprintf('Could not create new transaction journal: %s', $e->getMessage()), sprintf('Could not create new transaction journal: %s', $e->getMessage()),

View File

@@ -78,8 +78,7 @@ class AccountBalanceCalculator
->orderBy('transaction_journals.order', 'desc') ->orderBy('transaction_journals.order', 'desc')
->orderBy('transaction_journals.id', 'asc') ->orderBy('transaction_journals.id', 'asc')
->orderBy('transaction_journals.description', 'asc') ->orderBy('transaction_journals.description', 'asc')
->orderBy('transactions.amount', 'asc') ->orderBy('transactions.amount', 'asc');
;
if ($accounts->count() > 0) { if ($accounts->count() > 0) {
$query->whereIn('transactions.account_id', $accounts->pluck('id')->toArray()); $query->whereIn('transactions.account_id', $accounts->pluck('id')->toArray());
} }
@@ -138,8 +137,7 @@ class AccountBalanceCalculator
->orderBy('transaction_journals.id', 'DESC') ->orderBy('transaction_journals.id', 'DESC')
->orderBy('transaction_journals.description', 'DESC') ->orderBy('transaction_journals.description', 'DESC')
->orderBy('transactions.amount', 'DESC') ->orderBy('transactions.amount', 'DESC')
->where('transactions.account_id', $accountId) ->where('transactions.account_id', $accountId);
;
$notBefore->startOfDay(); $notBefore->startOfDay();
$query->where('transaction_journals.date', '<', $notBefore); $query->where('transaction_journals.date', '<', $notBefore);
@@ -201,11 +199,11 @@ class AccountBalanceCalculator
Log::debug(__METHOD__); Log::debug(__METHOD__);
$object = new self(); $object = new self();
// recalculate the involved accounts: $set = [];
$accounts = new Collection();
foreach ($transactionJournal->transactions as $transaction) { foreach ($transactionJournal->transactions as $transaction) {
$accounts->push($transaction->account); $set[$transaction->account_id] = $transaction->account;
} }
$accounts = new Collection($set);
$object->optimizedCalculation($accounts, $transactionJournal->date); $object->optimizedCalculation($accounts, $transactionJournal->date);
} }
} }