Merge pull request #10411 from firefly-iii/release-1748971941

🤖 Automatically merge the PR into the develop branch.
This commit is contained in:
github-actions[bot]
2025-06-03 19:32:29 +02:00
committed by GitHub
12 changed files with 119 additions and 116 deletions

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace FireflyIII\Events\Model\PiggyBank; namespace FireflyIII\Events\Model\PiggyBank;
use FireflyIII\Events\Event; use FireflyIII\Events\Event;
@@ -10,7 +12,5 @@ class ChangedName extends Event
{ {
use SerializesModels; use SerializesModels;
public function __construct(public PiggyBank $piggyBank, public string $oldName, public string $newName) public function __construct(public PiggyBank $piggyBank, public string $oldName, public string $newName) {}
{
}
} }

View File

@@ -37,16 +37,16 @@ use FireflyIII\Models\PiggyBankEvent;
*/ */
class PiggyBankEventHandler class PiggyBankEventHandler
{ {
public function changedPiggyBankName(ChangedName $event): void
public function changedPiggyBankName(ChangedName $event): void { {
// loop all accounts, collect all user's rules. // loop all accounts, collect all user's rules.
/** @var Account $account */ /** @var Account $account */
foreach($event->piggyBank->accounts as $account) { foreach ($event->piggyBank->accounts as $account) {
/** @var Rule $rule */ /** @var Rule $rule */
foreach($account->user->rules as $rule) { foreach ($account->user->rules as $rule) {
/** @var RuleAction $ruleAction */ /** @var RuleAction $ruleAction */
foreach($rule->ruleActions()->where('action_type', 'update_piggy')->get() as $ruleAction) { foreach ($rule->ruleActions()->where('action_type', 'update_piggy')->get() as $ruleAction) {
if($event->oldName === $ruleAction->action_value) { if ($event->oldName === $ruleAction->action_value) {
$ruleAction->action_value = $event->newName; $ruleAction->action_value = $event->newName;
$ruleAction->save(); $ruleAction->save();
} }
@@ -54,6 +54,7 @@ class PiggyBankEventHandler
} }
} }
} }
public function changePiggyAmount(ChangedAmount $event): void public function changePiggyAmount(ChangedAmount $event): void
{ {
// find journal if group is present. // find journal if group is present.

View File

@@ -78,17 +78,19 @@ class AmountController extends Controller
$leftToSave = bcsub($piggyBank->target_amount, $totalSaved); $leftToSave = bcsub($piggyBank->target_amount, $totalSaved);
$maxAmount = 0 === bccomp($piggyBank->target_amount, '0') ? $leftOnAccount : min($leftOnAccount, $leftToSave); $maxAmount = 0 === bccomp($piggyBank->target_amount, '0') ? $leftOnAccount : min($leftOnAccount, $leftToSave);
Log::debug(sprintf('Account "%s", left on account "%s", saved so far "%s", left to save "%s", max amount "%s".', Log::debug(sprintf(
'Account "%s", left on account "%s", saved so far "%s", left to save "%s", max amount "%s".',
$account->name, $account->name,
$leftOnAccount, $leftOnAccount,
$totalSaved, $totalSaved,
$leftToSave, $leftToSave,
$maxAmount,)); $maxAmount,
));
$accounts[] = [ $accounts[] = [
'account' => $account, 'account' => $account,
'left_on_account' => $leftOnAccount, 'left_on_account' => $leftOnAccount,
'total_saved' => $totalSaved, 'total_saved' => $totalSaved,
'left_to_save' => $leftToSave, 'left_to_save' => $leftToSave,
'max_amount' => $maxAmount, 'max_amount' => $maxAmount,
]; ];
@@ -107,9 +109,9 @@ class AmountController extends Controller
public function addMobile(PiggyBank $piggyBank) public function addMobile(PiggyBank $piggyBank)
{ {
/** @var Carbon $date */ /** @var Carbon $date */
$date = session('end', today(config('app.timezone'))); $date = session('end', today(config('app.timezone')));
$accounts = []; $accounts = [];
$total = '0'; $total = '0';
$totalSaved = $this->piggyRepos->getCurrentAmount($piggyBank); $totalSaved = $this->piggyRepos->getCurrentAmount($piggyBank);
foreach ($piggyBank->accounts as $account) { foreach ($piggyBank->accounts as $account) {
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $account, $date); $leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $account, $date);
@@ -118,7 +120,7 @@ class AmountController extends Controller
$accounts[] = [ $accounts[] = [
'account' => $account, 'account' => $account,
'left_on_account' => $leftOnAccount, 'left_on_account' => $leftOnAccount,
'total_saved' => $totalSaved, 'total_saved' => $totalSaved,
'left_to_save' => $leftToSave, 'left_to_save' => $leftToSave,
'max_amount' => $maxAmount, 'max_amount' => $maxAmount,
]; ];

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Requests; namespace FireflyIII\Http\Requests;
use Illuminate\Contracts\Validation\Validator; use Illuminate\Contracts\Validation\Validator;
use Carbon\Carbon;
use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ChecksLogin;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;

View File

@@ -211,7 +211,7 @@ class EventServiceProvider extends ServiceProvider
ChangedAmount::class => [ ChangedAmount::class => [
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changePiggyAmount', 'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changePiggyAmount',
], ],
ChangedName::class => [ ChangedName::class => [
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName', 'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName',
], ],

View File

@@ -65,15 +65,15 @@ trait ModifiesPiggyBanks
public function removeAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool public function removeAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool
{ {
$currentAmount = $this->getCurrentAmount($piggyBank, $account); $currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot; $pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcsub((string)$currentAmount, $amount); $pivot->current_amount = bcsub((string)$currentAmount, $amount);
$pivot->native_current_amount = null; $pivot->native_current_amount = null;
// also update native_current_amount. // also update native_current_amount.
$userCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup); $userCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
if ($userCurrency->id !== $piggyBank->transaction_currency_id) { if ($userCurrency->id !== $piggyBank->transaction_currency_id) {
$converter = new ExchangeRateConverter(); $converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true); $converter->setIgnoreSettings(true);
$pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount); $pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount);
} }
@@ -88,15 +88,15 @@ trait ModifiesPiggyBanks
public function addAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool public function addAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool
{ {
$currentAmount = $this->getCurrentAmount($piggyBank, $account); $currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot; $pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcadd((string)$currentAmount, $amount); $pivot->current_amount = bcadd((string)$currentAmount, $amount);
$pivot->native_current_amount = null; $pivot->native_current_amount = null;
// also update native_current_amount. // also update native_current_amount.
$userCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup); $userCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
if ($userCurrency->id !== $piggyBank->transaction_currency_id) { if ($userCurrency->id !== $piggyBank->transaction_currency_id) {
$converter = new ExchangeRateConverter(); $converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true); $converter->setIgnoreSettings(true);
$pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount); $pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount);
} }
@@ -112,10 +112,10 @@ trait ModifiesPiggyBanks
public function canAddAmount(PiggyBank $piggyBank, Account $account, string $amount): bool public function canAddAmount(PiggyBank $piggyBank, Account $account, string $amount): bool
{ {
Log::debug('Now in canAddAmount'); Log::debug('Now in canAddAmount');
$today = today(config('app.timezone'))->endOfDay(); $today = today(config('app.timezone'))->endOfDay();
$leftOnAccount = $this->leftOnAccount($piggyBank, $account, $today); $leftOnAccount = $this->leftOnAccount($piggyBank, $account, $today);
$savedSoFar = $this->getCurrentAmount($piggyBank); $savedSoFar = $this->getCurrentAmount($piggyBank);
$maxAmount = $leftOnAccount; $maxAmount = $leftOnAccount;
Log::debug(sprintf('Left on account: %s on %s', $leftOnAccount, $today->format('Y-m-d H:i:s'))); Log::debug(sprintf('Left on account: %s on %s', $leftOnAccount, $today->format('Y-m-d H:i:s')));
Log::debug(sprintf('Saved so far: %s', $savedSoFar)); Log::debug(sprintf('Saved so far: %s', $savedSoFar));
@@ -123,13 +123,13 @@ trait ModifiesPiggyBanks
if (0 !== bccomp($piggyBank->target_amount, '0')) { if (0 !== bccomp($piggyBank->target_amount, '0')) {
$leftToSave = bcsub($piggyBank->target_amount, (string)$savedSoFar); $leftToSave = bcsub($piggyBank->target_amount, (string)$savedSoFar);
$maxAmount = 1 === bccomp((string)$leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount; $maxAmount = 1 === bccomp((string)$leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
Log::debug(sprintf('Left to save: %s', $leftToSave)); Log::debug(sprintf('Left to save: %s', $leftToSave));
Log::debug(sprintf('Maximum amount: %s', $maxAmount)); Log::debug(sprintf('Maximum amount: %s', $maxAmount));
} }
$compare = bccomp($amount, (string)$maxAmount); $compare = bccomp($amount, (string)$maxAmount);
$result = $compare <= 0; $result = $compare <= 0;
Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true))); Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
@@ -163,15 +163,15 @@ trait ModifiesPiggyBanks
public function setCurrentAmount(PiggyBank $piggyBank, string $amount): PiggyBank public function setCurrentAmount(PiggyBank $piggyBank, string $amount): PiggyBank
{ {
$repetition = $this->getRepetition($piggyBank); $repetition = $this->getRepetition($piggyBank);
if (null === $repetition) { if (null === $repetition) {
return $piggyBank; return $piggyBank;
} }
$max = $piggyBank->target_amount; $max = $piggyBank->target_amount;
if (1 === bccomp($amount, $max) && 0 !== bccomp($piggyBank->target_amount, '0')) { if (1 === bccomp($amount, $max) && 0 !== bccomp($piggyBank->target_amount, '0')) {
$amount = $max; $amount = $max;
} }
$difference = bcsub($amount, (string)$repetition->current_amount); $difference = bcsub($amount, (string)$repetition->current_amount);
$repetition->current_amount = $amount; $repetition->current_amount = $amount;
$repetition->save(); $repetition->save();
@@ -202,7 +202,7 @@ trait ModifiesPiggyBanks
*/ */
public function store(array $data): PiggyBank public function store(array $data): PiggyBank
{ {
$factory = new PiggyBankFactory(); $factory = new PiggyBankFactory();
$factory->user = $this->user; $factory->user = $this->user;
return $factory->store($data); return $factory->store($data);
@@ -210,20 +210,20 @@ trait ModifiesPiggyBanks
public function update(PiggyBank $piggyBank, array $data): PiggyBank public function update(PiggyBank $piggyBank, array $data): PiggyBank
{ {
$piggyBank = $this->updateProperties($piggyBank, $data); $piggyBank = $this->updateProperties($piggyBank, $data);
if (array_key_exists('notes', $data)) { if (array_key_exists('notes', $data)) {
$this->updateNote($piggyBank, (string)$data['notes']); $this->updateNote($piggyBank, (string)$data['notes']);
} }
// update the order of the piggy bank: // update the order of the piggy bank:
$oldOrder = $piggyBank->order; $oldOrder = $piggyBank->order;
$newOrder = (int)($data['order'] ?? $oldOrder); $newOrder = (int)($data['order'] ?? $oldOrder);
if ($oldOrder !== $newOrder) { if ($oldOrder !== $newOrder) {
$this->setOrder($piggyBank, $newOrder); $this->setOrder($piggyBank, $newOrder);
} }
// update the accounts // update the accounts
$factory = new PiggyBankFactory(); $factory = new PiggyBankFactory();
$factory->user = $this->user; $factory->user = $this->user;
// the piggy bank currency is set or updated FIRST, if it exists. // the piggy bank currency is set or updated FIRST, if it exists.
@@ -300,11 +300,11 @@ trait ModifiesPiggyBanks
$piggyBank->target_amount = '0'; $piggyBank->target_amount = '0';
} }
if (array_key_exists('target_date', $data) && '' !== $data['target_date']) { if (array_key_exists('target_date', $data) && '' !== $data['target_date']) {
$piggyBank->target_date = $data['target_date']; $piggyBank->target_date = $data['target_date'];
$piggyBank->target_date_tz = $data['target_date']?->format('e'); $piggyBank->target_date_tz = $data['target_date']?->format('e');
} }
if (array_key_exists('start_date', $data)) { if (array_key_exists('start_date', $data)) {
$piggyBank->start_date = $data['start_date']; $piggyBank->start_date = $data['start_date'];
$piggyBank->start_date_tz = $data['target_date']?->format('e'); $piggyBank->start_date_tz = $data['target_date']?->format('e');
} }
$piggyBank->save(); $piggyBank->save();
@@ -320,7 +320,7 @@ trait ModifiesPiggyBanks
return; return;
} }
$dbNote = $piggyBank->notes()->first(); $dbNote = $piggyBank->notes()->first();
if (null === $dbNote) { if (null === $dbNote) {
$dbNote = new Note(); $dbNote = new Note();
$dbNote->noteable()->associate($piggyBank); $dbNote->noteable()->associate($piggyBank);
@@ -331,7 +331,7 @@ trait ModifiesPiggyBanks
public function setOrder(PiggyBank $piggyBank, int $newOrder): bool public function setOrder(PiggyBank $piggyBank, int $newOrder): bool
{ {
$oldOrder = $piggyBank->order; $oldOrder = $piggyBank->order;
// Log::debug(sprintf('Will move piggy bank #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder)); // Log::debug(sprintf('Will move piggy bank #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
if ($newOrder > $oldOrder) { if ($newOrder > $oldOrder) {
PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
@@ -339,7 +339,8 @@ trait ModifiesPiggyBanks
->where('accounts.user_id', $this->user->id) ->where('accounts.user_id', $this->user->id)
->where('piggy_banks.order', '<=', $newOrder)->where('piggy_banks.order', '>', $oldOrder) ->where('piggy_banks.order', '<=', $newOrder)->where('piggy_banks.order', '>', $oldOrder)
->where('piggy_banks.id', '!=', $piggyBank->id) ->where('piggy_banks.id', '!=', $piggyBank->id)
->distinct()->decrement('piggy_banks.order'); ->distinct()->decrement('piggy_banks.order')
;
$piggyBank->order = $newOrder; $piggyBank->order = $newOrder;
Log::debug(sprintf('[1] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder)); Log::debug(sprintf('[1] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
@@ -352,7 +353,8 @@ trait ModifiesPiggyBanks
->where('accounts.user_id', $this->user->id) ->where('accounts.user_id', $this->user->id)
->where('piggy_banks.order', '>=', $newOrder)->where('piggy_banks.order', '<', $oldOrder) ->where('piggy_banks.order', '>=', $newOrder)->where('piggy_banks.order', '<', $oldOrder)
->where('piggy_banks.id', '!=', $piggyBank->id) ->where('piggy_banks.id', '!=', $piggyBank->id)
->distinct()->increment('piggy_banks.order'); ->distinct()->increment('piggy_banks.order')
;
$piggyBank->order = $newOrder; $piggyBank->order = $newOrder;
Log::debug(sprintf('[2] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder)); Log::debug(sprintf('[2] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
@@ -373,7 +375,7 @@ trait ModifiesPiggyBanks
} }
// if this account contains less than the amount, remove the current amount, update the amount and continue. // if this account contains less than the amount, remove the current amount, update the amount and continue.
$this->removeAmount($piggyBank, $account, $current); $this->removeAmount($piggyBank, $account, $current);
$amount = bcsub($amount, (string)$current); $amount = bcsub($amount, (string)$current);
} }
} }
} }

View File

@@ -84,7 +84,7 @@ class ExchangeRateConverter
return '1'; return '1';
} }
if ($from->id === $to->id) { if ($from->id === $to->id) {
// Log::debug('ExchangeRateConverter: From and to are the same, return "1".'); // Log::debug('ExchangeRateConverter: From and to are the same, return "1".');
return '1'; return '1';
} }

View File

@@ -40,9 +40,7 @@ class UpdatePiggyBank implements ActionInterface
/** /**
* TriggerInterface constructor. * TriggerInterface constructor.
*/ */
public function __construct(private readonly RuleAction $action) public function __construct(private readonly RuleAction $action) {}
{
}
public function actOnArray(array $journal): bool public function actOnArray(array $journal): bool
{ {
@@ -52,12 +50,12 @@ class UpdatePiggyBank implements ActionInterface
// refresh the transaction type. // refresh the transaction type.
/** @var User $user */ /** @var User $user */
$user = User::find($journal['user_id']); $user = User::find($journal['user_id']);
/** @var TransactionJournal $journalObj */ /** @var TransactionJournal $journalObj */
$journalObj = $user->transactionJournals()->find($journal['transaction_journal_id']); $journalObj = $user->transactionJournals()->find($journal['transaction_journal_id']);
$piggyBank = $this->findPiggyBank($user, $actionValue); $piggyBank = $this->findPiggyBank($user, $actionValue);
if (!$piggyBank instanceof PiggyBank) { if (!$piggyBank instanceof PiggyBank) {
Log::info( Log::info(
sprintf('No piggy bank named "%s", cant execute action #%d of rule #%d', $actionValue, $this->action->id, $this->action->rule_id) sprintf('No piggy bank named "%s", cant execute action #%d of rule #%d', $actionValue, $this->action->id, $this->action->rule_id)
@@ -74,6 +72,7 @@ class UpdatePiggyBank implements ActionInterface
Log::info(sprintf('Piggy bank #%d ("%s") already has an event for transaction journal #%d, so no action will be taken.', $piggyBank->id, $piggyBank->name, $journalObj->id)); Log::info(sprintf('Piggy bank #%d ("%s") already has an event for transaction journal #%d, so no action will be taken.', $piggyBank->id, $piggyBank->name, $journalObj->id));
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_piggy', ['name' => $actionValue]))); event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_piggy', ['name' => $actionValue])));
return false; return false;
} }
@@ -81,7 +80,7 @@ class UpdatePiggyBank implements ActionInterface
/** @var Transaction $destination */ /** @var Transaction $destination */
$destination = $journalObj->transactions()->where('amount', '>', 0)->first(); $destination = $journalObj->transactions()->where('amount', '>', 0)->first();
$accounts = $this->getAccounts($journalObj); $accounts = $this->getAccounts($journalObj);
Log::debug(sprintf('Source account is #%d: "%s"', $accounts['source']->id, $accounts['source']->name)); Log::debug(sprintf('Source account is #%d: "%s"', $accounts['source']->id, $accounts['source']->name));
Log::debug(sprintf('Destination account is #%d: "%s"', $accounts['destination']->id, $accounts['source']->name)); Log::debug(sprintf('Destination account is #%d: "%s"', $accounts['destination']->id, $accounts['source']->name));
@@ -97,9 +96,9 @@ class UpdatePiggyBank implements ActionInterface
null, null,
[ [
'currency_symbol' => $journalObj->transactionCurrency->symbol, 'currency_symbol' => $journalObj->transactionCurrency->symbol,
'decimal_places' => $journalObj->transactionCurrency->decimal_places, 'decimal_places' => $journalObj->transactionCurrency->decimal_places,
'amount' => $destination->amount, 'amount' => $destination->amount,
'piggy' => $piggyBank->name, 'piggy' => $piggyBank->name,
] ]
) )
); );
@@ -120,10 +119,10 @@ class UpdatePiggyBank implements ActionInterface
null, null,
[ [
'currency_symbol' => $journalObj->transactionCurrency->symbol, 'currency_symbol' => $journalObj->transactionCurrency->symbol,
'decimal_places' => $journalObj->transactionCurrency->decimal_places, 'decimal_places' => $journalObj->transactionCurrency->decimal_places,
'amount' => $destination->amount, 'amount' => $destination->amount,
'piggy' => $piggyBank->name, 'piggy' => $piggyBank->name,
'piggy_id' => $piggyBank->id, 'piggy_id' => $piggyBank->id,
] ]
) )
); );
@@ -154,7 +153,7 @@ class UpdatePiggyBank implements ActionInterface
private function getAccounts(TransactionJournal $journal): array private function getAccounts(TransactionJournal $journal): array
{ {
return [ return [
'source' => $journal->transactions()->where('amount', '<', '0')->first()?->account, 'source' => $journal->transactions()->where('amount', '<', '0')->first()?->account,
'destination' => $journal->transactions()->where('amount', '>', '0')->first()?->account, 'destination' => $journal->transactions()->where('amount', '>', '0')->first()?->account,
]; ];
} }
@@ -180,11 +179,11 @@ class UpdatePiggyBank implements ActionInterface
$repository->setUser($journal->user); $repository->setUser($journal->user);
// how much can we remove from this piggy bank? // how much can we remove from this piggy bank?
$toRemove = $repository->getCurrentAmount($piggyBank, $account); $toRemove = $repository->getCurrentAmount($piggyBank, $account);
Log::debug(sprintf('Amount is %s, max to remove is %s', $amount, $toRemove)); Log::debug(sprintf('Amount is %s, max to remove is %s', $amount, $toRemove));
// if $amount is bigger than $toRemove, shrink it. // if $amount is bigger than $toRemove, shrink it.
$amount = -1 === bccomp($amount, $toRemove) ? $amount : $toRemove; $amount = -1 === bccomp($amount, $toRemove) ? $amount : $toRemove;
Log::debug(sprintf('Amount is now %s', $amount)); Log::debug(sprintf('Amount is now %s', $amount));
// if amount is zero, stop. // if amount is zero, stop.
@@ -213,7 +212,7 @@ class UpdatePiggyBank implements ActionInterface
// how much can we add to the piggy bank? // how much can we add to the piggy bank?
if (0 !== bccomp($piggyBank->target_amount, '0')) { if (0 !== bccomp($piggyBank->target_amount, '0')) {
$toAdd = bcsub($piggyBank->target_amount, $repository->getCurrentAmount($piggyBank, $account)); $toAdd = bcsub($piggyBank->target_amount, $repository->getCurrentAmount($piggyBank, $account));
Log::debug(sprintf('Max amount to add to piggy bank is %s, amount is %s', $toAdd, $amount)); Log::debug(sprintf('Max amount to add to piggy bank is %s, amount is %s', $toAdd, $amount));
// update amount to fit: // update amount to fit:

36
composer.lock generated
View File

@@ -129,16 +129,16 @@
}, },
{ {
"name": "brick/math", "name": "brick/math",
"version": "0.12.3", "version": "0.13.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/brick/math.git", "url": "https://github.com/brick/math.git",
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04",
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -177,7 +177,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/brick/math/issues", "issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.12.3" "source": "https://github.com/brick/math/tree/0.13.1"
}, },
"funding": [ "funding": [
{ {
@@ -185,7 +185,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-02-28T13:11:00+00:00" "time": "2025-03-29T13:50:30+00:00"
}, },
{ {
"name": "carbonphp/carbon-doctrine-types", "name": "carbonphp/carbon-doctrine-types",
@@ -1879,20 +1879,20 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v12.16.0", "version": "v12.17.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "293bb1c70224faebfd3d4328e201c37115da055f" "reference": "8729d084510480fdeec9b6ad198180147d4a7f06"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/293bb1c70224faebfd3d4328e201c37115da055f", "url": "https://api.github.com/repos/laravel/framework/zipball/8729d084510480fdeec9b6ad198180147d4a7f06",
"reference": "293bb1c70224faebfd3d4328e201c37115da055f", "reference": "8729d084510480fdeec9b6ad198180147d4a7f06",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"brick/math": "^0.11|^0.12", "brick/math": "^0.11|^0.12|^0.13",
"composer-runtime-api": "^2.2", "composer-runtime-api": "^2.2",
"doctrine/inflector": "^2.0.5", "doctrine/inflector": "^2.0.5",
"dragonmantank/cron-expression": "^3.4", "dragonmantank/cron-expression": "^3.4",
@@ -2090,7 +2090,7 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "source": "https://github.com/laravel/framework"
}, },
"time": "2025-05-27T15:49:44+00:00" "time": "2025-06-03T14:04:18+00:00"
}, },
{ {
"name": "laravel/passport", "name": "laravel/passport",
@@ -3880,16 +3880,16 @@
}, },
{ {
"name": "nette/utils", "name": "nette/utils",
"version": "v4.0.6", "version": "v4.0.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nette/utils.git", "url": "https://github.com/nette/utils.git",
"reference": "ce708655043c7050eb050df361c5e313cf708309" "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309", "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2",
"reference": "ce708655043c7050eb050df361c5e313cf708309", "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -3960,9 +3960,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nette/utils/issues", "issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v4.0.6" "source": "https://github.com/nette/utils/tree/v4.0.7"
}, },
"time": "2025-03-30T21:06:30+00:00" "time": "2025-06-03T04:55:08+00:00"
}, },
{ {
"name": "nunomaduro/collision", "name": "nunomaduro/collision",

View File

@@ -78,7 +78,7 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false), 'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag. // see cer.php for exchange rates feature flag.
], ],
'version' => 'develop/2025-06-02', 'version' => 'develop/2025-06-03',
'api_version' => '2.1.0', // field is no longer used. 'api_version' => '2.1.0', // field is no longer used.
'db_version' => 25, 'db_version' => 25,

