Compare commits

..

14 Commits

Author SHA1 Message Date
github-actions[bot]
9c195dcc66 Merge pull request #10416 from firefly-iii/release-1749057506
🤖 Automatically merge the PR into the develop branch.
2025-06-04 19:18:33 +02:00
JC5
0b45506e52 🤖 Auto commit for release 'develop' on 2025-06-04 2025-06-04 19:18:26 +02:00
James Cole
51e58f8d88 Fix #10413 2025-06-04 15:40:29 +02:00
James Cole
d0c658e79a Update message to include IP 2025-06-03 20:00:13 +02:00
github-actions[bot]
35e0791a9f Merge pull request #10411 from firefly-iii/release-1748971941
🤖 Automatically merge the PR into the develop branch.
2025-06-03 19:32:29 +02:00
JC5
637ecc66d2 🤖 Auto commit for release 'develop' on 2025-06-03 2025-06-03 19:32:21 +02:00
James Cole
3a38175b2a Fix renamed variable. 2025-06-03 19:26:07 +02:00
James Cole
d78fd86d7a Catch already linked transactions. 2025-06-02 20:46:09 +02:00
James Cole
395332d6dd Fix #10403 2025-06-02 20:32:58 +02:00
James Cole
c5cbceb81a Fix #10399 2025-06-02 05:39:43 +02:00
github-actions[bot]
ec94f1bcf1 Merge pull request #10401 from firefly-iii/release-1748835078
🤖 Automatically merge the PR into the develop branch.
2025-06-02 05:31:25 +02:00
JC5
ee3d18a8ea 🤖 Auto commit for release 'develop' on 2025-06-02 2025-06-02 05:31:18 +02:00
James Cole
a9cd8b6512 Better fit for run recalculations. 2025-06-01 14:00:35 +02:00
James Cole
5bd87036b0 Config works now. Does not feel solved yet, but still. 2025-06-01 13:32:18 +02:00
37 changed files with 318 additions and 188 deletions

View File

@@ -34,7 +34,6 @@ use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use League\Fractal\Resource\Item;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -73,26 +72,24 @@ class UpdateController extends Controller
public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse
{
app('log')->debug('Now in update routine for transaction group');
$data = $request->getAll();
$oldAmount = $this->groupRepository->getTotalAmount($transactionGroup);
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
$newAmount = $this->groupRepository->getTotalAmount($transactionGroup);
$manager = $this->getManager();
Log::debug(sprintf('Old amount: %s, new amount: %s', $oldAmount, $newAmount));
$data = $request->getAll();
$oldHash = $this->groupRepository->getCompareHash($transactionGroup);
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
$newHash = $this->groupRepository->getCompareHash($transactionGroup);
$manager = $this->getManager();
app('preferences')->mark();
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
$amountChanged = 0 !== bccomp($oldAmount, $newAmount);
event(new UpdatedTransactionGroup($transactionGroup, $applyRules, $fireWebhooks, $amountChanged));
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
$runRecalculations = $oldHash !== $newHash;
event(new UpdatedTransactionGroup($transactionGroup, $applyRules, $fireWebhooks, $runRecalculations));
/** @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)
// filter on transaction group.
@@ -101,20 +98,20 @@ class UpdateController extends Controller
->withAPIInformation()
;
$selectedGroup = $collector->getGroups()->first();
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw new NotFoundHttpException();
}
// 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

@@ -65,29 +65,29 @@ class UpdateController extends Controller
public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse
{
app('log')->debug('Now in update routine for transaction group [v2]!');
$data = $request->getAll();
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
$amountChanged = true;
$data = $request->getAll();
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
$runRecalculations = true;
event(new UpdatedTransactionGroup($transactionGroup, $applyRules, $fireWebhooks, $amountChanged));
event(new UpdatedTransactionGroup($transactionGroup, $applyRules, $fireWebhooks, $runRecalculations));
app('preferences')->mark();
/** @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)->setTransactionGroup($transactionGroup);
$selectedGroup = $collector->getGroups()->first();
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw new FireflyException('200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
}
$transformer = new TransactionGroupTransformer();
$transformer = new TransactionGroupTransformer();
$transformer->setParameters($this->parameters);
return response()->api($this->jsonApiObject('transactions', $selectedGroup, $transformer))->header('Content-Type', self::CONTENT_TYPE);

View File

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

View File

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

View File

@@ -37,5 +37,5 @@ class UpdatedTransactionGroup extends Event
/**
* Create a new event instance.
*/
public function __construct(public TransactionGroup $transactionGroup, public bool $applyRules, public bool $fireWebhooks, public bool $amountChanged) {}
public function __construct(public TransactionGroup $transactionGroup, public bool $applyRules, public bool $fireWebhooks, public bool $runRecalculations) {}
}

View File

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

View File

