Compare commits

..

35 Commits

Author SHA1 Message Date
github-actions[bot]
71d39707d9 Merge pull request #10738 from firefly-iii/release-1754800979
🤖 Automatically merge the PR into the develop branch.
2025-08-10 06:43:10 +02:00
JC5
9ccb8ae692 🤖 Auto commit for release 'develop' on 2025-08-10 2025-08-10 06:42:59 +02:00
James Cole
8cd50bb5bd Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-08-10 06:38:34 +02:00
James Cole
ae9e1278e5 Expand some timers, fix reports. 2025-08-10 06:38:23 +02:00
github-actions[bot]
58c03797b2 Merge pull request #10737 from firefly-iii/release-1754766186
🤖 Automatically merge the PR into the develop branch.
2025-08-09 21:03:19 +02:00
JC5
7db38b4c6c 🤖 Auto commit for release 'develop' on 2025-08-09 2025-08-09 21:03:06 +02:00
James Cole
da6b447e64 Add some extra debug info. 2025-08-09 20:42:49 +02:00
github-actions[bot]
c19ac2b0f3 Merge pull request #10736 from firefly-iii/release-1754764141
🤖 Automatically merge the PR into the develop branch.
2025-08-09 20:29:09 +02:00
JC5
d5ca2171b3 🤖 Auto commit for release 'develop' on 2025-08-09 2025-08-09 20:29:01 +02:00
James Cole
20972cb29f Merge errors. 2025-08-09 20:24:45 +02:00
James Cole
7b714d0866 Add some time logs. 2025-08-09 20:24:15 +02:00
github-actions[bot]
240ae8fa57 Merge pull request #10735 from firefly-iii/release-1754763336
🤖 Automatically merge the PR into the develop branch.
2025-08-09 20:16:11 +02:00
JC5
5a2f6b2652 🤖 Auto commit for release 'develop' on 2025-08-09 2025-08-09 20:15:36 +02:00
James Cole
4196ce31f0 Fix account thing overview. 2025-08-09 20:10:40 +02:00
James Cole
be8ca5db50 Update changelog. 2025-08-09 19:58:59 +02:00
github-actions[bot]
30a417ea3c Merge pull request #10734 from firefly-iii/release-1754750646
🤖 Automatically merge the PR into the develop branch.
2025-08-09 16:44:16 +02:00
JC5
695ed940e0 🤖 Auto commit for release 'develop' on 2025-08-09 2025-08-09 16:44:06 +02:00
James Cole
1353554cf8 Remove expensive call from loop. 2025-08-09 16:38:32 +02:00
James Cole
e1ba2732af Remove non-optimized method. 2025-08-09 16:34:25 +02:00
James Cole
42b57c0e0e Migrate to optimized method. 2025-08-09 16:31:11 +02:00
James Cole
a6072753b2 Remove PR 2025-08-09 16:01:44 +02:00
github-actions[bot]
e92c224c39 Merge pull request #10733 from firefly-iii/release-1754748022
🤖 Automatically merge the PR into the develop branch.
2025-08-09 16:00:30 +02:00
JC5
a3ed7ec8f6 🤖 Auto commit for release 'develop' on 2025-08-09 2025-08-09 16:00:22 +02:00
James Cole
17a2f99dff Order and palce in changelog. 2025-08-09 15:55:29 +02:00
James Cole
c14971543c Update changelog. 2025-08-09 15:43:36 +02:00
James Cole
55f899608d Add multi currency to piggy overview. 2025-08-09 15:33:03 +02:00
github-actions[bot]
83be63f27e Merge pull request #10731 from firefly-iii/release-1754731244
🤖 Automatically merge the PR into the develop branch.
2025-08-09 11:20:53 +02:00
JC5
ed48d190e5 🤖 Auto commit for release 'develop' on 2025-08-09 2025-08-09 11:20:44 +02:00
James Cole
3c3b6615e6 Make message singular / plural 2025-08-09 11:16:46 +02:00
James Cole
e71e5a877b Fix count for overdue bills. 2025-08-09 11:13:03 +02:00
James Cole
b2a65dc660 Small change in text. 2025-08-09 08:48:25 +02:00
James Cole
d66dccd076 Fix bad slack URL 2025-08-09 08:47:22 +02:00
github-actions[bot]
c1128b28f2 Merge pull request #10730 from firefly-iii/release-1754721510
🤖 Automatically merge the PR into the develop branch.
2025-08-09 08:38:39 +02:00
JC5
da8e78c28d 🤖 Auto commit for release 'develop' on 2025-08-09 2025-08-09 08:38:30 +02:00
James Cole
f50aa6b0ce Fix spam whoopsie. 2025-08-09 08:34:18 +02:00
27 changed files with 374 additions and 244 deletions

View File