56
package-lock.json generated
View File

@@ -43,9 +43,9 @@
} }
}, },
"node_modules/@babel/compat-data": { "node_modules/@babel/compat-data": {
"version": "7.27.3", "version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz",
"integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -94,13 +94,13 @@
} }
}, },
"node_modules/@babel/generator": { "node_modules/@babel/generator": {
"version": "7.27.3", "version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz",
"integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.27.3", "@babel/parser": "^7.27.5",
"@babel/types": "^7.27.3", "@babel/types": "^7.27.3",
"@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25", "@jridgewell/trace-mapping": "^0.3.25",
@@ -406,9 +406,9 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.27.4", "version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
"integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -683,9 +683,9 @@
} }
}, },
"node_modules/@babel/plugin-transform-block-scoping": { "node_modules/@babel/plugin-transform-block-scoping": {
"version": "7.27.3", "version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.3.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz",
"integrity": "sha512-+F8CnfhuLhwUACIJMLWnjz6zvzYM2r0yeIHKlbgfw7ml8rOMJsXNXV/hyRcb3nb493gRs4WvYpQAndWj/qQmkQ==", "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1255,9 +1255,9 @@
} }
}, },
"node_modules/@babel/plugin-transform-regenerator": { "node_modules/@babel/plugin-transform-regenerator": {
"version": "7.27.4", "version": "7.27.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.4.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz",
"integrity": "sha512-Glp/0n8xuj+E1588otw5rjJkTXfzW7FjH3IIUrfqiZOPQCd2vbg8e+DQE8jK9g4V5/zrxFW+D9WM9gboRPELpQ==", "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -5632,9 +5632,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.161", "version": "1.5.162",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.162.tgz",
"integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", "integrity": "sha512-hQA+Zb5QQwoSaXJWEAGEw1zhk//O7qDzib05Z4qTqZfNju/FAkrm5ZInp0JbTp4Z18A6bilopdZWEYrFSsfllA==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@@ -7713,9 +7713,9 @@
} }
}, },
"node_modules/laravel-vite-plugin": { "node_modules/laravel-vite-plugin": {
"version": "1.2.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.2.0.tgz", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.3.0.tgz",
"integrity": "sha512-R0pJ+IcTVeqEMoKz/B2Ij57QVq3sFTABiFmb06gAwFdivbOgsUtuhX6N2MGLEArajrS3U5JbberzwOe7uXHMHQ==", "integrity": "sha512-P5qyG56YbYxM8OuYmK2OkhcKe0AksNVJUjq9LUZ5tOekU9fBn9LujYyctI4t9XoLjuMvHJXXpCoPntY1oKltuA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -10414,9 +10414,9 @@
} }
}, },
"node_modules/shell-quote": { "node_modules/shell-quote": {
"version": "1.8.2", "version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
"integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -12081,9 +12081,9 @@
} }
}, },
"node_modules/webpack/node_modules/webpack-sources": { "node_modules/webpack/node_modules/webpack-sources": {
"version": "3.3.0", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.0.tgz", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.2.tgz",
"integrity": "sha512-77R0RDmJfj9dyv5p3bM5pOHa+X8/ZkO9c7kpDstigkC4nIDobadsfSGCwB4bKhMVxqAok8tajaoR8rirM7+VFQ==", "integrity": "sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {

View File

@@ -68,7 +68,7 @@ return [
'cannot_find_piggy' => 'Firefly III can\'t find a piggy bank named ":name"', 'cannot_find_piggy' => 'Firefly III can\'t find a piggy bank named ":name"',
'no_link_piggy' => 'This transaction\'s accounts are not linked to the piggy bank, so no action will be taken', 'no_link_piggy' => 'This transaction\'s accounts are not linked to the piggy bank, so no action will be taken',
'both_link_piggy' => 'This transaction\'s accounts are both linked to the piggy bank, so no action will be taken', 'both_link_piggy' => 'This transaction\'s accounts are both linked to the piggy bank, so no action will be taken',
'already_linked' => 'This transaction is already linked to piggy bank ":name"', 'already_linked' => 'This transaction is already linked to piggy bank ":name"',
'cannot_unlink_tag' => 'Tag ":tag" isn\'t linked to this transaction', 'cannot_unlink_tag' => 'Tag ":tag" isn\'t linked to this transaction',
'cannot_find_budget' => 'Firefly III can\'t find budget ":name"', 'cannot_find_budget' => 'Firefly III can\'t find budget ":name"',
'cannot_find_category' => 'Firefly III can\'t find category ":name"', 'cannot_find_category' => 'Firefly III can\'t find category ":name"',