@@ -49,7 +49,7 @@ class UpdatedGroupEventHandler
$this->processRules($event);
$this->recalculateCredit($event);
$this->triggerWebhooks($event);
if ($event->amountChanged) {
if ($event->runRecalculations) {
$this->updateRunningBalance($event);
}

View File

@@ -75,13 +75,22 @@ class AmountController extends Controller
$totalSaved = $this->piggyRepos->getCurrentAmount($piggyBank);
foreach ($piggyBank->accounts as $account) {
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $account, $date);
$savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank, $account);
$leftToSave = bcsub($piggyBank->target_amount, $savedSoFar);
$leftToSave = bcsub($piggyBank->target_amount, $totalSaved);
$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".',
$account->name,
$leftOnAccount,
$totalSaved,
$leftToSave,
$maxAmount,
));
$accounts[] = [
'account' => $account,
'left_on_account' => $leftOnAccount,
'saved_so_far' => $savedSoFar,
'total_saved' => $totalSaved,
'left_to_save' => $leftToSave,
'max_amount' => $maxAmount,
];
@@ -100,18 +109,18 @@ class AmountController extends Controller
public function addMobile(PiggyBank $piggyBank)
{
/** @var Carbon $date */
$date = session('end', today(config('app.timezone')));
$accounts = [];
$total = '0';
$date = session('end', today(config('app.timezone')));
$accounts = [];
$total = '0';
$totalSaved = $this->piggyRepos->getCurrentAmount($piggyBank);
foreach ($piggyBank->accounts as $account) {
$leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, $account, $date);
$savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank, $account);
$leftToSave = bcsub($piggyBank->target_amount, $savedSoFar);
$leftToSave = bcsub($piggyBank->target_amount, $totalSaved);
$maxAmount = 0 === bccomp($piggyBank->target_amount, '0') ? $leftOnAccount : min($leftOnAccount, $leftToSave);
$accounts[] = [
'account' => $account,
'left_on_account' => $leftOnAccount,
'saved_so_far' => $savedSoFar,
'total_saved' => $totalSaved,
'left_to_save' => $leftToSave,
'max_amount' => $maxAmount,
];

View File