@@ -31,6 +31,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Debug\Timer;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\User;
@@ -79,17 +80,20 @@ class AccountController extends Controller
*/
public function accounts(AutocompleteRequest $request): JsonResponse
{
$data = $request->getData();
$types = $data['types'];
$query = $data['query'];
$date = $data['date'] ?? today(config('app.timezone'));
$return = [];
Timer::start(sprintf('AC accounts "%s"', $query));
$result = $this->repository->searchAccount((string) $query, $types, $this->parameters->get('limit'));
$data = $request->getData();
$types = $data['types'];
$query = $data['query'];
$date = $data['date'] ?? today(config('app.timezone'));
$return = [];
$timer = Timer::getInstance();
$timer->start(sprintf('AC accounts "%s"', $query));
$result = $this->repository->searchAccount((string) $query, $types, $this->parameters->get('limit'));
// set date to subday + end-of-day for account balance. so it is at $date 23:59:59
$date->endOfDay();
$allBalances = Steam::accountsBalancesOptimized($result, $date, $this->primaryCurrency, $this->convertToPrimary);
/** @var Account $account */
foreach ($result as $account) {
$nameWithBalance = $account->name;
@@ -98,15 +102,11 @@ class AccountController extends Controller
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
// this one is correct.
Log::debug(sprintf('accounts: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
$balance = Steam::finalAccountBalance($account, $date);
$balance = $allBalances[$account->id] ?? [];
$key = $this->convertToPrimary && $currency->id !== $this->primaryCurrency->id ? 'pc_balance' : 'balance';
$useCurrency = $this->convertToPrimary && $currency->id !== $this->primaryCurrency->id ? $this->primaryCurrency : $currency;
$amount = $balance[$key] ?? '0';
$nameWithBalance = sprintf(
'%s (%s)',
$account->name,
app('amount')->formatAnything($useCurrency, $amount, false)
);
$nameWithBalance = sprintf('%s (%s)', $account->name, Amount::formatAnything($useCurrency, $amount, false));
}
$return[] = [
@@ -138,7 +138,7 @@ class AccountController extends Controller
return $posA - $posB;
}
);
Timer::stop(sprintf('AC accounts "%s"', $query));
$timer->stop(sprintf('AC accounts "%s"', $query));
return response()->api($return);
}

View File

@@ -5,13 +5,13 @@ declare(strict_types=1);
namespace FireflyIII\Events\Model\Bill;
use FireflyIII\Events\Event;
use FireflyIII\Models\Bill;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
class WarnUserAboutOverdueSubscription extends Event
class WarnUserAboutOverdueSubscriptions extends Event
{
use SerializesModels;
public function __construct(public Bill $bill, public array $dates) {}
public function __construct(public User $user, public array $overdue) {}
}

View File

@@ -26,9 +26,10 @@ namespace FireflyIII\Handlers\Events;
use Exception;
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscription;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
use FireflyIII\Models\Bill;
use FireflyIII\Notifications\User\BillReminder;
use FireflyIII\Notifications\User\SubscriptionOverdueReminder;
use FireflyIII\Notifications\User\SubscriptionsOverdueReminder;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
@@ -38,47 +39,69 @@ use Illuminate\Support\Facades\Notification;
*/
class BillEventHandler
{
public function warnAboutOverdueSubscription(WarnUserAboutOverdueSubscription $event): void
public function warnAboutOverdueSubscriptions(WarnUserAboutOverdueSubscriptions $event): void
{
$bill = $event->bill;
$dates = $event->dates;
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)));
foreach ($overdue as $item) {
/** @var Bill $bill */
$bill = $item['bill'];
$key = sprintf('bill_overdue_%s_%s', $bill->id, substr(hash('sha256', json_encode($item['dates']['pay_dates'], JSON_THROW_ON_ERROR)), 0, 10));
$pref = Preferences::getForUser($bill->user, $key, false);
if (true === $pref->data) {
Log::debug(sprintf('User #%d has already been warned about overdue subscription #%d.', $bill->user->id, $bill->id));
$key = sprintf('bill_overdue_%s_%s', $bill->id, substr(hash('sha256', json_encode($dates['pay_dates'], JSON_THROW_ON_ERROR)), 0, 10));
$pref = Preferences::getForUser($bill->user, $key, false);
if (true === $pref->data) {
Log::debug(sprintf('User %s has already been warned about overdue subscription %s.', $bill->user->id, $bill->id));
return;
continue;
}
$toBeWarned[] = $item;
}
unset($bill);
Log::debug(sprintf('Now %d bills to warn about.', count($toBeWarned)));
/** @var bool $sendNotification */
$sendNotification = Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data;
if (true === $sendNotification) {
Log::debug('Will warning about overdue subscription.');
Preferences::setForUser($bill->user, $key, true);
try {
Notification::send($bill->user, new SubscriptionOverdueReminder($bill, $dates));
} 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());
}
$sendNotification = Preferences::getForUser($user, 'notification_bill_reminder', true)->data;
if (false === $sendNotification) {
Log::debug('User has disabled bill reminders.');
return;
}
Log::debug('User has disabled bill reminders.');
Log::debug(sprintf('Will warn about %d overdue subscription(s).', count($toBeWarned)));
if (0 === count($toBeWarned)) {
Log::debug('No overdue subscriptions to warn about.');
return;
}
foreach ($toBeWarned as $item) {
/** @var Bill $bill */
$bill = $item['bill'];
$key = sprintf('bill_overdue_%s_%s', $bill->id, substr(hash('sha256', json_encode($item['dates']['pay_dates'], JSON_THROW_ON_ERROR)), 0, 10));
Preferences::setForUser($bill->user, $key, true);
}
Log::warning('should hit this ONCE');
try {
Notification::send($user, new SubscriptionsOverdueReminder($toBeWarned));
} 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());
}
}
public function warnAboutBill(WarnUserAboutBill $event): void

View File

