mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2026-01-19 12:24:11 +00:00
Compare commits
40 Commits
develop-20
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be6801654f | ||
|
|
93fe1f5558 | ||
|
|
ba5b6ff694 | ||
|
|
492aec0652 | ||
|
|
c81bb44552 | ||
|
|
0431c95b49 | ||
|
|
6408db5414 | ||
|
|
5de88a70f8 | ||
|
|
968da83c67 | ||
|
|
e77b4ee4a6 | ||
|
|
a36c775bd2 | ||
|
|
7773862eb9 | ||
|
|
ae107b1776 | ||
|
|
d1b167447b | ||
|
|
a08ba43c23 | ||
|
|
64325fbce7 | ||
|
|
aeac59e851 | ||
|
|
151be7df8a | ||
|
|
040bd0f6e3 | ||
|
|
a859ca4e38 | ||
|
|
95c62d783a | ||
|
|
e7b67bc85e | ||
|
|
208f13ee75 | ||
|
|
437eecc1c9 | ||
|
|
1d34d81389 | ||
|
|
30844e99d4 | ||
|
|
d734449f63 | ||
|
|
69a9e3a198 | ||
|
|
3ad2f8c750 | ||
|
|
2166839768 | ||
|
|
8ecb9d7774 | ||
|
|
8814fb0806 | ||
|
|
7481c8d4c0 | ||
|
|
1e618fbf6d | ||
|
|
8b322dc903 | ||
|
|
812b0e6940 | ||
|
|
9070856b9c | ||
|
|
84082d38f2 | ||
|
|
822352827a | ||
|
|
4c95ab49f4 |
10
.env.example
10
.env.example
@@ -14,7 +14,15 @@ SITE_OWNER=mail@example.com
|
||||
# Change it to a string of exactly 32 chars or use something like `php artisan key:generate` to generate it.
|
||||
# If you use Docker or similar, you can set this variable from a file by using APP_KEY_FILE
|
||||
#
|
||||
# Avoid the "#" character in your APP_KEY, it may break things.
|
||||
# Try to avoid special characters like #, < and > in your app key. This string does not need full entropy
|
||||
# When in doubt, follow the link below and pick one.
|
||||
#
|
||||
# https://www.random.org/strings/?num=5&len=32&digits=on&upperalpha=on&loweralpha=on&unique=on&format=html&rnd=new
|
||||
#
|
||||
# If you are a fancy linux nerd like me, use this command:
|
||||
#
|
||||
# head /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 32 && echo
|
||||
#
|
||||
#
|
||||
APP_KEY=SomeRandomStringOf32CharsExactly
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ class PiggyBankController extends Controller
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'object_group_id' => null === $objectGroup ? null : (string) $objectGroup->id,
|
||||
'object_group_title' => $objectGroup?->title,
|
||||
'object_group_order' => $objectGroup?->order,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace FireflyIII\Console\Commands\Correction;
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Support\System\OAuthKeys;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class RestoresOAuthKeys extends Command
|
||||
{
|
||||
@@ -40,7 +41,9 @@ class RestoresOAuthKeys extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
Log::debug('Restore OAuth Keys command.');
|
||||
$this->restoreOAuthKeys();
|
||||
Log::debug('Done with OAuth Keys command.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Updated.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\Account;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Updated
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(public Account $account) {}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ use Illuminate\Support\Facades\Log;
|
||||
/**
|
||||
* Class ChangedAmount
|
||||
*/
|
||||
class ChangedAmount extends Event
|
||||
class PiggyBankAmountIsChanged extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* ChangedName.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
* PiggyBankNameIsChanged.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -21,15 +21,16 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
/**
|
||||
* Needs to be an event because system needs old value as well as the new value.
|
||||
*/
|
||||
class PiggyBankNameIsChanged extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* WarnUserAboutBill.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
* SubscriptionNeedsExtensionOrRenewal.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -24,18 +22,15 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\Bill;
|
||||
namespace FireflyIII\Events\Model\Subscription;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\Bill;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class WarnUserAboutBill.
|
||||
*/
|
||||
class WarnUserAboutBill extends Event
|
||||
class SubscriptionNeedsExtensionOrRenewal extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(public Bill $bill, public string $field, public int $diff) {}
|
||||
public function __construct(public Bill $subscription, public string $field, public int $diff) {}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* WarnUserAboutOverdueSubscriptions.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
* SubscriptionIsOverdueForPayment.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -21,18 +21,15 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\Bill;
|
||||
namespace FireflyIII\Events\Model\Subscription;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class WarnUserAboutOverdueSubscriptions extends Event
|
||||
class SubscriptionsAreOverdueForPayment extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(public User $user, public array $overdue) {}
|
||||
|
||||
}
|
||||
@@ -23,7 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Factory;
|
||||
|
||||
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
|
||||
use FireflyIII\Events\Model\PiggyBank\PiggyBankAmountIsChanged;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
@@ -305,7 +305,7 @@ class PiggyBankFactory
|
||||
if (0 !== bccomp($oldSavedAmount, $newSavedAmount)) {
|
||||
Log::debug('Amount changed, will create event for it.');
|
||||
// create event for difference.
|
||||
event(new ChangedAmount($piggyBank, bcsub($newSavedAmount, $oldSavedAmount), null, null));
|
||||
event(new PiggyBankAmountIsChanged($piggyBank, bcsub($newSavedAmount, $oldSavedAmount), null, null));
|
||||
}
|
||||
}
|
||||
if (0 === count($toBeLinked)) {
|
||||
|
||||
@@ -28,6 +28,7 @@ use FireflyIII\Enums\WebhookTrigger;
|
||||
use FireflyIII\Events\DestroyedTransactionGroup;
|
||||
use FireflyIII\Events\RequestedSendWebhookMessages;
|
||||
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use FireflyIII\Support\Models\AccountBalanceCalculator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -61,6 +62,9 @@ class DestroyedGroupEventHandler
|
||||
|
||||
private function updateRunningBalance(DestroyedTransactionGroup $event): void
|
||||
{
|
||||
if (false === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
|
||||
return;
|
||||
}
|
||||
Log::debug(__METHOD__);
|
||||
$group = $event->transactionGroup;
|
||||
foreach ($group->transactionJournals as $journal) {
|
||||
|
||||
@@ -34,6 +34,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
|
||||
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use FireflyIII\Support\Models\AccountBalanceCalculator;
|
||||
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -217,6 +218,9 @@ class UpdatedGroupEventHandler
|
||||
|
||||
private function updateRunningBalance(UpdatedTransactionGroup $event): void
|
||||
{
|
||||
if (false === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
|
||||
return;
|
||||
}
|
||||
Log::debug(__METHOD__);
|
||||
$group = $event->transactionGroup;
|
||||
foreach ($group->transactionJournals as $journal) {
|
||||
|
||||
@@ -25,7 +25,9 @@ namespace FireflyIII\Handlers\Observer;
|
||||
|
||||
use FireflyIII\Models\Attachment;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionJournalLink;
|
||||
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
@@ -48,13 +50,37 @@ class TransactionJournalObserver
|
||||
}
|
||||
});
|
||||
|
||||
// delete all links:
|
||||
TransactionJournalLink::where('source_id', $transactionJournal->id)->delete();
|
||||
TransactionJournalLink::where('destination_id', $transactionJournal->id)->delete();
|
||||
|
||||
// update events
|
||||
// TODO move to repository
|
||||
$transactionJournal->piggyBankEvents()->update(['transaction_journal_id' => null]);
|
||||
|
||||
// delete all from 'budget_transaction_journal'
|
||||
DB::table('budget_transaction_journal')->where('transaction_journal_id', $transactionJournal->id)->delete();
|
||||
|
||||
// delete all from 'category_transaction_journal'
|
||||
DB::table('category_transaction_journal')->where('transaction_journal_id', $transactionJournal->id)->delete();
|
||||
|
||||
// delete all from 'tag_transaction_journal'
|
||||
DB::table('tag_transaction_journal')->where('transaction_journal_id', $transactionJournal->id)->delete();
|
||||
|
||||
/** @var Attachment $attachment */
|
||||
foreach ($transactionJournal->attachments()->get() as $attachment) {
|
||||
$repository->destroy($attachment);
|
||||
}
|
||||
$transactionJournal->transactionJournalMeta()->delete();
|
||||
$transactionJournal->locations()->delete();
|
||||
$transactionJournal->notes()->delete();
|
||||
$transactionJournal->sourceJournalLinks()->delete();
|
||||
$transactionJournal->destJournalLinks()->delete();
|
||||
$transactionJournal->auditLogEntries()->delete();
|
||||
|
||||
// set all transactions AFTER this one to balance_dirty for recalc.
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Bill;
|
||||
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\ObjectGroup\OrganisesObjectGroups;
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
@@ -38,6 +37,7 @@ use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
|
||||
/**
|
||||
@@ -82,22 +82,18 @@ class IndexController extends Controller
|
||||
|
||||
|
||||
$parameters = new ParameterBag();
|
||||
// sub one day from temp start so the last paid date is one day before it should be.
|
||||
$tempStart = clone $start;
|
||||
// 2023-06-23 do not sub one day from temp start, fix is in BillTransformer::payDates instead
|
||||
// $tempStart->subDay();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setStart($tempStart);
|
||||
$enrichment->setStart($start->clone());
|
||||
$enrichment->setEnd($end);
|
||||
$collection = $enrichment->enrich($collection);
|
||||
|
||||
|
||||
$parameters->set('start', $tempStart);
|
||||
$parameters->set('start', $start->clone());
|
||||
$parameters->set('end', $end);
|
||||
$parameters->set('convertToPrimary', $this->convertToPrimary);
|
||||
$parameters->set('primaryCurrency', $this->primaryCurrency);
|
||||
@@ -152,16 +148,28 @@ class IndexController extends Controller
|
||||
|
||||
private function getSums(array $bills): array
|
||||
{
|
||||
Log::debug(sprintf('now in getSums(count:%d)', count($bills)));
|
||||
$sums = [];
|
||||
$range = Navigation::getViewRange(true);
|
||||
|
||||
/** @var array $group */
|
||||
foreach ($bills as $groupOrder => $group) {
|
||||
Log::debug(sprintf('Summing up group "%s"', $group['object_group_title']));
|
||||
if (0 === count($group['bills'])) {
|
||||
Log::debug('Group has no subscriptions, continue');
|
||||
|
||||
continue;
|
||||
}
|
||||
Log::debug(sprintf('Group has %d subscription(s)', count($group['bills'])));
|
||||
|
||||
/** @var array $bill */
|
||||
foreach ($group['bills'] as $bill) {
|
||||
if (false === $bill['active']) {
|
||||
Log::debug(sprintf('Skip subscription #%d, inactive.', $bill['id']));
|
||||
|
||||
continue;
|
||||
}
|
||||
Log::debug(sprintf('Now at subscription #%d.', $bill['id']));
|
||||
|
||||
$currencyId = $bill['currency_id'];
|
||||
$sums[$groupOrder][$currencyId] ??= [
|
||||
@@ -175,26 +183,32 @@ class IndexController extends Controller
|
||||
'period' => $range,
|
||||
'per_period' => '0',
|
||||
];
|
||||
Log::debug(sprintf('Start with avg:%s, total_left_to_pay:%s, per_period:%s', $sums[$groupOrder][$currencyId]['avg'], $sums[$groupOrder][$currencyId]['total_left_to_pay'], $sums[$groupOrder][$currencyId]['per_period']));
|
||||
|
||||
// only fill in avg when bill is active.
|
||||
if (null !== $bill['next_expected_match']) {
|
||||
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
|
||||
$avg = bcmul($avg, (string)count($bill['pay_dates']));
|
||||
$sums[$groupOrder][$currencyId]['avg'] = bcadd($sums[$groupOrder][$currencyId]['avg'], $avg);
|
||||
}
|
||||
// only fill in total_left_to_pay when bill is not yet paid.
|
||||
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
|
||||
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
|
||||
if ($count > 0) {
|
||||
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
|
||||
$avg = bcmul($avg, (string)$count);
|
||||
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
|
||||
Log::debug(sprintf('next expected match is "%s", avg is now %s', $bill['next_expected_match'], $sums[$groupOrder][$currencyId]['avg']));
|
||||
|
||||
// only fill in total_left_to_pay when bill is not yet paid.
|
||||
// #11474 and when it is expected in the current period
|
||||
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
|
||||
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
|
||||
if ($count > 0) {
|
||||
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
|
||||
$avg = bcmul($avg, (string)$count);
|
||||
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
|
||||
Log::debug(sprintf('Bill has %d dates that need payment, total left to pay is now %s', $count, $sums[$groupOrder][$currencyId]['total_left_to_pay']), $bill['pay_dates']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$perPeriod = $this->amountPerPeriod($bill, $range);
|
||||
Log::debug(sprintf('Add amount %s to per_period', $perPeriod));
|
||||
// fill in per period regardless:
|
||||
$sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $this->amountPerPeriod($bill, $range));
|
||||
$sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $perPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,9 @@ class DebugController extends Controller
|
||||
|
||||
// also do some recalculations.
|
||||
Artisan::call('correction:recalculates-liabilities');
|
||||
AccountBalanceCalculator::recalculateAll(false);
|
||||
if (true === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
|
||||
AccountBalanceCalculator::recalculateAll(false);
|
||||
}
|
||||
|
||||
try {
|
||||
Artisan::call('twig:clean');
|
||||
|
||||
@@ -25,8 +25,8 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Jobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
|
||||
use FireflyIII\Events\Model\Subscription\SubscriptionNeedsExtensionOrRenewal;
|
||||
use FireflyIII\Events\Model\Subscription\SubscriptionsAreOverdueForPayment;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
@@ -143,7 +143,7 @@ class WarnAboutBills implements ShouldQueue
|
||||
{
|
||||
$diff = $this->getDiff($bill, $field);
|
||||
Log::debug('Will now send warning!');
|
||||
event(new WarnUserAboutBill($bill, $field, $diff));
|
||||
event(new SubscriptionNeedsExtensionOrRenewal($bill, $field, $diff));
|
||||
}
|
||||
|
||||
public function setDate(Carbon $date): void
|
||||
@@ -197,8 +197,8 @@ class WarnAboutBills implements ShouldQueue
|
||||
private function sendOverdueAlerts(User $user, array $overdue): void
|
||||
{
|
||||
if (count($overdue) > 0) {
|
||||
Log::debug(sprintf('Will now send warning about overdue bill for user #%d.', $user->id));
|
||||
event(new WarnUserAboutOverdueSubscriptions($user, $overdue));
|
||||
Log::debug(sprintf('Will now send warning about overdue subscription(s) for user #%d.', $user->id));
|
||||
event(new SubscriptionsAreOverdueForPayment($user, $overdue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* PiggyBankEventHandler.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
* CreatesPiggyBankEventForChangedAmount.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -20,43 +21,17 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace FireflyIII\Listeners\Model\PiggyBank;
|
||||
|
||||
namespace FireflyIII\Handlers\Events\Model;
|
||||
|
||||
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
|
||||
use FireflyIII\Events\Model\PiggyBank\ChangedName;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Events\Model\PiggyBank\PiggyBankAmountIsChanged;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class PiggyBankEventHandler
|
||||
*/
|
||||
class PiggyBankEventHandler
|
||||
class CreatesPiggyBankEventForChangedAmount implements ShouldQueue
|
||||
{
|
||||
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
|
||||
public function handle(PiggyBankAmountIsChanged $event): void
|
||||
{
|
||||
// find journal if group is present.
|
||||
$journal = $event->transactionJournal;
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* UpdatesRulesForChangedPiggyBankName.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Listeners\Model\PiggyBank;
|
||||
|
||||
use FireflyIII\Events\Model\PiggyBank\PiggyBankNameIsChanged;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class UpdatesRulesForChangedPiggyBankName implements ShouldQueue
|
||||
{
|
||||
public function handle(PiggyBankNameIsChanged $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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* RuleHandler.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
* NotifiesUserAboutFailedRuleAction.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -20,24 +21,20 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events\Model;
|
||||
namespace FireflyIII\Listeners\Model\Rule;
|
||||
|
||||
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
|
||||
use FireflyIII\Events\Model\Rule\RuleActionFailedOnObject;
|
||||
use FireflyIII\Notifications\User\RuleActionFailed;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
/**
|
||||
* Class RuleHandler
|
||||
*/
|
||||
class RuleHandler
|
||||
class NotifiesUserAboutFailedRuleAction implements ShouldQueue
|
||||
{
|
||||
public function ruleActionFailedOnArray(RuleActionFailedOnArray $event): void
|
||||
public function handle(RuleActionFailedOnArray|RuleActionFailedOnObject $event): void
|
||||
{
|
||||
$ruleAction = $event->ruleAction;
|
||||
$rule = $ruleAction->rule;
|
||||
@@ -52,9 +49,12 @@ class RuleHandler
|
||||
$error = $event->error;
|
||||
$user = $ruleAction->rule->user;
|
||||
|
||||
$mainMessage = trans('rules.main_message', ['rule' => $rule->title, 'action' => $ruleAction->action_type, 'group' => $journal['transaction_group_id'], 'error' => $error]);
|
||||
$groupTitle = $journal['description'] ?? '';
|
||||
$groupLink = route('transactions.show', [$journal['transaction_group_id']]);
|
||||
$groupId = is_array($journal) ? $journal['transaction_group_id'] : $journal->transaction_group_id;
|
||||
$groupTitle = is_array($journal) ? ($journal['description'] ?? '') : ($journal->description ?? '');
|
||||
|
||||
|
||||
$mainMessage = trans('rules.main_message', ['rule' => $rule->title, 'action' => $ruleAction->action_type, 'group' => $groupId, 'error' => $error]);
|
||||
$groupLink = route('transactions.show', [$groupId]);
|
||||
$ruleTitle = $rule->title;
|
||||
$ruleLink = route('rules.edit', [$rule->id]);
|
||||
$params = [$mainMessage, $groupTitle, $groupLink, $ruleTitle, $ruleLink];
|
||||
@@ -65,33 +65,4 @@ class RuleHandler
|
||||
Log::error(sprintf('[a] Error sending notification that the rule action failed: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public function ruleActionFailedOnObject(RuleActionFailedOnObject $event): void
|
||||
{
|
||||
$ruleAction = $event->ruleAction;
|
||||
$rule = $ruleAction->rule;
|
||||
|
||||
/** @var bool $preference */
|
||||
$preference = Preferences::getForUser($rule->user, 'notification_rule_action_failures', true)->data;
|
||||
if (false === $preference) {
|
||||
return;
|
||||
}
|
||||
Log::debug('Now in ruleActionFailedOnObject');
|
||||
$journal = $event->journal;
|
||||
$error = $event->error;
|
||||
$user = $ruleAction->rule->user;
|
||||
|
||||
$mainMessage = trans('rules.main_message', ['rule' => $rule->title, 'action' => $ruleAction->action_type, 'group' => $journal->transaction_group_id, 'error' => $error]);
|
||||
$groupTitle = $journal->description ?? '';
|
||||
$groupLink = route('transactions.show', [$journal->transaction_group_id]);
|
||||
$ruleTitle = $rule->title;
|
||||
$ruleLink = route('rules.edit', [$rule->id]);
|
||||
$params = [$mainMessage, $groupTitle, $groupLink, $ruleTitle, $ruleLink];
|
||||
|
||||
try {
|
||||
Notification::send($user, new RuleActionFailed($params));
|
||||
} catch (ClientException $e) {
|
||||
Log::error(sprintf('[b] Error sending notification that the rule action failed: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* NotifiesAboutExtensionOrRenewal.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Listeners\Model\Subscription;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Events\Model\Subscription\SubscriptionNeedsExtensionOrRenewal;
|
||||
use FireflyIII\Notifications\User\BillReminder;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class NotifiesAboutExtensionOrRenewal implements ShouldQueue
|
||||
{
|
||||
public function handle(SubscriptionNeedsExtensionOrRenewal $event): void
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$subscription = $event->subscription;
|
||||
|
||||
/** @var bool $preference */
|
||||
$preference = Preferences::getForUser($subscription->user, 'notification_bill_reminder', true)->data;
|
||||
|
||||
if (true === $preference) {
|
||||
Log::debug('Subscription reminder is true!');
|
||||
|
||||
try {
|
||||
Notification::send($subscription->user, new BillReminder($subscription, $event->field, $event->diff));
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug('User has disabled subscription reminders.');
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* BillEventHandler.php
|
||||
* Copyright (c) 2022 james@firefly-iii.org
|
||||
* NotifiesAboutOverdueSubscription.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -20,35 +21,27 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
namespace FireflyIII\Listeners\Model\Subscription;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
|
||||
use FireflyIII\Events\Model\Subscription\SubscriptionsAreOverdueForPayment;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Notifications\User\BillReminder;
|
||||
use FireflyIII\Notifications\User\SubscriptionsOverdueReminder;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
use function Safe\json_encode;
|
||||
|
||||
/**
|
||||
* Class BillEventHandler
|
||||
*/
|
||||
class BillEventHandler
|
||||
class NotifiesAboutOverdueSubscriptions implements ShouldQueue
|
||||
{
|
||||
public function warnAboutOverdueSubscriptions(WarnUserAboutOverdueSubscriptions $event): void
|
||||
public function handle(SubscriptionsAreOverdueForPayment $event): void
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
// make sure user does not get the warning twice.
|
||||
$overdue = $event->overdue;
|
||||
$user = $event->user;
|
||||
$toBeWarned = [];
|
||||
Log::debug(sprintf('%d bills to warn about.', count($overdue)));
|
||||
Log::debug(sprintf('%d subscriptions to warn about.', count($overdue)));
|
||||
foreach ($overdue as $item) {
|
||||
/** @var Bill $bill */
|
||||
$bill = $item['bill'];
|
||||
@@ -62,12 +55,12 @@ class BillEventHandler
|
||||
$toBeWarned[] = $item;
|
||||
}
|
||||
unset($bill);
|
||||
Log::debug(sprintf('Now %d bills to warn about.', count($toBeWarned)));
|
||||
Log::debug(sprintf('Now %d subscription(s) to warn about.', count($toBeWarned)));
|
||||
|
||||
/** @var bool $sendNotification */
|
||||
$sendNotification = Preferences::getForUser($user, 'notification_bill_reminder', true)->data;
|
||||
if (false === $sendNotification) {
|
||||
Log::debug('User has disabled bill reminders.');
|
||||
Log::debug('User has disabled subscription reminders.');
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -105,39 +98,4 @@ class BillEventHandler
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function warnAboutBill(WarnUserAboutBill $event): void
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$bill = $event->bill;
|
||||
|
||||
/** @var bool $preference */
|
||||
$preference = Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data;
|
||||
|
||||
if (true === $preference) {
|
||||
Log::debug('Bill reminder is true!');
|
||||
|
||||
try {
|
||||
Notification::send($bill->user, new BillReminder($bill, $event->field, $event->diff));
|
||||
} catch (Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
if (str_contains($message, 'Bcc')) {
|
||||
Log::warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
if (str_contains($message, 'RFC 2822')) {
|
||||
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug('User has disabled bill reminders.');
|
||||
}
|
||||
}
|
||||
@@ -45,11 +45,11 @@ class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
Schema::defaultStringLength(191);
|
||||
// Passport::$clientUuids = false;
|
||||
Response::macro('api', static function (array $value) {
|
||||
Response::macro('api', function (array $value) {
|
||||
$headers = [
|
||||
'Cache-Control' => 'no-store',
|
||||
];
|
||||
$uuid = (string) request()->header('X-Trace-Id');
|
||||
$uuid = (string)request()->header('X-Trace-Id');
|
||||
if ('' !== trim($uuid) && (1 === preg_match('/^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i', trim($uuid)))) {
|
||||
$headers['X-Trace-Id'] = $uuid;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
});
|
||||
|
||||
// blade extension
|
||||
Blade::directive('activeXRoutePartial', static function (string $route): string {
|
||||
Blade::directive('activeXRoutePartial', function (string $route): string {
|
||||
$name = Route::getCurrentRoute()->getName() ?? '';
|
||||
if (str_contains($name, $route)) {
|
||||
return 'menu-open';
|
||||
@@ -69,7 +69,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
|
||||
return '';
|
||||
});
|
||||
Blade::if('partialroute', static function (string $route, string $firstParam = ''): bool {
|
||||
Blade::if('partialroute', function (string $route, string $firstParam = ''): bool {
|
||||
$name = Route::getCurrentRoute()->getName() ?? '';
|
||||
if ('' === $firstParam && str_contains($name, $route)) {
|
||||
return true;
|
||||
|
||||
@@ -27,12 +27,6 @@ use FireflyIII\Events\ActuallyLoggedIn;
|
||||
use FireflyIII\Events\Admin\InvitationCreated;
|
||||
use FireflyIII\Events\DestroyedTransactionGroup;
|
||||
use FireflyIII\Events\DetectedNewIPAddress;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
|
||||
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\Model\TransactionGroup\TriggeredStoredTransactionGroup;
|
||||
use FireflyIII\Events\NewVersionAvailable;
|
||||
use FireflyIII\Events\Preferences\UserGroupChangedPrimaryCurrency;
|
||||
@@ -73,145 +67,145 @@ class EventServiceProvider extends ServiceProvider
|
||||
protected $listen
|
||||
= [
|
||||
// is a User related event.
|
||||
RegisteredUser::class => [
|
||||
RegisteredUser::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationMail',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendAdminRegistrationNotification',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@attachUserRole',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@createGroupMembership',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@createExchangeRates',
|
||||
],
|
||||
UserAttemptedLogin::class => [
|
||||
UserAttemptedLogin::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendLoginAttemptNotification',
|
||||
],
|
||||
// is a User related event.
|
||||
Login::class => [
|
||||
Login::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish',
|
||||
],
|
||||
ActuallyLoggedIn::class => [
|
||||
ActuallyLoggedIn::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@storeUserIPAddress',
|
||||
],
|
||||
DetectedNewIPAddress::class => [
|
||||
DetectedNewIPAddress::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@notifyNewIPAddress',
|
||||
],
|
||||
RequestedVersionCheckStatus::class => [
|
||||
RequestedVersionCheckStatus::class => [
|
||||
'FireflyIII\Handlers\Events\VersionCheckEventHandler@checkForUpdates',
|
||||
],
|
||||
RequestedReportOnJournals::class => [
|
||||
RequestedReportOnJournals::class => [
|
||||
'FireflyIII\Handlers\Events\AutomationHandler@reportJournals',
|
||||
],
|
||||
|
||||
// is a User related event.
|
||||
RequestedNewPassword::class => [
|
||||
RequestedNewPassword::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendNewPassword',
|
||||
],
|
||||
UserTestNotificationChannel::class => [
|
||||
UserTestNotificationChannel::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendTestNotification',
|
||||
],
|
||||
// is a User related event.
|
||||
UserChangedEmail::class => [
|
||||
UserChangedEmail::class => [
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeConfirmMail',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeUndoMail',
|
||||
],
|
||||
// admin related
|
||||
OwnerTestNotificationChannel::class => [
|
||||
OwnerTestNotificationChannel::class => [
|
||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendTestNotification',
|
||||
],
|
||||
NewVersionAvailable::class => [
|
||||
NewVersionAvailable::class => [
|
||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion',
|
||||
],
|
||||
InvitationCreated::class => [
|
||||
InvitationCreated::class => [
|
||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification',
|
||||
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite',
|
||||
],
|
||||
UnknownUserAttemptedLogin::class => [
|
||||
UnknownUserAttemptedLogin::class => [
|
||||
'FireflyIII\Handlers\Events\AdminEventHandler@sendLoginAttemptNotification',
|
||||
],
|
||||
|
||||
// is a Transaction Journal related event.
|
||||
StoredTransactionGroup::class => [
|
||||
StoredTransactionGroup::class => [
|
||||
'FireflyIII\Handlers\Events\StoredGroupEventHandler@runAllHandlers',
|
||||
],
|
||||
TriggeredStoredTransactionGroup::class => [
|
||||
TriggeredStoredTransactionGroup::class => [
|
||||
'FireflyIII\Handlers\Events\StoredGroupEventHandler@triggerRulesManually',
|
||||
],
|
||||
// is a Transaction Journal related event.
|
||||
UpdatedTransactionGroup::class => [
|
||||
UpdatedTransactionGroup::class => [
|
||||
'FireflyIII\Handlers\Events\UpdatedGroupEventHandler@runAllHandlers',
|
||||
],
|
||||
DestroyedTransactionGroup::class => [
|
||||
DestroyedTransactionGroup::class => [
|
||||
'FireflyIII\Handlers\Events\DestroyedGroupEventHandler@runAllHandlers',
|
||||
],
|
||||
// API related events:
|
||||
AccessTokenCreated::class => [
|
||||
AccessTokenCreated::class => [
|
||||
'FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated',
|
||||
],
|
||||
|
||||
// Webhook related event:
|
||||
RequestedSendWebhookMessages::class => [
|
||||
RequestedSendWebhookMessages::class => [
|
||||
'FireflyIII\Handlers\Events\WebhookEventHandler@sendWebhookMessages',
|
||||
],
|
||||
|
||||
// account related events:
|
||||
StoredAccount::class => [
|
||||
StoredAccount::class => [
|
||||
'FireflyIII\Handlers\Events\StoredAccountEventHandler@recalculateCredit',
|
||||
],
|
||||
UpdatedAccount::class => [
|
||||
UpdatedAccount::class => [
|
||||
'FireflyIII\Handlers\Events\UpdatedAccountEventHandler@recalculateCredit',
|
||||
],
|
||||
|
||||
// bill related events:
|
||||
WarnUserAboutBill::class => [
|
||||
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutBill',
|
||||
],
|
||||
WarnUserAboutOverdueSubscriptions::class => [
|
||||
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutOverdueSubscriptions',
|
||||
],
|
||||
// subscription related events:
|
||||
// SubscriptionNeedsExtensionOrRenewal::class => [
|
||||
// 'FireflyIII\Handlers\Events\BillEventHandler@warnAboutBill',
|
||||
// ],
|
||||
// WarnUserAboutOverdueSubscriptions::class => [
|
||||
// 'FireflyIII\Handlers\Events\BillEventHandler@warnAboutOverdueSubscriptions',
|
||||
// ],
|
||||
|
||||
// audit log events:
|
||||
TriggeredAuditLog::class => [
|
||||
TriggeredAuditLog::class => [
|
||||
'FireflyIII\Handlers\Events\AuditEventHandler@storeAuditEvent',
|
||||
],
|
||||
// piggy bank related events:
|
||||
ChangedAmount::class => [
|
||||
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changePiggyAmount',
|
||||
],
|
||||
ChangedName::class => [
|
||||
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName',
|
||||
],
|
||||
// PiggyBankAmountIsChanged::class => [
|
||||
// 'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changePiggyAmount',
|
||||
// ],
|
||||
// ChangedName::class => [
|
||||
// 'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName',
|
||||
// ],
|
||||
|
||||
// rule actions
|
||||
RuleActionFailedOnArray::class => [
|
||||
'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnArray',
|
||||
],
|
||||
RuleActionFailedOnObject::class => [
|
||||
'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnObject',
|
||||
],
|
||||
// RuleActionFailedOnArray::class => [
|
||||
// 'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnArray',
|
||||
// ],
|
||||
// RuleActionFailedOnObject::class => [
|
||||
// 'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnObject',
|
||||
// ],
|
||||
|
||||
// security related
|
||||
EnabledMFA::class => [
|
||||
EnabledMFA::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAEnabledMail',
|
||||
],
|
||||
DisabledMFA::class => [
|
||||
DisabledMFA::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFADisabledMail',
|
||||
],
|
||||
MFANewBackupCodes::class => [
|
||||
MFANewBackupCodes::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendNewMFABackupCodesMail',
|
||||
],
|
||||
MFAUsedBackupCode::class => [
|
||||
MFAUsedBackupCode::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendUsedBackupCodeMail',
|
||||
],
|
||||
MFABackupFewLeft::class => [
|
||||
MFABackupFewLeft::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendBackupFewLeftMail',
|
||||
],
|
||||
MFABackupNoLeft::class => [
|
||||
MFABackupNoLeft::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendBackupNoLeftMail',
|
||||
],
|
||||
MFAManyFailedAttempts::class => [
|
||||
MFAManyFailedAttempts::class => [
|
||||
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAFailedAttemptsMail',
|
||||
],
|
||||
// preferences
|
||||
UserGroupChangedPrimaryCurrency::class => [
|
||||
UserGroupChangedPrimaryCurrency::class => [
|
||||
'FireflyIII\Handlers\Events\PreferencesEventHandler@resetPrimaryCurrencyAmounts',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -514,7 +514,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
|
||||
{
|
||||
$query = sprintf('%%%s%%', $query);
|
||||
|
||||
return $this->user->bills()->whereLike('name', $query)->take($limit)->get();
|
||||
return $this->user->bills()->orderBy('name', 'ASC')->whereLike('name', $query)->take($limit)->get();
|
||||
}
|
||||
|
||||
public function setObjectGroup(Bill $bill, string $objectGroupTitle): Bill
|
||||
|
||||
@@ -331,7 +331,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
|
||||
|
||||
public function searchCategory(string $query, int $limit): Collection
|
||||
{
|
||||
$search = $this->user->categories();
|
||||
$search = $this->user->categories()->orderBy('name', 'ASC');
|
||||
if ('' !== $query) {
|
||||
$search->whereLike('name', sprintf('%%%s%%', $query));
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
|
||||
|
||||
public function searchCurrency(string $search, int $limit): Collection
|
||||
{
|
||||
$query = TransactionCurrency::where('enabled', true);
|
||||
$query = TransactionCurrency::where('enabled', true)->orderBy('code', 'ASC');
|
||||
if ('' !== $search) {
|
||||
$query->whereLike('name', sprintf('%%%s%%', $search));
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
|
||||
{
|
||||
$query = $this->user->transactionJournals()
|
||||
->orderBy('date', 'DESC')
|
||||
->orderBy('description', 'ASC')
|
||||
;
|
||||
if ('' !== $search) {
|
||||
$query->whereLike('description', sprintf('%%%s%%', $search));
|
||||
|
||||
@@ -25,8 +25,8 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Repositories\PiggyBank;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
|
||||
use FireflyIII\Events\Model\PiggyBank\ChangedName;
|
||||
use FireflyIII\Events\Model\PiggyBank\PiggyBankAmountIsChanged;
|
||||
use FireflyIII\Events\Model\PiggyBank\PiggyBankNameIsChanged;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Factory\PiggyBankFactory;
|
||||
use FireflyIII\Models\Account;
|
||||
@@ -68,7 +68,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.
|
||||
@@ -82,7 +82,7 @@ trait ModifiesPiggyBanks
|
||||
$pivot->save();
|
||||
|
||||
Log::debug('ChangedAmount: removeAmount [a]: Trigger change for negative amount.');
|
||||
event(new ChangedAmount($piggyBank, bcmul($amount, '-1'), $journal, null));
|
||||
event(new PiggyBankAmountIsChanged($piggyBank, bcmul($amount, '-1'), $journal, null));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -91,7 +91,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.
|
||||
@@ -105,7 +105,7 @@ trait ModifiesPiggyBanks
|
||||
$pivot->save();
|
||||
|
||||
Log::debug('ChangedAmount: addAmount [b]: Trigger change for positive amount.');
|
||||
event(new ChangedAmount($piggyBank, $amount, $journal, null));
|
||||
event(new PiggyBankAmountIsChanged($piggyBank, $amount, $journal, null));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -123,13 +123,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)));
|
||||
@@ -141,7 +141,7 @@ trait ModifiesPiggyBanks
|
||||
{
|
||||
$savedSoFar = $this->getCurrentAmount($piggyBank, $account);
|
||||
|
||||
return bccomp($amount, (string) $savedSoFar) <= 0;
|
||||
return bccomp($amount, (string)$savedSoFar) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,11 +178,11 @@ trait ModifiesPiggyBanks
|
||||
|
||||
if (-1 === bccomp($difference, '0')) {
|
||||
Log::debug('ChangedAmount: addAmount [c]: Trigger change for negative amount.');
|
||||
event(new ChangedAmount($piggyBank, $difference, null, null));
|
||||
event(new PiggyBankAmountIsChanged($piggyBank, $difference, null, null));
|
||||
}
|
||||
if (1 === bccomp($difference, '0')) {
|
||||
Log::debug('ChangedAmount: addAmount [d]: Trigger change for positive amount.');
|
||||
event(new ChangedAmount($piggyBank, $difference, null, null));
|
||||
event(new PiggyBankAmountIsChanged($piggyBank, $difference, null, null));
|
||||
}
|
||||
|
||||
return $piggyBank;
|
||||
@@ -234,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));
|
||||
@@ -283,7 +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']));
|
||||
event(new PiggyBankNameIsChanged($piggyBank, $piggyBank->name, $data['name']));
|
||||
$piggyBank->name = $data['name'];
|
||||
}
|
||||
if (array_key_exists('transaction_currency_id', $data) && is_int($data['transaction_currency_id'])) {
|
||||
|
||||
@@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Repositories\PiggyBank;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
|
||||
use FireflyIII\Events\Model\PiggyBank\PiggyBankAmountIsChanged;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Factory\PiggyBankFactory;
|
||||
use FireflyIII\Models\Account;
|
||||
@@ -431,7 +431,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
|
||||
'objectGroups',
|
||||
]
|
||||
)
|
||||
->orderBy('piggy_banks.order', 'ASC')->distinct()
|
||||
->orderBy('piggy_banks.order', 'ASC')->orderBy('piggy_banks.name', 'ASC')->distinct()
|
||||
;
|
||||
if ('' !== $query) {
|
||||
$search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query));
|
||||
@@ -448,7 +448,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
|
||||
$piggyBank->piggyBankEvents()->delete();
|
||||
foreach ($piggyBank->accounts as $account) {
|
||||
if (0 !== bccomp('0', (string) $account->pivot->current_amount)) {
|
||||
event(new ChangedAmount($piggyBank, $account->pivot->current_amount, null, null));
|
||||
event(new PiggyBankAmountIsChanged($piggyBank, $account->pivot->current_amount, null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ class UserRepository implements UserRepositoryInterface
|
||||
if (!$user instanceof User) {
|
||||
throw new FireflyException('User is not a User object.');
|
||||
}
|
||||
$now = today(config('app.timezone'));
|
||||
$now = now(config('app.timezone'));
|
||||
$now->addDays(2);
|
||||
$invitee = new InvitedUser();
|
||||
$invitee->user()->associate($user);
|
||||
|
||||
@@ -25,12 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Services\Internal\Destroy;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use FireflyIII\Models\Attachment;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionJournalLink;
|
||||
use FireflyIII\Models\TransactionJournalMeta;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Class JournalDestroyService
|
||||
@@ -41,51 +36,6 @@ class JournalDestroyService
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journal->transactions()->get() as $transaction) {
|
||||
Log::debug(sprintf('Will now delete transaction #%d', $transaction->id));
|
||||
$transaction->delete();
|
||||
}
|
||||
|
||||
// also delete journal_meta entries.
|
||||
/** @var TransactionJournalMeta $meta */
|
||||
foreach ($journal->transactionJournalMeta()->get() as $meta) {
|
||||
Log::debug(sprintf('Will now delete meta-entry #%d', $meta->id));
|
||||
$meta->delete();
|
||||
}
|
||||
|
||||
// also delete attachments.
|
||||
/** @var Attachment $attachment */
|
||||
foreach ($journal->attachments()->get() as $attachment) {
|
||||
$attachment->delete();
|
||||
}
|
||||
|
||||
// delete all from 'budget_transaction_journal'
|
||||
DB::table('budget_transaction_journal')
|
||||
->where('transaction_journal_id', $journal->id)->delete()
|
||||
;
|
||||
|
||||
// delete all from 'category_transaction_journal'
|
||||
DB::table('category_transaction_journal')
|
||||
->where('transaction_journal_id', $journal->id)->delete()
|
||||
;
|
||||
|
||||
// delete all from 'tag_transaction_journal'
|
||||
DB::table('tag_transaction_journal')
|
||||
->where('transaction_journal_id', $journal->id)->delete()
|
||||
;
|
||||
|
||||
// delete all links:
|
||||
TransactionJournalLink::where('source_id', $journal->id)->delete();
|
||||
TransactionJournalLink::where('destination_id', $journal->id)->delete();
|
||||
|
||||
// delete all notes
|
||||
$journal->notes()->delete();
|
||||
|
||||
// update events
|
||||
// TODO move to repository
|
||||
$journal->piggyBankEvents()->update(['transaction_journal_id' => null]);
|
||||
|
||||
$journal->delete();
|
||||
|
||||
// delete group, if group is empty:
|
||||
|
||||
@@ -31,6 +31,8 @@ use FireflyIII\Models\AccountBalance;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -64,6 +66,9 @@ class AccountBalanceCalculator
|
||||
|
||||
public static function recalculateForJournal(TransactionJournal $transactionJournal): void
|
||||
{
|
||||
if (false === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
|
||||
return;
|
||||
}
|
||||
Log::debug(__METHOD__);
|
||||
$object = new self();
|
||||
|
||||
@@ -93,10 +98,9 @@ class AccountBalanceCalculator
|
||||
|
||||
return '0';
|
||||
}
|
||||
Log::debug(sprintf('getLatestBalance: notBefore date is "%s", calculating', $notBefore->format('Y-m-d')));
|
||||
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->whereNull('transactions.deleted_at')
|
||||
->where('transaction_journals.transaction_currency_id', $currencyId)
|
||||
->where('transactions.transaction_currency_id', $currencyId)
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
// this order is the same as GroupCollector
|
||||
->orderBy('transaction_journals.date', 'DESC')
|
||||
@@ -106,21 +110,32 @@ class AccountBalanceCalculator
|
||||
->orderBy('transactions.amount', 'DESC')
|
||||
->where('transactions.account_id', $accountId)
|
||||
;
|
||||
$notBefore->startOfDay();
|
||||
$query->where('transaction_journals.date', '<', $notBefore);
|
||||
|
||||
$first = $query->first(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount', 'transactions.balance_after']);
|
||||
|
||||
if (null === $first) {
|
||||
Log::debug(sprintf('Found no transactions for currency #%d and account #%d, return 0.', $currencyId, $accountId));
|
||||
|
||||
return '0';
|
||||
}
|
||||
|
||||
|
||||
$balance = (string)($first->balance_after ?? '0');
|
||||
Log::debug(sprintf('getLatestBalance: found balance: %s in transaction #%d', $balance, $first->id ?? 0));
|
||||
Log::debug(sprintf('getLatestBalance: found balance: %s in transaction #%d on moment %s', Steam::bcround($balance, 2), $first->id ?? 0, $notBefore->format('Y-m-d H:i:s')));
|
||||
|
||||
return $balance;
|
||||
}
|
||||
|
||||
private function optimizedCalculation(Collection $accounts, ?Carbon $notBefore = null): void
|
||||
{
|
||||
Log::debug('start of optimizedCalculation');
|
||||
if ($notBefore instanceof Carbon) {
|
||||
$notBefore->startOfDay();
|
||||
}
|
||||
|
||||
Log::debug(sprintf('start of optimizedCalculation with date "%s"', $notBefore?->format('Y-m-d H:i:s')));
|
||||
if ($accounts->count() > 0) {
|
||||
Log::debug(sprintf('Limited to %d account(s)', $accounts->count()));
|
||||
Log::debug(sprintf('Limited to %d account(s): %s', $accounts->count(), implode(', ', $accounts->pluck('id')->toArray())));
|
||||
}
|
||||
// collect all transactions and the change they make.
|
||||
$balances = [];
|
||||
@@ -139,18 +154,18 @@ class AccountBalanceCalculator
|
||||
$query->whereIn('transactions.account_id', $accounts->pluck('id')->toArray());
|
||||
}
|
||||
if ($notBefore instanceof Carbon) {
|
||||
$notBefore->startOfDay();
|
||||
$query->where('transaction_journals.date', '>=', $notBefore);
|
||||
}
|
||||
|
||||
$set = $query->get(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount']);
|
||||
Log::debug(sprintf('Counted %d transaction(s)', $set->count()));
|
||||
Log::debug(sprintf('Found %d transaction(s)', $set->count()));
|
||||
|
||||
// the balance value is an array.
|
||||
// first entry is the balance, second is the date.
|
||||
|
||||
/** @var Transaction $entry */
|
||||
foreach ($set as $entry) {
|
||||
Log::debug(sprintf('Processing transaction #%d with currency #%d and amount %s', $entry->id, $entry->transaction_currency_id, Steam::bcround($entry->amount)));
|
||||
// start with empty array:
|
||||
$balances[$entry->account_id] ??= [];
|
||||
$balances[$entry->account_id][$entry->transaction_currency_id] ??= [$this->getLatestBalance($entry->account_id, $entry->transaction_currency_id, $notBefore), null];
|
||||
@@ -158,6 +173,9 @@ class AccountBalanceCalculator
|
||||
// before and after are easy:
|
||||
$before = $balances[$entry->account_id][$entry->transaction_currency_id][0];
|
||||
$after = bcadd($before, (string)$entry->amount);
|
||||
|
||||
Log::debug(sprintf('Before:%s, after:%s', Steam::bcround($before, 2), Steam::bcround($after, 2)));
|
||||
|
||||
if (true === $entry->balance_dirty || $accounts->count() > 0) {
|
||||
// update the transaction:
|
||||
$entry->balance_before = $before;
|
||||
|
||||
@@ -24,12 +24,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\System;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Laravel\Passport\Console\KeysCommand;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
@@ -48,16 +48,27 @@ class OAuthKeys
|
||||
|
||||
public static function generateKeys(): void
|
||||
{
|
||||
Log::debug('Will now run generateKeys()');
|
||||
Artisan::registerCommand(new KeysCommand());
|
||||
Artisan::call('firefly-iii:laravel-passport-keys');
|
||||
Log::debug('Done with generateKeys()');
|
||||
}
|
||||
|
||||
public static function hasKeyFiles(): bool
|
||||
{
|
||||
$private = storage_path('oauth-private.key');
|
||||
$public = storage_path('oauth-public.key');
|
||||
Log::debug('hasKeyFiles()');
|
||||
$private = storage_path('oauth-private.key');
|
||||
$public = storage_path('oauth-public.key');
|
||||
$privateExists = file_exists($private);
|
||||
$publicExists = file_exists($public);
|
||||
|
||||
return file_exists($private) && file_exists($public);
|
||||
Log::debug(sprintf('Private key file at "%s" exists? %s', $private, var_export($privateExists, true)));
|
||||
Log::debug(sprintf('Public key file at "%s" exists ? %s', $public, var_export($publicExists, true)));
|
||||
|
||||
$result = file_exists($private) && file_exists($public);
|
||||
Log::debug(sprintf('Method will return %s', var_export($result, true)));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function keysInDatabase(): bool
|
||||
@@ -65,17 +76,36 @@ class OAuthKeys
|
||||
$privateKey = '';
|
||||
$publicKey = '';
|
||||
// better check if keys are in the database:
|
||||
if (FireflyConfig::has(self::PRIVATE_KEY) && FireflyConfig::has(self::PUBLIC_KEY)) {
|
||||
$hasPrivate = FireflyConfig::has(self::PRIVATE_KEY);
|
||||
$hasPublic = FireflyConfig::has(self::PUBLIC_KEY);
|
||||
|
||||
Log::debug(sprintf('keysInDatabase: hasPrivate:%s, hasPublic:%s', var_export($hasPrivate, true), var_export($hasPublic, true)));
|
||||
|
||||
if ($hasPrivate && $hasPublic) {
|
||||
try {
|
||||
$privateKey = (string)FireflyConfig::get(self::PRIVATE_KEY)?->data;
|
||||
$publicKey = (string)FireflyConfig::get(self::PUBLIC_KEY)?->data;
|
||||
$privateKey = trim((string)FireflyConfig::get(self::PRIVATE_KEY)?->data);
|
||||
$publicKey = trim((string)FireflyConfig::get(self::PUBLIC_KEY)?->data);
|
||||
} catch (ContainerExceptionInterface|FireflyException|NotFoundExceptionInterface $e) {
|
||||
Log::error(sprintf('Could not validate keysInDatabase(): %s', $e->getMessage()));
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
if ('' === $privateKey) {
|
||||
Log::warning('Private key in DB is unexpectedly an empty string.');
|
||||
}
|
||||
if ('' === $publicKey) {
|
||||
Log::warning('Public key in DB is unexpectedly an empty string.');
|
||||
}
|
||||
if ('' !== $privateKey) {
|
||||
Log::debug(sprintf('SHA2 hash of private key in DB: %s', hash('sha256', $privateKey)));
|
||||
}
|
||||
if ('' !== $publicKey) {
|
||||
Log::debug(sprintf('SHA2 hash of public key in DB : %s', hash('sha256', $publicKey)));
|
||||
}
|
||||
$return = '' !== $privateKey && '' !== $publicKey;
|
||||
Log::debug(sprintf('keysInDatabase will return %s', var_export($return, true)));
|
||||
|
||||
return '' !== $privateKey && '' !== $publicKey;
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,12 +116,20 @@ class OAuthKeys
|
||||
*/
|
||||
public static function restoreKeysFromDB(): bool
|
||||
{
|
||||
Log::debug('restoreKeysFromDB()');
|
||||
$privateKey = (string)FireflyConfig::get(self::PRIVATE_KEY)?->data;
|
||||
$publicKey = (string)FireflyConfig::get(self::PUBLIC_KEY)?->data;
|
||||
|
||||
if ('' === $privateKey) {
|
||||
Log::warning('Private key is not in the database.');
|
||||
}
|
||||
if ('' === $publicKey) {
|
||||
Log::warning('Public key is not in the database.');
|
||||
}
|
||||
|
||||
try {
|
||||
$privateContent = Crypt::decrypt($privateKey);
|
||||
$publicContent = Crypt::decrypt($publicKey);
|
||||
$privateContent = trim(Crypt::decrypt($privateKey));
|
||||
$publicContent = trim(Crypt::decrypt($publicKey));
|
||||
} catch (DecryptException $e) {
|
||||
Log::error('Could not decrypt pub/private keypair.');
|
||||
Log::error($e->getMessage());
|
||||
@@ -99,6 +137,7 @@ class OAuthKeys
|
||||
// delete config vars from DB:
|
||||
FireflyConfig::delete(self::PRIVATE_KEY);
|
||||
FireflyConfig::delete(self::PUBLIC_KEY);
|
||||
Log::debug('Done with generateKeysFromDB(), return FALSE');
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -107,15 +146,24 @@ class OAuthKeys
|
||||
file_put_contents($private, $privateContent);
|
||||
file_put_contents($public, $publicContent);
|
||||
|
||||
Log::debug(sprintf('Will store private key with hash "%s" in file "%s"', hash('sha256', $privateContent), $private));
|
||||
Log::debug(sprintf('Will store public key with hash "%s" in file "%s"', hash('sha256', $publicContent), $public));
|
||||
Log::debug('Done with generateKeysFromDB()');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function storeKeysInDB(): void
|
||||
{
|
||||
$private = storage_path('oauth-private.key');
|
||||
$public = storage_path('oauth-public.key');
|
||||
FireflyConfig::set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private)));
|
||||
FireflyConfig::set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public)));
|
||||
$private = storage_path('oauth-private.key');
|
||||
$public = storage_path('oauth-public.key');
|
||||
$privateContent = file_get_contents($private);
|
||||
$publicContent = file_get_contents($public);
|
||||
FireflyConfig::set(self::PRIVATE_KEY, Crypt::encrypt($privateContent));
|
||||
FireflyConfig::set(self::PUBLIC_KEY, Crypt::encrypt($publicContent));
|
||||
|
||||
Log::debug(sprintf('Will store the content of file "%s" as "%s" in the database (hash: %s)', $private, self::PRIVATE_KEY, hash('sha256', $privateContent)));
|
||||
Log::debug(sprintf('Will store the content of file "%s" as "%s" in the database (hash: %s)', $public, self::PUBLIC_KEY, hash('sha256', $publicContent)));
|
||||
}
|
||||
|
||||
public static function verifyKeysRoutine(): void
|
||||
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Validation;
|
||||
|
||||
use ErrorException;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use Config;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
@@ -210,7 +211,12 @@ class FireflyValidator extends Validator
|
||||
$value = strtoupper($value);
|
||||
|
||||
// replace characters outside of ASCI range.
|
||||
$value = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
|
||||
try {
|
||||
$value = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
|
||||
} catch (ErrorException $e) {
|
||||
Log::error(sprintf('Could not convert IBAN "%s" to safe characters. Future steps may fail.', $value));
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
$search = [' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
|
||||
$replace = ['', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35'];
|
||||
|
||||
|
||||
20
changelog.md
20
changelog.md
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## v6.4.16 - 2026-01-18
|
||||
|
||||
> [!WARNING]
|
||||
> This will be one of the last Firefly III data importer releases that supports PHP 8.4.
|
||||
|
||||
### Fixed
|
||||
- [Discussion 11431](https://github.com/orgs/firefly-iii/discussions/11431) (Settings don't get saved) started by @PVTejas
|
||||
- [Issue 11473](https://github.com/firefly-iii/firefly-iii/issues/11473) (Searching transaction with two tags_contains returns results matching only one of those) reported by @F-DXI
|
||||
- [Issue 11474](https://github.com/firefly-iii/firefly-iii/issues/11474) (Potential error in sub total computation for group in subscription) reported by @ma-clog
|
||||
- [Issue 11479](https://github.com/firefly-iii/firefly-iii/issues/11479) (Editing a user profile as admin without setting a new password causes a 500 Internal server error) reported by @watertrainer
|
||||
- [Issue 11501](https://github.com/firefly-iii/firefly-iii/issues/11501) (Schema of /api/v1/available-budgets different from spec) reported by @RadCod3
|
||||
- [Issue 11502](https://github.com/firefly-iii/firefly-iii/issues/11502) (Visual bug - Transaction notes' markdown doesn't properly render code blocks in dark mode) reported by @AyluinReymaer
|
||||
- [Discussion 11508](https://github.com/orgs/firefly-iii/discussions/11508) (Grouped Piggy banks show as ungrouped when creating a transaction) started by @AyluinReymaer
|
||||
- [Discussion 11509](https://github.com/orgs/firefly-iii/discussions/11509) (IBAN - iconv(): Wrong encoding) started by @s0fax
|
||||
- [Discussion 11524](https://github.com/orgs/firefly-iii/discussions/11524) (Can items in dropdowns (specifically categories) be sorted alphabetically?) started by @mvpaderin
|
||||
- [Issue 11531](https://github.com/firefly-iii/firefly-iii/issues/11531) (Performance: updateRunningBalance executes even when use_running_balance is disabled, causing timeouts on Mass Edits) reported by @maxime-killinger
|
||||
|
||||
### Changed
|
||||
- Rules that delete a transaction will no longer throws a 500, but a 410.
|
||||
|
||||
## v6.4.15 - 2026-01-07
|
||||
|
||||
### Added
|
||||
|
||||
74
composer.lock
generated
74
composer.lock
generated
@@ -2176,16 +2176,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
"version": "v0.3.9",
|
||||
"version": "v0.3.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/prompts.git",
|
||||
"reference": "5c41bf0555b7cfefaad4e66d3046675829581ac4"
|
||||
"reference": "360ba095ef9f51017473505191fbd4ab73e1cab3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/5c41bf0555b7cfefaad4e66d3046675829581ac4",
|
||||
"reference": "5c41bf0555b7cfefaad4e66d3046675829581ac4",
|
||||
"url": "https://api.github.com/repos/laravel/prompts/zipball/360ba095ef9f51017473505191fbd4ab73e1cab3",
|
||||
"reference": "360ba095ef9f51017473505191fbd4ab73e1cab3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2229,9 +2229,9 @@
|
||||
"description": "Add beautiful and user-friendly forms to your command-line applications.",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/prompts/issues",
|
||||
"source": "https://github.com/laravel/prompts/tree/v0.3.9"
|
||||
"source": "https://github.com/laravel/prompts/tree/v0.3.10"
|
||||
},
|
||||
"time": "2026-01-07T21:00:29+00:00"
|
||||
"time": "2026-01-13T20:29:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sanctum",
|
||||
@@ -3304,20 +3304,20 @@
|
||||
},
|
||||
{
|
||||
"name": "league/uri",
|
||||
"version": "7.7.0",
|
||||
"version": "7.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/uri.git",
|
||||
"reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807"
|
||||
"reference": "4436c6ec8d458e4244448b069cc572d088230b76"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807",
|
||||
"reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807",
|
||||
"url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76",
|
||||
"reference": "4436c6ec8d458e4244448b069cc572d088230b76",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"league/uri-interfaces": "^7.7",
|
||||
"league/uri-interfaces": "^7.8",
|
||||
"php": "^8.1",
|
||||
"psr/http-factory": "^1"
|
||||
},
|
||||
@@ -3331,11 +3331,11 @@
|
||||
"ext-gmp": "to improve IPV4 host parsing",
|
||||
"ext-intl": "to handle IDN host with the best performance",
|
||||
"ext-uri": "to use the PHP native URI class",
|
||||
"jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain",
|
||||
"league/uri-components": "Needed to easily manipulate URI objects components",
|
||||
"league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP",
|
||||
"jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain",
|
||||
"league/uri-components": "to provide additional tools to manipulate URI objects components",
|
||||
"league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP",
|
||||
"php-64bit": "to improve IPV4 host parsing",
|
||||
"rowbot/url": "to handle WHATWG URL",
|
||||
"rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
|
||||
"symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
|
||||
},
|
||||
"type": "library",
|
||||
@@ -3390,7 +3390,7 @@
|
||||
"docs": "https://uri.thephpleague.com",
|
||||
"forum": "https://thephpleague.slack.com",
|
||||
"issues": "https://github.com/thephpleague/uri-src/issues",
|
||||
"source": "https://github.com/thephpleague/uri/tree/7.7.0"
|
||||
"source": "https://github.com/thephpleague/uri/tree/7.8.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3398,20 +3398,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-07T16:02:06+00:00"
|
||||
"time": "2026-01-14T17:24:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/uri-interfaces",
|
||||
"version": "7.7.0",
|
||||
"version": "7.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/uri-interfaces.git",
|
||||
"reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c"
|
||||
"reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c",
|
||||
"reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c",
|
||||
"url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4",
|
||||
"reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3424,7 +3424,7 @@
|
||||
"ext-gmp": "to improve IPV4 host parsing",
|
||||
"ext-intl": "to handle IDN host with the best performance",
|
||||
"php-64bit": "to improve IPV4 host parsing",
|
||||
"rowbot/url": "to handle WHATWG URL",
|
||||
"rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
|
||||
"symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
|
||||
},
|
||||
"type": "library",
|
||||
@@ -3474,7 +3474,7 @@
|
||||
"docs": "https://uri.thephpleague.com",
|
||||
"forum": "https://thephpleague.slack.com",
|
||||
"issues": "https://github.com/thephpleague/uri-src/issues",
|
||||
"source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0"
|
||||
"source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3482,7 +3482,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-07T16:03:21+00:00"
|
||||
"time": "2026-01-15T06:54:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mailersend/laravel-driver",
|
||||
@@ -10706,16 +10706,16 @@
|
||||
},
|
||||
{
|
||||
"name": "larastan/larastan",
|
||||
"version": "v3.8.1",
|
||||
"version": "v3.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/larastan/larastan.git",
|
||||
"reference": "ff3725291bc4c7e6032b5a54776e3e5104c86db9"
|
||||
"reference": "82c18890d0d5b012bc39a3432531e5b6cd1b4b3a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/larastan/larastan/zipball/ff3725291bc4c7e6032b5a54776e3e5104c86db9",
|
||||
"reference": "ff3725291bc4c7e6032b5a54776e3e5104c86db9",
|
||||
"url": "https://api.github.com/repos/larastan/larastan/zipball/82c18890d0d5b012bc39a3432531e5b6cd1b4b3a",
|
||||
"reference": "82c18890d0d5b012bc39a3432531e5b6cd1b4b3a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10784,7 +10784,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/larastan/larastan/issues",
|
||||
"source": "https://github.com/larastan/larastan/tree/v3.8.1"
|
||||
"source": "https://github.com/larastan/larastan/tree/v3.9.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -10792,7 +10792,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-11T16:37:35+00:00"
|
||||
"time": "2026-01-17T23:00:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel-json-api/testing",
|
||||
@@ -11784,16 +11784,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "12.5.4",
|
||||
"version": "12.5.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "4ba0e923f9d3fc655de22f9547c01d15a41fc93a"
|
||||
"reference": "ab8e4374264bc65523d1458d14bf80261577e01f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4ba0e923f9d3fc655de22f9547c01d15a41fc93a",
|
||||
"reference": "4ba0e923f9d3fc655de22f9547c01d15a41fc93a",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ab8e4374264bc65523d1458d14bf80261577e01f",
|
||||
"reference": "ab8e4374264bc65523d1458d14bf80261577e01f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11807,7 +11807,7 @@
|
||||
"phar-io/manifest": "^2.0.4",
|
||||
"phar-io/version": "^3.2.1",
|
||||
"php": ">=8.3",
|
||||
"phpunit/php-code-coverage": "^12.5.1",
|
||||
"phpunit/php-code-coverage": "^12.5.2",
|
||||
"phpunit/php-file-iterator": "^6.0.0",
|
||||
"phpunit/php-invoker": "^6.0.0",
|
||||
"phpunit/php-text-template": "^5.0.0",
|
||||
@@ -11861,7 +11861,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.4"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11885,7 +11885,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-15T06:05:34+00:00"
|
||||
"time": "2026-01-16T16:28:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "rector/rector",
|
||||
|
||||
@@ -78,8 +78,8 @@ return [
|
||||
'running_balance_column' => (bool)envNonEmpty('USE_RUNNING_BALANCE', true), // this is only the default value, is not used.
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => 'develop/2026-01-14',
|
||||
'build_time' => 1768366713,
|
||||
'version' => 'develop/2026-01-19',
|
||||
'build_time' => 1768805645,
|
||||
'api_version' => '2.1.0', // field is no longer used.
|
||||
'db_version' => 28, // field is no longer used.
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ null-type-hint = "null_pipe"
|
||||
|
||||
[linter]
|
||||
integrations = ["symfony", "laravel", "phpunit"]
|
||||
excludes = ["app/Providers/AppServiceProvider.php"] # Additionally excluded from linter only
|
||||
|
||||
[linter.rules]
|
||||
ambiguous-function-call = { enabled = false }
|
||||
|
||||
90
package-lock.json
generated
90
package-lock.json
generated
@@ -3232,9 +3232,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz",
|
||||
"integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==",
|
||||
"version": "25.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz",
|
||||
"integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3350,42 +3350,42 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.26",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz",
|
||||
"integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==",
|
||||
"version": "3.5.27",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz",
|
||||
"integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@vue/shared": "3.5.26",
|
||||
"@vue/shared": "3.5.27",
|
||||
"entities": "^7.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.26",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz",
|
||||
"integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==",
|
||||
"version": "3.5.27",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz",
|
||||
"integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.26",
|
||||
"@vue/shared": "3.5.26"
|
||||
"@vue/compiler-core": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.26",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz",
|
||||
"integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==",
|
||||
"version": "3.5.27",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz",
|
||||
"integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.5",
|
||||
"@vue/compiler-core": "3.5.26",
|
||||
"@vue/compiler-dom": "3.5.26",
|
||||
"@vue/compiler-ssr": "3.5.26",
|
||||
"@vue/shared": "3.5.26",
|
||||
"@vue/compiler-core": "3.5.27",
|
||||
"@vue/compiler-dom": "3.5.27",
|
||||
"@vue/compiler-ssr": "3.5.27",
|
||||
"@vue/shared": "3.5.27",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"postcss": "^8.5.6",
|
||||
@@ -3393,14 +3393,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.26",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz",
|
||||
"integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==",
|
||||
"version": "3.5.27",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz",
|
||||
"integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.26",
|
||||
"@vue/shared": "3.5.26"
|
||||
"@vue/compiler-dom": "3.5.27",
|
||||
"@vue/shared": "3.5.27"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/component-compiler-utils": {
|
||||
@@ -3482,9 +3482,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.26",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz",
|
||||
"integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==",
|
||||
"version": "3.5.27",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz",
|
||||
"integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -4131,9 +4131,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.9.14",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz",
|
||||
"integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==",
|
||||
"version": "2.9.15",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz",
|
||||
"integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -4271,9 +4271,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bootstrap5-autocomplete": {
|
||||
"version": "1.1.41",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap5-autocomplete/-/bootstrap5-autocomplete-1.1.41.tgz",
|
||||
"integrity": "sha512-GnjG9/oNOzDPdNumGeWyUhLct6q2y+HrYsznEbmNVhS51IEa6kls9txnogFZLxAXhH93F1RRt4BQMyTBMu5DDg==",
|
||||
"version": "1.1.42",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap5-autocomplete/-/bootstrap5-autocomplete-1.1.42.tgz",
|
||||
"integrity": "sha512-bmglwpqdKALLUVuxktgX+AmKCNy3aR7TqDLCxvOM/NXaeLxSY/q7uLMvxFaTyvoNRTXi5uW1Vzuxo2kNHCIChA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bootstrap5-tags": {
|
||||
@@ -4564,9 +4564,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001764",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz",
|
||||
"integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==",
|
||||
"version": "1.0.30001765",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz",
|
||||
"integrity": "sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -10974,9 +10974,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.44.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz",
|
||||
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
|
||||
"version": "5.46.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz",
|
||||
"integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
@@ -11766,9 +11766,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.0.tgz",
|
||||
"integrity": "sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==",
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
|
||||
"integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -12290,9 +12290,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.19",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
|
||||
"integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
|
||||
"version": "1.1.20",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
|
||||
"integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -121,8 +121,10 @@ export default {
|
||||
let srcType = this.source.type ? this.source.type.toLowerCase() : 'invalid';
|
||||
let tType = this.transactionType ? this.transactionType.toLowerCase() : 'invalid';
|
||||
let liabilities = ['loan', 'debt', 'mortgage'];
|
||||
let asset = ['asset account'];
|
||||
let sourceIsLiability = liabilities.indexOf(srcType) !== -1;
|
||||
let destIsLiability = liabilities.indexOf(destType) !== -1;
|
||||
let destIsAsset =asset.indexOf(destType) !== -1;
|
||||
|
||||
|
||||
// console.log(srcType + ' (source) is a liability: ' + sourceIsLiability);
|
||||
@@ -131,18 +133,15 @@ export default {
|
||||
if (tType === 'transfer' || destIsLiability || sourceIsLiability) {
|
||||
console.log('Source or dest is a liability.')
|
||||
console.log('Source is liability OR dest is liability, OR transfer. Lock list on currency of destination.');
|
||||
console.log(this.destination.type);
|
||||
// console.log('Length of currencies is ' + this.currencies.length);
|
||||
// console.log(this.currencies);
|
||||
this.liability = true;
|
||||
// lock dropdown list on currencyID of destination UNLESS dest is not liab
|
||||
for (const key in this.currencies) {
|
||||
if (this.currencies.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
|
||||
if (
|
||||
parseInt(this.currencies[key].id) === parseInt(this.destination.currency_id) || !destIsLiability
|
||||
) {
|
||||
console.log('Enable currency!!');
|
||||
console.log(this.currencies[key]);
|
||||
// console.log(this.destination);
|
||||
if (parseInt(this.currencies[key].id) === parseInt(this.destination.currency_id) || (!destIsLiability && 'transfer' !== tType)) {
|
||||
console.log('Enable currency: ' + this.currencies[key].attributes.code + '.');
|
||||
this.enabledCurrencies.push(this.currencies[key]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,11 +80,11 @@ export default {
|
||||
// add to temp list
|
||||
let currentPiggy = res.data[key];
|
||||
if (currentPiggy.objectGroup) {
|
||||
let groupOrder = currentPiggy.objectGroup.order;
|
||||
let groupOrder = currentPiggy.object_group_order;
|
||||
if (!tempList[groupOrder]) {
|
||||
tempList[groupOrder] = {
|
||||
group: {
|
||||
title: currentPiggy.objectGroup.title
|
||||
title: currentPiggy.object_group_title
|
||||
},
|
||||
piggies: [],
|
||||
};
|
||||
@@ -94,7 +94,7 @@ export default {
|
||||
id: currentPiggy.id
|
||||
});
|
||||
}
|
||||
if (!currentPiggy.objectGroup) {
|
||||
if (null === currentPiggy.object_group_id) {
|
||||
// add to empty one:
|
||||
tempList[0].piggies.push({name_with_balance: currentPiggy.name_with_balance, id: currentPiggy.id});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"firefly": {
|
||||
"administrations_page_title": "Financial administrations",
|
||||
"administrations_index_menu": "Financial administrations",
|
||||
"administrations_page_title": "Administrasi keuangan",
|
||||
"administrations_index_menu": "Administrasi keuangan",
|
||||
"expires_at": "Expires at",
|
||||
"temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its primary currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.",
|
||||
"temp_administrations_introduction": "Firefly III segera akan memiliki kemampuan untuk mengelola beberapa administrasi keuangan. Saat ini, Anda hanya memiliki satu. Anda dapat mengatur judul administrasi ini serta mata uang utamanya. Pengaturan ini menggantikan pengaturan sebelumnya di mana Anda menetapkan \u201cmata uang default\u201d. Sekarang, pengaturan ini terkait langsung dengan masing-masing administrasi keuangan dan dapat berbeda untuk setiap administrasi.",
|
||||
"administration_currency_form_help": "It may take a long time for the page to load if you change the primary currency because transaction may need to be converted to your (new) primary currency.",
|
||||
"administrations_page_edit_sub_title_js": "Edit financial administration \"{title}\"",
|
||||
"administrations_page_edit_sub_title_js": "Edit administrasi keuangan \u201c{title}\u201d",
|
||||
"table": "Meja",
|
||||
"welcome_back": "Apa yang sedang dimainkan?",
|
||||
"flash_error": "Kesalahan!",
|
||||
@@ -20,7 +20,7 @@
|
||||
"split": "Pisah",
|
||||
"single_split": "Pisah",
|
||||
"not_enough_currencies": "Not enough currencies",
|
||||
"not_enough_currencies_enabled": "If you have just one currency enabled, there is no need to add exchange rates.",
|
||||
"not_enough_currencies_enabled": "Jika Anda hanya mengaktifkan satu mata uang, tidak perlu menambahkan nilai tukar.",
|
||||
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID} (\"{title}\")<\/a> has been stored.",
|
||||
"webhook_stored_link": "<a href=\"webhooks\/show\/{ID}\">Webhook #{ID} (\"{title}\")<\/a> has been stored.",
|
||||
"webhook_updated_link": "<a href=\"webhooks\/show\/{ID}\">Webhook #{ID}<\/a> (\"{title}\") has been updated.",
|
||||
@@ -31,7 +31,7 @@
|
||||
"apply_rules_checkbox": "Apply rules",
|
||||
"fire_webhooks_checkbox": "Fire webhooks",
|
||||
"no_budget_pointer": "Anda tampaknya belum memiliki anggaran. Anda harus membuat beberapa di halaman-<a href=\"budgets\">anggaran<\/a>. Anggaran dapat membantu anda melacak pengeluaran.",
|
||||
"no_bill_pointer": "You seem to have no subscription yet. You should create some on the <a href=\"subscriptions\">subscription<\/a>-page. Subscriptions can help you keep track of expenses.",
|
||||
"no_bill_pointer": "Sepertinya Anda belum memiliki langganan. Buat beberapa di halaman <a href=\"subscriptions\">Langganan<\/a>. Langganan dapat membantu Anda melacak pengeluaran.",
|
||||
"source_account": "Akun sumber",
|
||||
"hidden_fields_preferences": "You can enable more transaction options in your <a href=\"preferences\">preferences<\/a>.",
|
||||
"destination_account": "Akun tujuan",
|
||||
@@ -50,7 +50,7 @@
|
||||
"category": "Kategori",
|
||||
"attachments": "Lampiran",
|
||||
"notes": "Notes",
|
||||
"external_url": "URL luar",
|
||||
"external_url": "URL eksternal",
|
||||
"update_transaction": "Update transaction",
|
||||
"after_update_create_another": "After updating, return here to continue editing.",
|
||||
"store_as_new": "Store as a new transaction instead of updating.",
|
||||
@@ -59,7 +59,7 @@
|
||||
"no_piggy_bank": "(tidak ada celengan)",
|
||||
"description": "Deskripsi",
|
||||
"split_transaction_title_help": "If you create a split transaction, there must be a global description for all splits of the transaction.",
|
||||
"destination_account_reconciliation": "You can't edit the destination account of a reconciliation transaction.",
|
||||
"destination_account_reconciliation": "Anda tidak dapat mengubah rekening tujuan pada transaksi rekonsiliasi.",
|
||||
"source_account_reconciliation": "Anda tidak dapat mengedit akun sumber dari transaksi rekonsiliasi.",
|
||||
"budget": "Anggaran",
|
||||
"bill": "Subscription",
|
||||
@@ -146,14 +146,14 @@
|
||||
"response": "Tanggapan",
|
||||
"visit_webhook_url": "Visit webhook URL",
|
||||
"reset_webhook_secret": "Reset webhook secret",
|
||||
"header_exchange_rates": "Exchange rates",
|
||||
"exchange_rates_intro": "Firefly III supports downloading and using exchange rates. Read more about this in <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">the documentation<\/a>.",
|
||||
"exchange_rates_from_to": "Between {from} and {to} (and the other way around)",
|
||||
"exchange_rates_intro_rates": "Firefly III uses the following exchange rates. The inverse is automatically calculated when it is not provided. If no exchange rate exists for the date of the transaction, Firefly III will go back in time to find one. If none are present, the rate \"1\" will be used.",
|
||||
"header_exchange_rates_rates": "Exchange rates",
|
||||
"header_exchange_rates_table": "Table with exchange rates",
|
||||
"header_exchange_rates": "Nilai tukar",
|
||||
"exchange_rates_intro": "Firefly III mendukung pengunduhan dan penggunaan nilai tukar. Baca selengkapnya di <a href=\"https:\/\/docs.firefly-iii.org\/explanation\/financial-concepts\/exchange-rates\/\">dokumentasi<\/a>.",
|
||||
"exchange_rates_from_to": "Antara {from} dan {to} (dan sebaliknya)",
|
||||
"exchange_rates_intro_rates": "Firefly III menggunakan nilai tukar berikut. Nilai tukar terbalik akan dihitung secara otomatis jika tidak tersedia. Jika tidak ada nilai tukar untuk tanggal transaksi, Firefly III akan mencari nilai tukar sebelumnya. Jika tetap tidak ditemukan, nilai tukar \u201c1\u201d akan digunakan.",
|
||||
"header_exchange_rates_rates": "Nilai tukar",
|
||||
"header_exchange_rates_table": "Tabel dengan nilai tukar",
|
||||
"help_rate_form": "On this day, how many {to} will you get for one {from}?",
|
||||
"add_new_rate": "Add a new exchange rate",
|
||||
"add_new_rate": "Tambahkan nilai tukar baru",
|
||||
"save_new_rate": "Save new rate"
|
||||
},
|
||||
"form": {
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
"list": {
|
||||
"title": "Titlu",
|
||||
"active": "Este activ?",
|
||||
"primary_currency": "Primary currency",
|
||||
"primary_currency": "Moned\u0103 principal\u0103",
|
||||
"trigger": "Declan\u0219ator",
|
||||
"response": "R\u0103spuns",
|
||||
"delivery": "Livrare",
|
||||
|
||||
@@ -314,7 +314,12 @@
|
||||
{% if account.id == transaction.source_account_id %}
|
||||
<span title="Transfer, source">{{ formatAmountBySymbol(transaction.source_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}</span>
|
||||
{% else %}
|
||||
<span title="Transfer, dest">{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}</span>
|
||||
{% if null == transaction.foreign_currency_id %}
|
||||
<span title="Transfer, dest, normal currency">{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}</span>
|
||||
{% endif %}
|
||||
{% if null != transaction.foreign_currency_id %}
|
||||
<span title="Transfer, dest, foreign currency">{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user