@@ -63,6 +63,14 @@ class EditController extends Controller
);
}
public function resetHistory(PiggyBank $piggyBank): RedirectResponse
{
$this->piggyRepos->resetHistory($piggyBank);
session()->flash('success', (string) trans('firefly.piggy_history_reset'));
return redirect(route('piggy-banks.show', [$piggyBank->id]));
}
/**
* Edit a piggy bank.
*

View File

@@ -292,7 +292,7 @@ class ConvertController extends Controller
$group->refresh();
session()->flash('success', (string) trans('firefly.converted_to_'.$destinationType->type));
event(new UpdatedTransactionGroup($group, true, true, false));
event(new UpdatedTransactionGroup($group, true, true, true));
return redirect(route('transactions.show', [$group->id]));
}

View File

@@ -191,15 +191,15 @@ class MassController extends Controller
*/
private function updateJournal(int $journalId, MassEditJournalRequest $request): void
{
$journal = $this->repository->find($journalId);
$journal = $this->repository->find($journalId);
if (!$journal instanceof TransactionJournal) {
throw new FireflyException(sprintf('Trying to edit non-existent or deleted journal #%d', $journalId));
}
$service = app(JournalUpdateService::class);
$service = app(JournalUpdateService::class);
// for each field, call the update service.
$service->setTransactionJournal($journal);
$data = [
$data = [
'date' => $this->getDateFromRequest($request, $journal->id, 'date'),
'description' => $this->getStringFromRequest($request, $journal->id, 'description'),
'source_id' => $this->getIntFromRequest($request, $journal->id, 'source_id'),
@@ -217,8 +217,8 @@ class MassController extends Controller
$service->setData($data);
$service->update();
// trigger rules
$amountChanged = $service->isAmountChanged();
event(new UpdatedTransactionGroup($journal->transactionGroup, true, true, $amountChanged));
$runRecalculations = $service->isCompareHashChanged();
event(new UpdatedTransactionGroup($journal->transactionGroup, true, true, $runRecalculations));
}
private function getDateFromRequest(MassEditJournalRequest $request, int $journalId, string $key): ?Carbon

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Requests;
use Illuminate\Contracts\Validation\Validator;
use Carbon\Carbon;
use FireflyIII\Support\Request\ChecksLogin;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log;
@@ -41,15 +40,9 @@ class SelectTransactionsRequest extends FormRequest
*/
public function rules(): array
{
// fixed
/** @var Carbon $sessionFirst */
$sessionFirst = clone session('first');
$first = $sessionFirst->subDay()->format('Y-m-d');
$today = today(config('app.timezone'))->addDay()->format('Y-m-d');
return [
'start' => 'required|date|after:'.$first,
'end' => 'required|date|before:'.$today,
'start' => 'required|date|after:1900-01-01|before:2099-12-31|before:end|required_with:end',
'end' => 'required|date|after:1900-01-01|before:2099-12-31|after:start|required_with:start',
'accounts' => 'required',
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
];

View File

@@ -67,9 +67,10 @@ class UserFailedLoginAttempt extends Notification
{
$settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable);
$message = new Message();
$ip = Request::ip();
$message->topic($settings['ntfy_topic']);
$message->title((string) trans('email.failed_login_subject'));
$message->body((string) trans('email.failed_login_message', ['email' => $this->user->email]));
$message->body((string) trans('email.failed_login_message', ['ip' => $ip, 'email' => $this->user->email]));
return $message;
}
@@ -79,7 +80,9 @@ class UserFailedLoginAttempt extends Notification
*/
public function toPushover(User $notifiable): PushoverMessage
{
return PushoverMessage::create((string) trans('email.failed_login_message', ['email' => $this->user->email]))
$ip = Request::ip();
return PushoverMessage::create((string) trans('email.failed_login_message', ['ip' => $ip, 'email' => $this->user->email]))
->title((string) trans('email.failed_login_subject'))
;
}
@@ -89,7 +92,8 @@ class UserFailedLoginAttempt extends Notification
*/
public function toSlack(User $notifiable): SlackMessage
{
$message = (string) trans('email.failed_login_message', ['email' => $this->user->email]);
$ip = Request::ip();
$message = (string) trans('email.failed_login_message', ['ip' => $ip, 'email' => $this->user->email]);
return new SlackMessage()->content($message);
}

View File

@@ -31,6 +31,7 @@ use FireflyIII\Events\Model\BudgetLimit\Created;
use FireflyIII\Events\Model\BudgetLimit\Deleted;
use FireflyIII\Events\Model\BudgetLimit\Updated;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Events\Model\PiggyBank\ChangedName;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnObject;
use FireflyIII\Events\NewVersionAvailable;
@@ -210,6 +211,9 @@ class EventServiceProvider extends ServiceProvider
ChangedAmount::class => [
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changePiggyAmount',
],
ChangedName::class => [
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName',
],
// budget related events: CRUD budget limit
Created::class => [

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\PiggyBank;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Events\Model\PiggyBank\ChangedName;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\PiggyBankFactory;
use FireflyIII\Models\Account;
@@ -66,7 +67,7 @@ trait ModifiesPiggyBanks
{
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$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;
// also update native_current_amount.
@@ -89,7 +90,7 @@ trait ModifiesPiggyBanks
{
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$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;
// also update native_current_amount.
@@ -121,13 +122,13 @@ trait ModifiesPiggyBanks
if (0 !== bccomp($piggyBank->target_amount, '0')) {
$leftToSave = bcsub($piggyBank->target_amount, (string) $savedSoFar);
$maxAmount = 1 === bccomp((string) $leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
$leftToSave = bcsub($piggyBank->target_amount, (string)$savedSoFar);
$maxAmount = 1 === bccomp((string)$leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
Log::debug(sprintf('Left to save: %s', $leftToSave));
Log::debug(sprintf('Maximum amount: %s', $maxAmount));
}
$compare = bccomp($amount, (string) $maxAmount);
$compare = bccomp($amount, (string)$maxAmount);
$result = $compare <= 0;
Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
@@ -139,7 +140,7 @@ trait ModifiesPiggyBanks
{
$savedSoFar = $this->getCurrentAmount($piggyBank, $account);
return bccomp($amount, (string) $savedSoFar) <= 0;
return bccomp($amount, (string)$savedSoFar) <= 0;
}
/**
@@ -170,7 +171,7 @@ trait ModifiesPiggyBanks
if (1 === bccomp($amount, $max) && 0 !== bccomp($piggyBank->target_amount, '0')) {
$amount = $max;
}
$difference = bcsub($amount, (string) $repetition->current_amount);
$difference = bcsub($amount, (string)$repetition->current_amount);
$repetition->current_amount = $amount;
$repetition->save();
@@ -211,12 +212,12 @@ trait ModifiesPiggyBanks
{
$piggyBank = $this->updateProperties($piggyBank, $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:
$oldOrder = $piggyBank->order;
$newOrder = (int) ($data['order'] ?? $oldOrder);
$newOrder = (int)($data['order'] ?? $oldOrder);
if ($oldOrder !== $newOrder) {
$this->setOrder($piggyBank, $newOrder);
}
@@ -233,9 +234,9 @@ trait ModifiesPiggyBanks
// if the piggy bank is now smaller than the sum of the money saved,
// remove money from all accounts until the piggy bank is the right amount.
$currentAmount = $this->getCurrentAmount($piggyBank);
if (1 === bccomp((string) $currentAmount, (string) $piggyBank->target_amount) && 0 !== bccomp((string) $piggyBank->target_amount, '0')) {
if (1 === bccomp((string)$currentAmount, (string)$piggyBank->target_amount) && 0 !== bccomp((string)$piggyBank->target_amount, '0')) {
Log::debug(sprintf('Current amount is %s, target amount is %s', $currentAmount, $piggyBank->target_amount));
$difference = bcsub((string) $piggyBank->target_amount, (string) $currentAmount);
$difference = bcsub((string)$piggyBank->target_amount, (string)$currentAmount);
// an amount will be removed, create "negative" event:
// Log::debug(sprintf('ChangedAmount: is triggered with difference "%s"', $difference));
@@ -248,7 +249,7 @@ trait ModifiesPiggyBanks
// update using name:
if (array_key_exists('object_group_title', $data)) {
$objectGroupTitle = (string) $data['object_group_title'];
$objectGroupTitle = (string)$data['object_group_title'];
if ('' !== $objectGroupTitle) {
$objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle);
if (null !== $objectGroup) {
@@ -264,7 +265,7 @@ trait ModifiesPiggyBanks
// try also with ID:
if (array_key_exists('object_group_id', $data)) {
$objectGroupId = (int) ($data['object_group_id'] ?? 0);
$objectGroupId = (int)($data['object_group_id'] ?? 0);
if (0 !== $objectGroupId) {
$objectGroup = $this->findObjectGroupById($objectGroupId);
if (null !== $objectGroup) {
@@ -282,6 +283,7 @@ trait ModifiesPiggyBanks
private function updateProperties(PiggyBank $piggyBank, array $data): PiggyBank
{
if (array_key_exists('name', $data) && '' !== $data['name']) {
event(new ChangedName($piggyBank, $piggyBank->name, $data['name']));
$piggyBank->name = $data['name'];
}
if (array_key_exists('transaction_currency_id', $data) && is_int($data['transaction_currency_id'])) {
@@ -366,14 +368,14 @@ trait ModifiesPiggyBanks
foreach ($piggyBank->accounts as $account) {
$current = $account->pivot->current_amount;
// if this account contains more than the amount, remove the amount and return.
if (1 === bccomp((string) $current, $amount)) {
if (1 === bccomp((string)$current, $amount)) {
$this->removeAmount($piggyBank, $account, $amount);
return;
}
// if this account contains less than the amount, remove the current amount, update the amount and continue.
$this->removeAmount($piggyBank, $account, $current);
$amount = bcsub($amount, (string) $current);
$amount = bcsub($amount, (string)$current);
}
}
}

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\PiggyBank;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\User;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
@@ -438,4 +439,14 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
return $search->take($limit)->get(['piggy_banks.*']);
}
public function resetHistory(PiggyBank $piggyBank): void
{
$piggyBank->piggyBankEvents()->delete();
foreach ($piggyBank->accounts as $account) {
if (0 !== bccomp('0', $account->pivot->current_amount)) {
event(new ChangedAmount($piggyBank, $account->pivot->current_amount, null, null));
}
}
}
}

View File

@@ -47,6 +47,8 @@ use Illuminate\Support\Collection;
*/
interface PiggyBankRepositoryInterface
{
public function resetHistory(PiggyBank $piggyBank): void;
public function addAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool;
public function addAmountToPiggyBank(PiggyBank $piggyBank, string $amount, TransactionJournal $journal): void;

View File

@@ -217,7 +217,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
'link' => $entry->outward,
'group' => $entry->destination->transaction_group_id,
'description' => $entry->destination->description,
'editable' => 1 === (int) $entry->editable,
'editable' => 1 === (int)$entry->editable,
'amount' => $amount,
'foreign_amount' => $foreignAmount,
];
@@ -230,7 +230,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
'link' => $entry->inward,
'group' => $entry->source->transaction_group_id,
'description' => $entry->source->description,
'editable' => 1 === (int) $entry->editable,
'editable' => 1 === (int)$entry->editable,
'amount' => $amount,
'foreign_amount' => $foreignAmount,
];
@@ -264,7 +264,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
if (null === $transaction->foreign_amount || '' === $transaction->foreign_amount) {
return '';
}
if (0 === bccomp('0', (string) $transaction->foreign_amount)) {
if (0 === bccomp('0', (string)$transaction->foreign_amount)) {
return '';
}
$currency = $transaction->foreignCurrency;
@@ -305,7 +305,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
$return = [];
foreach ($query as $row) {
$return[$row->name] = new Carbon(json_decode((string) $row->data, true, 512, JSON_THROW_ON_ERROR));
$return[$row->name] = new Carbon(json_decode((string)$row->data, true, 512, JSON_THROW_ON_ERROR));
}
return new NullArrayObject($return);
@@ -325,7 +325,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
$return = [];
foreach ($query as $row) {
$return[$row->name] = json_decode((string) $row->data);
$return[$row->name] = json_decode((string)$row->data);
}
return new NullArrayObject($return);
@@ -433,20 +433,22 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
return $service->update($transactionGroup, $data);
}
public function getTotalAmount(TransactionGroup $group): string
public function getCompareHash(TransactionGroup $group): string
{
$sum = '0';
$sum = '0';
$names = '';
/** @var TransactionJournal $journal */
foreach ($group->transactionJournals as $journal) {
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
if (-1 === bccomp('0', (string) $transaction->amount)) {
$sum = bcadd($sum, $transaction->amount);
if (-1 === bccomp('0', (string)$transaction->amount)) {
$sum = bcadd($sum, $transaction->amount);
$names = sprintf('%s%s', $names, $transaction->account->name);
}
}
}
return $sum;
return hash('sha256', sprintf('%s-%s', $names, $sum));
}
}

View File

@@ -49,7 +49,10 @@ interface TransactionGroupRepositoryInterface
{
public function countAttachments(int $journalId): int;
public function getTotalAmount(TransactionGroup $group): string;
/**
* Small method that returns a hash that can be used to compare two transaction groups.
*/
public function getCompareHash(TransactionGroup $group): string;
public function destroy(TransactionGroup $group): void;

View File

@@ -43,6 +43,7 @@ use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\NullArrayObject;
@@ -60,6 +61,7 @@ class JournalUpdateService
private BillRepositoryInterface $billRepository;
private CurrencyRepositoryInterface $currencyRepository;
private TransactionGroupRepositoryInterface $transactionGroupRepository;
private array $data;
private ?Account $destinationAccount;
private ?Transaction $destinationTransaction;
@@ -69,26 +71,27 @@ class JournalUpdateService
private ?Transaction $sourceTransaction;
private ?TransactionGroup $transactionGroup;
private ?TransactionJournal $transactionJournal;
private bool $amountChanged = false;
private string $startCompareHash = '';
/**
* JournalUpdateService constructor.
*/
public function __construct()
{
$this->destinationAccount = null;
$this->destinationTransaction = null;
$this->sourceAccount = null;
$this->sourceTransaction = null;
$this->transactionGroup = null;
$this->transactionJournal = null;
$this->billRepository = app(BillRepositoryInterface::class);
$this->categoryRepository = app(CategoryRepositoryInterface::class);
$this->budgetRepository = app(BudgetRepositoryInterface::class);
$this->tagFactory = app(TagFactory::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
$this->metaString = [
$this->destinationAccount = null;
$this->destinationTransaction = null;
$this->sourceAccount = null;
$this->sourceTransaction = null;
$this->transactionGroup = null;
$this->transactionJournal = null;
$this->billRepository = app(BillRepositoryInterface::class);
$this->categoryRepository = app(CategoryRepositoryInterface::class);
$this->budgetRepository = app(BudgetRepositoryInterface::class);
$this->tagFactory = app(TagFactory::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
$this->transactionGroupRepository = app(TransactionGroupRepositoryInterface::class);
$this->metaString = [
'sepa_cc',
'sepa_ct_op',
'sepa_ct_id',
@@ -103,7 +106,7 @@ class JournalUpdateService
'external_id',
'external_url',
];
$this->metaDate = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date',
$this->metaDate = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date',
'invoice_date', ];
}
@@ -120,10 +123,12 @@ class JournalUpdateService
$this->budgetRepository->setUser($transactionGroup->user);
$this->tagFactory->setUser($transactionGroup->user);
$this->accountRepository->setUser($transactionGroup->user);
$this->transactionGroupRepository->setUser($transactionGroup->user);
$this->destinationAccount = null;
$this->destinationTransaction = null;
$this->sourceAccount = null;
$this->sourceTransaction = null;
$this->startCompareHash = $this->transactionGroupRepository->getCompareHash($transactionGroup);
}
public function setTransactionJournal(TransactionJournal $transactionJournal): void
@@ -675,7 +680,6 @@ class JournalUpdateService
return;
}
$origSourceTransaction = $this->getSourceTransaction();
$this->amountChanged = 0 !== bccomp($origSourceTransaction->amount, app('steam')->negative($amount));
$origSourceTransaction->amount = app('steam')->negative($amount);
$origSourceTransaction->balance_dirty = true;
$origSourceTransaction->save();
@@ -818,8 +822,13 @@ class JournalUpdateService
return false;
}
public function isAmountChanged(): bool
public function isCompareHashChanged(): bool
{
return $this->amountChanged;
Log::debug(sprintf('Now in %s', __METHOD__));
$compareHash = $this->transactionGroupRepository->getCompareHash($this->transactionGroup);
Log::debug(sprintf('Compare hash is "%s".', $compareHash));
Log::debug(sprintf('Start compare hash is "%s".', $this->startCompareHash));
return $compareHash !== $this->startCompareHash;
}
}

View File

@@ -79,12 +79,12 @@ class ExchangeRateConverter
public function getCurrencyRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
{
if (false === $this->enabled()) {
Log::debug('ExchangeRateConverter: disabled, return "1".');
// Log::debug('ExchangeRateConverter: disabled, return "1".');
return '1';
}
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';
}

View File

@@ -35,6 +35,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Debug\Timer;
use Illuminate\Support\Facades\Log;
/**
* Trait PeriodOverview.
@@ -77,6 +78,7 @@ trait PeriodOverview
*/
protected function getAccountPeriodOverview(Account $account, Carbon $start, Carbon $end): array
{
Log::debug('Now in getAccountPeriodOverview()');
Timer::start('account-period-total');
$this->accountRepository = app(AccountRepositoryInterface::class);
$range = app('navigation')->getViewRange(true);
@@ -100,6 +102,7 @@ trait PeriodOverview
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
// loop dates
Log::debug(sprintf('Count of loops: %d', count($dates)));
foreach ($dates as $currentDate) {
$title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
@@ -119,6 +122,7 @@ trait PeriodOverview
}
$cache->store($entries);
Timer::stop('account-period-total');
Log::debug('End of getAccountPeriodOverview()');
return $entries;
}

View File

@@ -67,6 +67,16 @@ class UpdatePiggyBank implements ActionInterface
Log::debug(sprintf('Found piggy bank #%d ("%s")', $piggyBank->id, $piggyBank->name));
// piggy bank already has an event for this transaction journal?
if ($this->alreadyEventPresent($piggyBank, $journal)) {
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])));
return false;
}
/** @var Transaction $destination */
$destination = $journalObj->transactions()->where('amount', '>', 0)->first();
@@ -231,4 +241,9 @@ class UpdatePiggyBank implements ActionInterface
$repository->addAmount($piggyBank, $account, $amount, $journal);
}
private function alreadyEventPresent(PiggyBank $piggyBank, array $journal): bool
{
return $piggyBank->piggyBankEvents()->where('transaction_journal_id', $journal['transaction_journal_id'])->exists();
}
}