@@ -77,8 +77,8 @@ class NetWorth implements NetWorthInterface
Log::debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d H:i:s')));
$primary = Amount::getPrimaryCurrency();
$netWorth = [];
Log::debug(sprintf('NetWorth: finalAccountsBalance("%s")', $date->format('Y-m-d H:i:s')));
$balances = Steam::finalAccountsBalance($accounts, $date);
Log::debug(sprintf('NetWorth: accountsBalancesOptimized("%s")', $date->format('Y-m-d H:i:s')));
$balances = Steam::accountsBalancesOptimized($accounts, $date, null, $convertToPrimary);
/** @var Account $account */
foreach ($accounts as $account) {
@@ -143,8 +143,8 @@ class NetWorth implements NetWorthInterface
*/
$accounts = $this->getAccounts();
$return = [];
Log::debug(sprintf('SumNetWorth: finalAccountsBalance("%s")', $date->format('Y-m-d H:i:s')));
$balances = Steam::finalAccountsBalance($accounts, $date);
Log::debug(sprintf('SumNetWorth: accountsBalancesOptimized("%s")', $date->format('Y-m-d H:i:s')));
$balances = Steam::accountsBalancesOptimized($accounts, $date);
foreach ($accounts as $account) {
$currency = $this->accountRepository->getAccountCurrency($account);
$balance = $balances[$account->id]['balance'] ?? '0';

View File

@@ -93,10 +93,10 @@ class IndexController extends Controller
$start->subSecond();
$ids = $accounts->pluck('id')->toArray();
Log::debug(sprintf('inactive start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
Log::debug(sprintf('inactive end: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
$startBalances = Steam::finalAccountsBalance($accounts, $start);
$endBalances = Steam::finalAccountsBalance($accounts, $end);
Log::debug(sprintf('inactive start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
Log::debug(sprintf('inactive end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
$activities = Steam::getLastActivities($ids);
@@ -170,10 +170,10 @@ class IndexController extends Controller
$start->subSecond();
$ids = $accounts->pluck('id')->toArray();
Log::debug(sprintf('index start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
Log::debug(sprintf('index end: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
$startBalances = Steam::finalAccountsBalance($accounts, $start);
$endBalances = Steam::finalAccountsBalance($accounts, $end);
Log::debug(sprintf('index start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
Log::debug(sprintf('index end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
$activities = Steam::getLastActivities($ids);

View File

@@ -40,6 +40,7 @@ use Illuminate\Routing\Redirector;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class ShowController
@@ -81,7 +82,9 @@ class ShowController extends Controller
* */
public function show(Request $request, Account $account, ?Carbon $start = null, ?Carbon $end = null)
{
if (0 === $account->id) {
throw new NotFoundHttpException();
}
$objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type));
if (!$this->isEditableAccount($account)) {
@@ -116,18 +119,19 @@ class ShowController extends Controller
$firstTransaction = $this->repository->oldestJournalDate($account) ?? $start;
Log::debug('Start period overview');
Timer::start('period-overview');
$timer = Timer::getInstance();
$timer->start('period-overview');
$periods = $this->getAccountPeriodOverview($account, $firstTransaction, $end);
Log::debug('End period overview');
Timer::stop('period-overview');
$timer->stop('period-overview');
// if layout = v2, overrule the page title.
if ('v1' !== config('view.layout')) {
$subTitle = (string) trans('firefly.all_journals_for_account', ['name' => $account->name]);
}
Log::debug('Collect transactions');
Timer::start('collection');
$timer->start('collection');
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
@@ -146,7 +150,7 @@ class ShowController extends Controller
Log::debug('End collect transactions');
Timer::stop('collection');
$timer->stop('collection');
// enrich data in arrays.

View File

@@ -114,10 +114,10 @@ class AccountController extends Controller
$accountNames = $this->extractNames($accounts);
// grab all balances
Log::debug(sprintf('expenseAccounts: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
Log::debug(sprintf('expenseAccounts: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
$startBalances = Steam::finalAccountsBalance($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
$endBalances = Steam::finalAccountsBalance($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
// loop the accounts, then check for balance and currency info.
foreach ($accounts as $account) {
@@ -654,10 +654,10 @@ class AccountController extends Controller
$accountNames = $this->extractNames($accounts);
// grab all balances
Log::debug(sprintf('revAccounts: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
Log::debug(sprintf('revAccounts: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
$startBalances = Steam::finalAccountsBalance($accounts, $start);
$endBalances = Steam::finalAccountsBalance($accounts, $end);
Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s')));
Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary);
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary);
// loop the accounts, then check for balance and currency info.

View File

@@ -188,16 +188,7 @@ class ReportController extends Controller
$start->endOfDay(); // end of day so the final balance is at the end of that day.
$end->endOfDay();
app('view')->share(
'subTitle',
trans(
'firefly.report_default',
[
'start' => $start->isoFormat($this->monthAndDayFormat),
'end' => $end->isoFormat($this->monthAndDayFormat),
]
)
);
app('view')->share('subTitle', trans('firefly.report_default', ['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]));
$generator = ReportGeneratorFactory::reportGenerator('Standard', $start, $end);
$generator->setAccounts($accounts);
@@ -222,16 +213,7 @@ class ReportController extends Controller
$start->endOfDay(); // end of day so the final balance is at the end of that day.
$end->endOfDay();
app('view')->share(
'subTitle',
trans(
'firefly.report_double',
[
'start' => $start->isoFormat($this->monthAndDayFormat),
'end' => $end->isoFormat($this->monthAndDayFormat),
]
)
);
app('view')->share('subTitle', trans('firefly.report_double', ['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]));
$generator = ReportGeneratorFactory::reportGenerator('Account', $start, $end);
$generator->setAccounts($accounts);

View File

@@ -26,10 +26,11 @@ namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscription;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
use FireflyIII\Models\Bill;
use FireflyIII\Support\Facades\Navigation;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
@@ -76,28 +77,30 @@ class WarnAboutBills implements ShouldQueue
public function handle(): void
{
Log::debug(sprintf('Now at start of WarnAboutBills() job for %s.', $this->date->format('D d M Y')));
$bills = Bill::all();
foreach (User::all() as $user) {
$bills = $user->bills()->where('active', true)->get();
$overdue = [];
/** @var Bill $bill */
foreach ($bills as $bill) {
Log::debug(sprintf('Now checking bill #%d ("%s")', $bill->id, $bill->name));
$dates = $this->getDates($bill);
if ($this->needsOverdueAlert($dates)) {
$this->sendOverdueAlert($bill, $dates);
}
if ($this->hasDateFields($bill)) {
if ($this->needsWarning($bill, 'end_date')) {
$this->sendWarning($bill, 'end_date');
/** @var Bill $bill */
foreach ($bills as $bill) {
Log::debug(sprintf('Now checking bill #%d ("%s")', $bill->id, $bill->name));
$dates = $this->getDates($bill);
if ($this->needsOverdueAlert($dates)) {
$overdue[] = ['bill' => $bill, 'dates' => $dates];
}
if ($this->needsWarning($bill, 'extension_date')) {
$this->sendWarning($bill, 'extension_date');
if ($this->hasDateFields($bill)) {
if ($this->needsWarning($bill, 'end_date')) {
$this->sendWarning($bill, 'end_date');
}
if ($this->needsWarning($bill, 'extension_date')) {
$this->sendWarning($bill, 'extension_date');
}
}
}
$this->sendOverdueAlerts($user, $overdue);
}
Log::debug('Done with handle()');
// clear cache:
app('preferences')->mark();
}
private function hasDateFields(Bill $bill): bool
@@ -195,9 +198,11 @@ class WarnAboutBills implements ShouldQueue
return true;
}
private function sendOverdueAlert(Bill $bill, array $dates): void
private function sendOverdueAlerts(User $user, array $overdue): void
{
Log::debug('Will now send warning about overdue bill.');
event(new WarnUserAboutOverdueSubscription($bill, $dates));
if (count($overdue) > 0) {
Log::debug(sprintf('Will now send warning about overdue bill for user #%d.', $user->id));
event(new WarnUserAboutOverdueSubscriptions($user, $overdue));
}
}
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\User;
use Carbon\Carbon;
use FireflyIII\Models\Bill;
use FireflyIII\Notifications\ReturnsAvailableChannels;
use FireflyIII\Notifications\ReturnsSettings;
use FireflyIII\User;
@@ -15,11 +14,11 @@ use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\Pushover\PushoverMessage;
class SubscriptionOverdueReminder extends Notification
class SubscriptionsOverdueReminder extends Notification
{
use Queueable;
public function __construct(private Bill $bill, private array $dates) {}
public function __construct(private array $overdue) {}
/**
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
@@ -35,23 +34,36 @@ class SubscriptionOverdueReminder extends Notification
*/
public function toMail(User $notifiable): MailMessage
{
// format the dates in a human-readable way
$this->dates['pay_dates'] = array_map(
static function (string $date): string {
return new Carbon($date)->isoFormat((string) trans('config.month_and_day_moment_js'));
},
$this->dates['pay_dates']
);
// format the data
$info = [];
$count = 0;
foreach ($this->overdue as $item) {
$current = [
'bill' => $item['bill'],
];
$current['pay_dates'] = array_map(
static function (string $date): string {
return new Carbon($date)->isoFormat((string)trans('config.month_and_day_moment_js'));
},
$item['dates']['pay_dates']
);
$info[] = $current;
++$count;
}
return new MailMessage()
->markdown('emails.subscription-overdue-warning', ['bill' => $this->bill, 'dates' => $this->dates])
->markdown('emails.subscriptions-overdue-warning', ['info' => $info, 'count' => $count])
->subject($this->getSubject())
;
}
private function getSubject(): string
{
return (string) trans('email.subscription_overdue_subject', ['name' => $this->bill->name]);
if (count($this->overdue) > 1) {
return (string)trans('email.subscriptions_overdue_subject_multi', ['count' => count($this->overdue)]);
}
return (string)trans('email.subscriptions_overdue_subject_single');
}
public function toNtfy(User $notifiable): Message
@@ -60,7 +72,7 @@ class SubscriptionOverdueReminder extends Notification
$message = new Message();
$message->topic($settings['ntfy_topic']);
$message->title($this->getSubject());
$message->body((string) trans('email.bill_warning_please_action'));
$message->body((string)trans('email.bill_warning_please_action'));
return $message;
}
@@ -70,7 +82,7 @@ class SubscriptionOverdueReminder extends Notification
*/
public function toPushover(User $notifiable): PushoverMessage
{
return PushoverMessage::create((string) trans('email.bill_warning_please_action'))
return PushoverMessage::create((string)trans('email.bill_warning_please_action'))
->title($this->getSubject())
;
}
@@ -80,13 +92,12 @@ class SubscriptionOverdueReminder extends Notification
*/
public function toSlack(User $notifiable): SlackMessage
{
$bill = $this->bill;
$url = route('bills.show', [$bill->id]);
$url = route('bills.index');
return new SlackMessage()
->warning()
->attachment(static function ($attachment) use ($bill, $url): void {
$attachment->title((string) trans('firefly.visit_bill', ['name' => $bill->name]), $url);
->attachment(static function ($attachment) use ($url): void {
$attachment->title((string)trans('firefly.visit_bills'), $url);
})
->content($this->getSubject())
;

View File

@@ -28,7 +28,7 @@ use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\DestroyedTransactionGroup;
use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscription;
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
use FireflyIII\Events\Model\BudgetLimit\Created;
use FireflyIII\Events\Model\BudgetLimit\Deleted;
use FireflyIII\Events\Model\BudgetLimit\Updated;
@@ -115,153 +115,153 @@ 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',
],
// 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 => [
WarnUserAboutBill::class => [
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutBill',
],
WarnUserAboutOverdueSubscription::class => [
'FireflyIII\Handlers\Events\BillEventHandler@warnAboutOverdueSubscription',
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 => [
ChangedAmount::class => [
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changePiggyAmount',
],
ChangedName::class => [
ChangedName::class => [
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName',
],
// budget related events: CRUD budget limit
Created::class => [
Created::class => [
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@created',
],
Updated::class => [
Updated::class => [
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@updated',
],
Deleted::class => [
Deleted::class => [
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@deleted',
],
// rule actions
RuleActionFailedOnArray::class => [
RuleActionFailedOnArray::class => [
'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnArray',
],
RuleActionFailedOnObject::class => [
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',
],
];

View File

@@ -566,7 +566,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
'transaction_types.type',
'transaction_journals.transaction_currency_id',
'transactions.amount',
'transactions.native_amount',
'transactions.native_amount as pc_amount',
'transactions.foreign_amount',
])
->toArray()

View File

@@ -50,10 +50,10 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
$yesterday = clone $start;
$yesterday->subDay()->endOfDay(); // exactly up until $start but NOT including.
$end->endOfDay(); // needs to be end of day to be correct.
Log::debug(sprintf('getAccountReport: finalAccountsBalance("%s")', $yesterday->format('Y-m-d H:i:s')));
Log::debug(sprintf('getAccountReport: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
$startSet = Steam::finalAccountsBalance($accounts, $yesterday);
$endSet = Steam::finalAccountsBalance($accounts, $end);
Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $yesterday->format('Y-m-d H:i:s')));
Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s')));
$startSet = Steam::accountsBalancesOptimized($accounts, $yesterday);
$endSet = Steam::accountsBalancesOptimized($accounts, $end);
Log::debug('Start of accountreport');
/** @var AccountRepositoryInterface $repository */

View File

@@ -28,19 +28,34 @@ use Illuminate\Support\Facades\Log;
class Timer
{
private static array $times = [];
private array $times = [];
private static ?Timer $instance = null;
public static function start(string $title): void
private function __construct()
{
self::$times[$title] = microtime(true);
// Private constructor to prevent direct instantiation.
}
public static function stop(string $title): void
public static function getInstance(): self
{
$start = self::$times[$title] ?? 0;
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function start(string $title): void
{
$this->times[$title] = microtime(true);
}
public function stop(string $title): void
{
$start = $this->times[$title] ?? 0;
$end = microtime(true);
$diff = $end - $start;
unset(self::$times[$title]);
unset($this->times[$title]);
Log::debug(sprintf('Timer "%s" took %f seconds', $title, $diff));
}
}

View File

@@ -80,7 +80,8 @@ trait PeriodOverview
protected function getAccountPeriodOverview(Account $account, Carbon $start, Carbon $end): array
{
Log::debug('Now in getAccountPeriodOverview()');
Timer::start('account-period-total');
$timer = Timer::getInstance();
$timer->start('account-period-total');
$this->accountRepository = app(AccountRepositoryInterface::class);
$range = Navigation::getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
@@ -92,23 +93,24 @@ trait PeriodOverview
$cache->addProperty('account-show-period-entries');
$cache->addProperty($account->id);
if ($cache->has()) {
// return $cache->get();
Log::debug('Return CACHED in getAccountPeriodOverview()');
return $cache->get();
}
/** @var array $dates */
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
$spent = [];
$earned = [];
$transferredAway = [];
$transferredIn = [];
// run a custom query because doing this with the collector is MEGA slow.
$timer->start('account-period-collect');
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
$timer->stop('account-period-collect');
// loop dates
Log::debug(sprintf('Count of loops: %d', count($dates)));
$loops = 0;
// stop after 10 loops for memory reasons.
$timer->start('account-period-loop');
foreach ($dates as $currentDate) {
$title = Navigation::periodShow($currentDate['start'], $currentDate['period']);
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
@@ -127,8 +129,9 @@ trait PeriodOverview
];
++$loops;
}
$timer->stop('account-period-loop');
$cache->store($entries);
Timer::stop('account-period-total');
$timer->stop('account-period-total');
Log::debug('End of getAccountPeriodOverview()');
return $entries;
@@ -168,7 +171,7 @@ trait PeriodOverview
* @var array $item
*/
foreach ($transactions as $index => $item) {
$date = Carbon::parse($item['date']);
$date = Carbon::parse($item['date']);
if ($date >= $start && $date <= $end) {
if ('away' === $direction && -1 === bccomp((string)$item['amount'], '0')) {
$result[] = $item;
@@ -180,8 +183,8 @@ trait PeriodOverview
continue;
}
$filtered[] = $item;
}
$filtered[] = $item;
}
return [$filtered, $result];

View File

@@ -321,6 +321,7 @@ class Steam
public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
{
Log::debug(sprintf('accountsBalancesOptimized: Called with date/time "%s"', $date->toIso8601String()));
$result = [];
$convertToPrimary ??= Amount::convertToPrimary();
$primary ??= Amount::getPrimaryCurrency();
@@ -520,17 +521,6 @@ class Steam
return $total;
}
public function finalAccountsBalance(Collection $accounts, Carbon $date): array
{
Log::debug(sprintf('finalAccountsBalance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
$balances = [];
foreach ($accounts as $account) {
$balances[$account->id] = $this->finalAccountBalance($account, $date);
}
return $balances;
}
/**
* @throws FireflyException
*/

View File

@@ -3,6 +3,53 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 6.3.0 - 2025-08-xx
> ⚠️ Firefly III v6.3.0 introduces a lot of API changes that deal with multi-currency support. Make sure your beloved apps are updated to support this.
### Added
- [Issue 6836](https://github.com/firefly-iii/firefly-iii/issues/6836) (Send email about coming/past-due bills) reported by @elgatho
- [Issue 9640](https://github.com/firefly-iii/firefly-iii/issues/9640) (UI Improvements for Rules) reported by @siriuspal
- [Issue 9650](https://github.com/firefly-iii/firefly-iii/issues/9650) (Extra line in bills overview) reported by @poudenes
- Add Arabic as language, translations follow.
### Changed
- [Issue 10071](https://github.com/firefly-iii/firefly-iii/issues/10071) (Allow toggling password field to text) reported by @ragul-engg
- Renamed all instances of "default" and "native" currency to "primary" currency. This influences translations and API endpoints. The database is not changed because that's difficult to do reliably.
### Removed
- Any API-field called `default_*` or `native_*`. Use `primary_*` instead.
- All v2 endpoints.
### Fixed
- [Issue 9849](https://github.com/firefly-iii/firefly-iii/issues/9849) ("Display native amounts" not taken into account in report's pie charts) reported by @polter-rnd
- [Issue 10565](https://github.com/firefly-iii/firefly-iii/issues/10565) (Unable to delete reconciliation transaction) reported by @berta24
- [Issue 10600](https://github.com/firefly-iii/firefly-iii/issues/10600) (Show attachmen iccon when listing tranactions) reported by @JcMinarro
- [Discussion 10618](https://github.com/orgs/firefly-iii/discussions/10618) (Starting balance includes transactions that occur at 00:00 on the 1st of month) started by @jteez
- [Issue 10646](https://github.com/firefly-iii/firefly-iii/issues/10646) (Webhooks fire even if disabled) reported by @lvu
- [Issue 10656](https://github.com/firefly-iii/firefly-iii/issues/10656) (spent info "per day" shows the period total) reported by @frank-bg
- [Issue 10678](https://github.com/firefly-iii/firefly-iii/issues/10678) (Transactions from asset to liability account do not appear on category reports.) reported by @slackspace-io
- [Issue 10687](https://github.com/firefly-iii/firefly-iii/issues/10687) (Creating new Piggy Bank via API fails (Unexpected empty currency)) reported by @Madnex
- [Issue 10700](https://github.com/firefly-iii/firefly-iii/issues/10700) (Setting financial year date is inconsistent due to timezone calculations) reported by @AgeManning
- [Issue 10702](https://github.com/firefly-iii/firefly-iii/issues/10702) (Wrong order of months in category report) reported by @kapuett
- [Issue 10703](https://github.com/firefly-iii/firefly-iii/issues/10703) (Fire more than 5 webhooks in batch or through the cron job, and document it.) reported by @JC5
- [Issue 10704](https://github.com/firefly-iii/firefly-iii/issues/10704) (Some triggers with rule automation seems to have an issue) reported by @Alienlog
- [Issue 10706](https://github.com/firefly-iii/firefly-iii/issues/10706) (Add KRW in Default Currency List) reported by @readingsnail
- [Issue 10708](https://github.com/firefly-iii/firefly-iii/issues/10708) (Incomplete display of a rule when a trigger negates "description caontains") reported by @dethegeek
- [Issue 10709](https://github.com/firefly-iii/firefly-iii/issues/10709) (has_any_external_id search parameter invalid) reported by @Alienlog
- Tag overview will no longer search for tags dated < 1970.
### API
- All remaining API v2 endpoints are deprecated and removed in favour of the API v1 endpoints.
- All API read endpoints now support multi-currency. Fields such as the balance and amount fields will also be available as `pc_*`-fields. Objects with currency information also come with new `primary_currency_*` fields.
- All API read endpoints are DB optimized and should be faster.
- All documentation should be in sync again.
- [More info in the docs](https://docs.firefly-iii.org/references/firefly-iii/api/).
## 6.2.21 - 2025-07-18
### Added

40
composer.lock generated
View File

@@ -11618,16 +11618,16 @@
},
{
"name": "phpunit/phpunit",
"version": "12.3.0",
"version": "12.3.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "264da860d6fe0d00582355a6ecbbf7ae57b44895"
"reference": "5fd1b6e8ab560e0c62600591d438d22a8d978d68"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/264da860d6fe0d00582355a6ecbbf7ae57b44895",
"reference": "264da860d6fe0d00582355a6ecbbf7ae57b44895",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5fd1b6e8ab560e0c62600591d438d22a8d978d68",
"reference": "5fd1b6e8ab560e0c62600591d438d22a8d978d68",
"shasum": ""
},
"require": {
@@ -11637,7 +11637,7 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.13.3",
"myclabs/deep-copy": "^1.13.4",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
@@ -11653,7 +11653,7 @@
"sebastian/exporter": "^7.0.0",
"sebastian/global-state": "^8.0.0",
"sebastian/object-enumerator": "^7.0.0",
"sebastian/type": "^6.0.2",
"sebastian/type": "^6.0.3",
"sebastian/version": "^6.0.0",
"staabm/side-effects-detector": "^1.0.5"
},
@@ -11695,7 +11695,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.3.0"
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.1"
},
"funding": [
{
@@ -11719,7 +11719,7 @@
"type": "tidelift"
}
],
"time": "2025-08-01T05:14:47+00:00"
"time": "2025-08-09T07:12:41+00:00"
},
{
"name": "rector/rector",
@@ -12509,16 +12509,16 @@
},
{
"name": "sebastian/type",
"version": "6.0.2",
"version": "6.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "1d7cd6e514384c36d7a390347f57c385d4be6069"
"reference": "e549163b9760b8f71f191651d22acf32d56d6d4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/1d7cd6e514384c36d7a390347f57c385d4be6069",
"reference": "1d7cd6e514384c36d7a390347f57c385d4be6069",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d",
"reference": "e549163b9760b8f71f191651d22acf32d56d6d4d",
"shasum": ""
},
"require": {
@@ -12554,15 +12554,27 @@
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"security": "https://github.com/sebastianbergmann/type/security/policy",
"source": "https://github.com/sebastianbergmann/type/tree/6.0.2"
"source": "https://github.com/sebastianbergmann/type/tree/6.0.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/type",
"type": "tidelift"
}
],
"time": "2025-03-18T13:37:31+00:00"
"time": "2025-08-09T06:57:12+00:00"
},
{
"name": "sebastian/version",

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-08-09',
'build_time' => 1754719342,
'version' => 'develop/2025-08-10',
'build_time' => 1754800876,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 26,

10
package-lock.json generated
View File

@@ -4326,9 +4326,9 @@
}
},
"node_modules/browserslist": {
"version": "4.25.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
"integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
"version": "4.25.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz",
"integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==",
"dev": true,
"funding": [
{
@@ -4346,8 +4346,8 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001726",
"electron-to-chromium": "^1.5.173",
"caniuse-lite": "^1.0.30001733",
"electron-to-chromium": "^1.5.199",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.3"
},

View File

@@ -139,10 +139,13 @@ return [
'new_journals_header' => 'Firefly III has created a transaction for you. You can find it in your Firefly III installation:|Firefly III has created :count transactions for you. You can find them in your Firefly III installation:',
// subscription is overdue.
'subscription_overdue_subject' => 'Your subscription ":name" is overdue to be paid',
'subscription_overdue_warning_intro' => 'Your subscription ":name" is overdue to be paid. At the following date(s) a payment was expected, but it has not yet arrived.',
'subscription_overdue_please_action' => 'Perhaps you have simply not linked the transaction to subscription ":name". In that case, please do so. You will NOT get another warning about this overdue bill.',
'subscription_overdue_outro' => 'If you believe this message is wrong, please contact the Firefly III developer.',
'subscriptions_overdue_subject_multi' => 'You have :count subscriptions that are overdue to be paid',
'subscriptions_overdue_subject_single' => 'You have a subscription that is overdue to be paid',
'subscriptions_overdue_warning_intro_single' => 'You have one subscription that is overdue to be paid. At the following date(s) a payment was expected, but it has not yet arrived.',
'subscriptions_overdue_warning_intro_multi' => 'You have :count subscription(s) that are overdue to be paid. At the following date(s) a payment was expected, but it has not yet arrived.',
'subscriptions_overdue_please_action_single' => 'Perhaps you have simply not linked a transaction to this subscription. In that case, please do so. You will NOT get another warning about this overdue subscription. A new warning will be sent out for the NEXT due payment.',
'subscriptions_overdue_please_action_multi' => 'Perhaps you have simply not linked a transaction to these subscriptions. In that case, please do so. You will NOT get another warning about these overdue subscriptions. A new warning will be sent out for the NEXT due payments.',
'subscriptions_overdue_outro' => 'If you believe this message is wrong, please contact the Firefly III developer. Thank you for using Firefly III.',
// bill warning
'bill_warning_subject_end_date' => 'Your subscription ":name" is due to end in :diff days',
'bill_warning_subject_now_end_date' => 'Your subscription ":name" is due to end TODAY',

View File

@@ -1875,6 +1875,7 @@ return [
'subscr_expected_x_times' => 'Expect to pay {{amount}} {{times}} times this period',
'not_or_not_yet' => 'Not (yet)',
'visit_bill' => 'Visit subscription ":name" at Firefly III',
'visit_bills' => 'Visit subscriptions at Firefly III',
'match_between_amounts' => 'Subscription matches transactions between :low and :high.',
'running_again_loss' => 'Previously linked transactions to this subscription may lose their connection, if they (no longer) match the rule(s).',
'bill_related_rules' => 'Rules related to this subscription',
@@ -2332,6 +2333,7 @@ return [
// reports:
'quick_link_needs_accounts' => 'In order to generate reports, you need to add at least one asset account to Firefly III.',
'report_default' => 'Default financial report between :start and :end',
'report_audit' => 'Transaction history overview between :start and :end',
'report_category' => 'Category report between :start and :end',

View File

@@ -1,10 +0,0 @@
@component('mail::message')
{{ trans('email.subscription_overdue_warning_intro', ['name' => $bill->name]) }}
@foreach($dates['pay_dates'] as $date)
- {{ $date }}
@endforeach
{{ trans('email.subscription_overdue_please_action', ['name' => $bill->name]) }}
@endcomponent

View File

@@ -0,0 +1,24 @@
@component('mail::message')
@if(1 === $count)
{{ trans('email.subscriptions_overdue_warning_intro_single') }}
@endif
@if(1 !== $count)
{{ trans('email.subscriptions_overdue_warning_intro_multi', ['count' => $count]) }}
@endif
@foreach($info as $row)
- {{ $row['bill']->name }}:
@foreach($row['pay_dates'] as $date)
- {{ $date }}
@endforeach
@endforeach
@if(1 === $count)
{{ trans('email.subscriptions_overdue_please_action_single') }}
@endif
@if(1 !== $count)
{{ trans('email.subscriptions_overdue_please_action_multi', ['count' => $count]) }}
@endif
{{ trans('email.subscriptions_overdue_outro') }}
@endcomponent

View File

@@ -12,7 +12,6 @@
<td style="text-align: right;">{{ period.total_transactions }}</td>
</tr>
{% endif %}
{% for entry in period.spent %}
{% if entry.amount != 0 %}
<tr>

View File

@@ -46,8 +46,12 @@
{% endif %}
</td>
<td style="text-align: right;" class="piggySaved">
<span title="Saved so far"
style="text-align:right;">{{ formatAmountBySymbol(piggy.current_amount,piggy.currency_symbol,piggy.currency_decimal_places) }}</span>
<span title="Saved so far" style="text-align:right;">
{{ formatAmountBySymbol(piggy.current_amount,piggy.currency_symbol,piggy.currency_decimal_places) }}
{% if convertToPrimary and piggy.currency_id != primaryCurrency.id and null != piggy.pc_current_amount %}
({{ formatAmountBySymbol(piggy.pc_current_amount,primaryCurrency.symbol,primaryCurrency.decimal_places) }})
{% endif %}
</span>
</td>
<td class="hidden-sm hidden-xs" style="text-align:right;width:40px;">
{% if piggy.current_amount > 0 %}
@@ -86,16 +90,25 @@
<td class="hidden-sm hidden-xs" style="text-align:right;">
{% if null != piggy.target_amount and 0 != piggy.target_amount %}
<span title="{{ 'target_amount'|_ }}">{{ formatAmountBySymbol(piggy.target_amount,piggy.currency_symbol,piggy.currency_decimal_places) }}</span>
{% if convertToPrimary and piggy.currency_id != primaryCurrency.id and null != piggy.pc_target_amount %}
(<span title="{{ 'target_amount'|_ }}">{{ formatAmountBySymbol(piggy.pc_target_amount,primaryCurrency.symbol, primaryCurrency.decimal_places) }}</span>)
{% endif %}
{% endif %}
</td>
<td class="hidden-sm hidden-xs" style="text-align:right;">
{% if piggy.left_to_save > 0 %}
<span title="{{ 'left_to_save'|_ }}">{{ formatAmountBySymbol(piggy.left_to_save,piggy.currency_symbol,piggy.currency_decimal_places) }}</span>
{% if convertToPrimary and piggy.currency_id != primaryCurrency.id and null != piggy.pc_left_to_save %}
(<span title="{{ 'left_to_save'|_ }}">{{ formatAmountBySymbol(piggy.pc_left_to_save, primaryCurrency.symbol,primaryCurrency.decimal_places) }}</span>)
{% endif %}
{% endif %}
</td>
<td class="hidden-sm hidden-xs" style="text-align:right;">
{% if piggy.target_date and piggy.save_per_month %}
{{ formatAmountBySymbol(piggy.save_per_month, piggy.currency_symbol, piggy.currency_decimal_places) }}
{% if convertToPrimary and piggy.currency_id != primaryCurrency.id and null != piggy.pc_save_per_month %}
({{ formatAmountBySymbol(piggy.pc_save_per_month, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% endif %}
</td>
</tr>

View File

@@ -128,6 +128,12 @@
<h3 class="box-title">{{ 'quick_link_reports'|_ }}</h3>
</div>
<div class="box-body">
{% if '' == accountList %}
<p class="text-danger">
{{ 'quick_link_needs_accounts'|_ }}
</p>
{% endif %}
{% if '' != accountList %}
<p>
{{ 'quick_link_examples'|_ }}
</p>
@@ -172,6 +178,7 @@
<p>
<em>{{ 'reports_can_bookmark'|_ }}</em>
</p>
{% endif %}
</div>
</div>