77
composer.lock generated
View File

@@ -129,16 +129,16 @@
},
{
"name": "brick/math",
"version": "0.12.3",
"version": "0.13.1",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba"
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba",
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba",
"url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04",
"shasum": ""
},
"require": {
@@ -177,7 +177,7 @@
],
"support": {
"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": [
{
@@ -185,7 +185,7 @@
"type": "github"
}
],
"time": "2025-02-28T13:11:00+00:00"
"time": "2025-03-29T13:50:30+00:00"
},
{
"name": "carbonphp/carbon-doctrine-types",
@@ -939,16 +939,16 @@
},
{
"name": "filp/whoops",
"version": "2.18.0",
"version": "2.18.1",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e"
"reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"url": "https://api.github.com/repos/filp/whoops/zipball/8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26",
"reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26",
"shasum": ""
},
"require": {
@@ -998,7 +998,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.18.0"
"source": "https://github.com/filp/whoops/tree/2.18.1"
},
"funding": [
{
@@ -1006,7 +1006,7 @@
"type": "github"
}
],
"time": "2025-03-15T12:00:00+00:00"
"time": "2025-06-03T18:56:14+00:00"
},
{
"name": "firebase/php-jwt",
@@ -1879,20 +1879,20 @@
},
{
"name": "laravel/framework",
"version": "v12.16.0",
"version": "v12.17.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "293bb1c70224faebfd3d4328e201c37115da055f"
"reference": "8729d084510480fdeec9b6ad198180147d4a7f06"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/293bb1c70224faebfd3d4328e201c37115da055f",
"reference": "293bb1c70224faebfd3d4328e201c37115da055f",
"url": "https://api.github.com/repos/laravel/framework/zipball/8729d084510480fdeec9b6ad198180147d4a7f06",
"reference": "8729d084510480fdeec9b6ad198180147d4a7f06",
"shasum": ""
},
"require": {
"brick/math": "^0.11|^0.12",
"brick/math": "^0.11|^0.12|^0.13",
"composer-runtime-api": "^2.2",
"doctrine/inflector": "^2.0.5",
"dragonmantank/cron-expression": "^3.4",
@@ -2090,7 +2090,7 @@
"issues": "https://github.com/laravel/framework/issues",
"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",
@@ -3473,22 +3473,22 @@
},
{
"name": "mailersend/laravel-driver",
"version": "v2.9.1",
"version": "v2.11.0",
"source": {
"type": "git",
"url": "https://github.com/mailersend/mailersend-laravel-driver.git",
"reference": "87fd5ab76808bbaac9221be0d306baef13e98725"
"reference": "63acebb5064745076df27b1a80423986b6d7b69e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mailersend/mailersend-laravel-driver/zipball/87fd5ab76808bbaac9221be0d306baef13e98725",
"reference": "87fd5ab76808bbaac9221be0d306baef13e98725",
"url": "https://api.github.com/repos/mailersend/mailersend-laravel-driver/zipball/63acebb5064745076df27b1a80423986b6d7b69e",
"reference": "63acebb5064745076df27b1a80423986b6d7b69e",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/support": "^9.0 || ^10.0 || ^11.0 || ^12.0",
"mailersend/mailersend": "^0.31.0",
"mailersend/mailersend": "^0.34.0",
"nyholm/psr7": "^1.5",
"php": ">=8.0",
"php-http/guzzle7-adapter": "^1.0",
@@ -3536,29 +3536,28 @@
],
"support": {
"issues": "https://github.com/mailersend/mailersend-laravel-driver/issues",
"source": "https://github.com/mailersend/mailersend-laravel-driver/tree/v2.9.1"
"source": "https://github.com/mailersend/mailersend-laravel-driver/tree/v2.11.0"
},
"time": "2025-04-09T09:33:07+00:00"
"time": "2025-06-04T08:47:41+00:00"
},
{
"name": "mailersend/mailersend",
"version": "v0.31.0",
"version": "v0.34.0",
"source": {
"type": "git",
"url": "https://github.com/mailersend/mailersend-php.git",
"reference": "513ff83ee768526055ad52987cde401ea7218c67"
"reference": "1cb8c42e5569e7455b1e0e794dcbf68e3b7898ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mailersend/mailersend-php/zipball/513ff83ee768526055ad52987cde401ea7218c67",
"reference": "513ff83ee768526055ad52987cde401ea7218c67",
"url": "https://api.github.com/repos/mailersend/mailersend-php/zipball/1cb8c42e5569e7455b1e0e794dcbf68e3b7898ab",
"reference": "1cb8c42e5569e7455b1e0e794dcbf68e3b7898ab",
"shasum": ""
},
"require": {
"beberlei/assert": "^3.2",
"ext-json": "*",
"illuminate/collections": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
"php": "^7.4|^8.0",
"php": "^7.4 || ^8.0 <8.5",
"php-http/client-common": "^2.2",
"php-http/discovery": "^1.9",
"php-http/httplug": "^2.1",
@@ -3603,9 +3602,9 @@
],
"support": {
"issues": "https://github.com/mailersend/mailersend-php/issues",
"source": "https://github.com/mailersend/mailersend-php/tree/v0.31.0"
"source": "https://github.com/mailersend/mailersend-php/tree/v0.34.0"
},
"time": "2025-04-03T12:16:11+00:00"
"time": "2025-06-04T07:53:52+00:00"
},
{
"name": "monolog/monolog",
@@ -3880,16 +3879,16 @@
},
{
"name": "nette/utils",
"version": "v4.0.6",
"version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "ce708655043c7050eb050df361c5e313cf708309"
"reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309",
"reference": "ce708655043c7050eb050df361c5e313cf708309",
"url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2",
"reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2",
"shasum": ""
},
"require": {
@@ -3960,9 +3959,9 @@
],
"support": {
"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",

View File

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

62
package-lock.json generated
View File

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

View File

@@ -24,4 +24,12 @@ $(function () {
if (typeof(lineChart) === 'function' && typeof(piggyBankID) !== 'undefined') {
lineChart('chart/piggy-bank/' + piggyBankID, 'piggy-bank-history');
}
});
// on submit of logout button:
$('.reset-link').click(function(e) {
console.log('here we are');
e.preventDefault();
document.getElementById('reset-form').submit();
return false;
});
});

View File

@@ -490,7 +490,6 @@ let transactions = function () {
// addedSplit, is called from the HTML
// for source account
const renderAccount = function (item, b, c) {
console.log('render account');
return item.name_with_balance + '<br><small class="text-muted">' + i18next.t('firefly.account_type_' + item.type) + '</small>';
};
console.log('here we are in');
@@ -499,7 +498,7 @@ let transactions = function () {
serverUrl: urls.account,
onRenderItem: renderAccount,
valueField: 'id',
labelField: 'title',
labelField: 'name_with_balance',
onChange: changeSourceAccount,
onSelectItem: selectSourceAccount,
hiddenValue: this.entries[count].source_account.alpine_name
@@ -509,7 +508,7 @@ let transactions = function () {
serverUrl: urls.account,
account_types: this.filters.destination,
valueField: 'id',
labelField: 'title',
labelField: 'name_with_balance',
onRenderItem: renderAccount,
onChange: changeDestinationAccount,
onSelectItem: selectDestinationAccount

View File

@@ -45,7 +45,7 @@ export function addAutocomplete(options) {
liveServer: true,
};
if (typeof options.account_types !== 'undefined' && options.account_types.length > 0) {
params.serverParams['filter[account_types]'] = options.account_types;
params.serverParams['types'] = options.account_types;
}
if (typeof options.onRenderItem !== 'undefined' && null !== options.onRenderItem) {
console.log('overrule onRenderItem.');

View File

@@ -55,7 +55,7 @@ export function changeDestinationAccount(item, ac) {
export function selectDestinationAccount(item, ac) {
const index = parseInt(ac._searchInput.attributes['data-index'].value);
document.querySelector('#form')._x_dataStack[0].$data.entries[index].destination_account = {
id: item.id, name: item.title, alpine_name: item.title, type: item.meta.type, currency_code: item.meta.currency_code,
id: item.id, name: item.name, alpine_name: item.name, type: item.type, currency_code: item.currency_code,
};
document.querySelector('#form')._x_dataStack[0].changedDestinationAccount();
}
@@ -78,7 +78,7 @@ export function changeSourceAccount(item, ac) {
export function selectSourceAccount(item, ac) {
const index = parseInt(ac._searchInput.attributes['data-index'].value);
document.querySelector('#form')._x_dataStack[0].$data.entries[index].source_account = {
id: item.id, name: item.title, alpine_name: item.title, type: item.meta.type, currency_code: item.meta.currency_code,
id: item.id, name: item.name, alpine_name: item.name, type: item.type, currency_code: item.currency_code,
};
document.querySelector('#form')._x_dataStack[0].changedSourceAccount();
}

View File

@@ -36,8 +36,8 @@ export default defineConfig(({command, mode, isSsrBuild, isPreview}) => {
let https = null;
if (command === 'serve') {
https = {
key: fs.readFileSync(`/Users/sander/Sites/vm/tls-certificates/wildcard.sd.internal.key`),
cert: fs.readFileSync(`/Users/sander/Sites/vm/tls-certificates/wildcard.sd.internal.crt`),
key: fs.readFileSync(`/vagrant/tls-certificates/wildcard.sd.internal.key`),
cert: fs.readFileSync(`/vagrant/tls-certificates/wildcard.sd.internal.crt`),
};
}
@@ -83,7 +83,7 @@ export default defineConfig(({command, mode, isSsrBuild, isPreview}) => {
server: {
cors: true,
origin: 'https://localhost:5173',
origin: 'https://192.168.96.154:5173',
watch: {
usePolling: true,
},

View File

@@ -70,7 +70,7 @@ return [
// known user login attempt
'failed_login_subject' => 'Firefly III detected a failed login attempt',
'failed_login_body' => 'Firefly III detected that somebody (you?) failed to login with your account ":email". Please verify that this was you.',
'failed_login_message' => 'A failed login attempt on your Firefly III account ":email" was detected.',
'failed_login_message' => 'A failed login attempt (:ip) on your Firefly III account ":email" was detected.',
'failed_login_warning' => 'If you recognize this IP address or the login attempt, you can ignore this message. If you didn\'t try to login, of if you have no idea what this is about, verify your password security, change it, and log out all other sessions. To do this, go to your profile page. Of course you have 2FA enabled already, right? Stay safe!',
// registered

View File

@@ -2486,6 +2486,10 @@ return [
'left_for_piggy_banks' => 'Left for piggy banks',
'sum_of_piggy_banks' => 'Sum of piggy banks',
'saved_so_far' => 'Saved so far',
'saved_so_far_total' => 'Saved so far in total',
'reset_history' => 'reset history',
'reset_history_confirm' => 'Are you sure you want to reset the history of this piggy bank? This will make the chart match the piggy bank\'s amount again.',
'piggy_history_reset' => 'The piggy bank history has been reset',
'left_to_save' => 'Left to save',
'suggested_amount' => 'Suggested monthly amount to save',
'add_money_to_piggy_title' => 'Add money to piggy bank ":name"',

View File

@@ -68,6 +68,7 @@ return [
'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',
'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"',
'cannot_unlink_tag' => 'Tag ":tag" isn\'t linked to this transaction',
'cannot_find_budget' => 'Firefly III can\'t find budget ":name"',
'cannot_find_category' => 'Firefly III can\'t find category ":name"',

View File

@@ -163,6 +163,7 @@
{% endif %}
</td>
<td style="{{ style|raw }};text-align:right">
{# deposit #}
{% if transaction.transaction_type_type == 'Deposit' %}
{# amount of deposit #}

View File

@@ -54,8 +54,20 @@
</td>
</tr>
{% endif %}
{% for account in piggy.accounts %}
<tr>
<td>
{{ 'saved_so_far'|_ }}
(<a href="{{ route('accounts.show', account.id) }}">{{ account.name }}</a>)
</td>
<td>
{{ formatAmountBySymbol(account.current_amount, piggy.currency_symbol, piggy.currency_decimal_places) }}
</td>
</tr>
{% endfor %}
<tr>
<td>{{ 'saved_so_far'|_ }}</td>
<td>{{ 'saved_so_far_total'|_ }}</td>
<td>
{{ formatAmountBySymbol(piggy.current_amount, piggy.currency_symbol, piggy.currency_decimal_places) }}
</td>
@@ -102,7 +114,7 @@
</div>
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'event_history'|_ }}</h3>
<h3 class="box-title">{{ 'event_history'|_ }} (<a onclick="return confirm('{{ 'reset_history_confirm'|_|escape('js') }}');" class="reset-link" href="#">{{ 'reset_history'|_ }}</a>)</h3>
</div>
<div class="box-body no-padding" id="piggyEvents">
{% include 'list/piggy-bank-events' %}
@@ -140,6 +152,9 @@
</div>
{% endif %}
</div>
<form id="reset-form" action="{{ route('piggy-banks.reset', [piggyBank.id]) }}" method="POST" style="display: none;">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
</form>
{% endblock %}
{% block scripts %}

View File

@@ -803,6 +803,7 @@ Route::group(
Route::post('store', ['uses' => 'PiggyBank\CreateController@store', 'as' => 'store']);
Route::post('update/{piggyBank}', ['uses' => 'PiggyBank\EditController@update', 'as' => 'update']);
Route::post('destroy/{piggyBank}', ['uses' => 'PiggyBank\DeleteController@destroy', 'as' => 'destroy']);
Route::post('reset-history/{piggyBank}', ['uses' => 'PiggyBank\EditController@resetHistory', 'as' => 'reset']);
Route::post('add/{piggyBank}', ['uses' => 'PiggyBank\AmountController@postAdd', 'as' => 'add']);
Route::post('remove/{piggyBank}', ['uses' => 'PiggyBank\AmountController@postRemove', 'as' => 'remove']);