mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-01 18:55:23 +00:00
Compare commits
54 Commits
develop-20
...
develop-20
Author | SHA1 | Date | |
---|---|---|---|
|
240ae8fa57 | ||
|
5a2f6b2652 | ||
|
4196ce31f0 | ||
|
be8ca5db50 | ||
|
30a417ea3c | ||
|
695ed940e0 | ||
|
1353554cf8 | ||
|
e1ba2732af | ||
|
42b57c0e0e | ||
|
a6072753b2 | ||
|
e92c224c39 | ||
|
a3ed7ec8f6 | ||
|
17a2f99dff | ||
|
c14971543c | ||
|
55f899608d | ||
|
83be63f27e | ||
|
ed48d190e5 | ||
|
3c3b6615e6 | ||
|
e71e5a877b | ||
|
b2a65dc660 | ||
|
d66dccd076 | ||
|
c1128b28f2 | ||
|
da8e78c28d | ||
|
f50aa6b0ce | ||
|
661e4e53e6 | ||
|
3eeda4a6aa | ||
|
4dba9cea21 | ||
|
6aab5fab05 | ||
|
4b0597d19a | ||
|
92f534bcb3 | ||
|
76e91be4dc | ||
|
deca4fed56 | ||
|
73512b0365 | ||
|
aaffc125e7 | ||
|
41a48c39a0 | ||
|
2d96bd84b5 | ||
|
ad1c1d2254 | ||
|
813206766d | ||
|
bb25d4a82a | ||
|
f3b78beecc | ||
|
64073768fe | ||
|
fe6dd0f901 | ||
|
aac8d11ff6 | ||
|
afa99a35b5 | ||
|
e9cb0a51d7 | ||
|
9fbcccfd02 | ||
|
468c9c9d56 | ||
|
f76b27a73d | ||
|
579fe81616 | ||
|
ec9ba53690 | ||
|
85337c53d4 | ||
|
eb6d585bb2 | ||
|
378ffbc609 | ||
|
3b3c8e5bcd |
@@ -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,19 @@ 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 = [];
|
||||
$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'));
|
||||
$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 +101,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[] = [
|
||||
|
97
app/Api/V1/Controllers/Chart/BalanceController.php
Normal file
97
app/Api/V1/Controllers/Chart/BalanceController.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Chart\ChartData;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Http\Api\AccountBalanceGrouped;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* Class BalanceController
|
||||
*/
|
||||
class BalanceController extends Controller
|
||||
{
|
||||
use CleansChartData;
|
||||
use CollectsAccountsFromFilter;
|
||||
|
||||
private ChartData $chartData;
|
||||
private GroupCollectorInterface $collector;
|
||||
private AccountRepositoryInterface $repository;
|
||||
|
||||
// private TransactionCurrency $default;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$this->collector = app(GroupCollectorInterface::class);
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
$this->collector->setUserGroup($userGroup);
|
||||
$this->chartData = new ChartData();
|
||||
// $this->default = app('amount')->getPrimaryCurrency();
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The code is practically a duplicate of ReportController::operations.
|
||||
*
|
||||
* Currency is up to the account/transactions in question, but conversion to the default
|
||||
* currency is possible.
|
||||
*
|
||||
* If the transaction being processed is already in native currency OR if the
|
||||
* foreign amount is in the native currency, the amount will not be converted.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function balance(ChartRequest $request): JsonResponse
|
||||
{
|
||||
$queryParameters = $request->getParameters();
|
||||
$accounts = $this->getAccountList($queryParameters);
|
||||
|
||||
// prepare for currency conversion and data collection:
|
||||
/** @var TransactionCurrency $primary */
|
||||
$primary = Amount::getPrimaryCurrency();
|
||||
|
||||
// get journals for entire period:
|
||||
|
||||
$this->collector->setRange($queryParameters['start'], $queryParameters['end'])
|
||||
->withAccountInformation()
|
||||
->setXorAccounts($accounts)
|
||||
->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::RECONCILIATION->value, TransactionTypeEnum::TRANSFER->value])
|
||||
;
|
||||
$journals = $this->collector->getExtractedJournals();
|
||||
|
||||
$object = new AccountBalanceGrouped();
|
||||
$object->setPreferredRange($queryParameters['period']);
|
||||
$object->setPrimary($primary);
|
||||
$object->setAccounts($accounts);
|
||||
$object->setJournals($journals);
|
||||
$object->setStart($queryParameters['start']);
|
||||
$object->setEnd($queryParameters['end']);
|
||||
$object->groupByCurrencyAndPeriod();
|
||||
$data = $object->convertToChartData();
|
||||
foreach ($data as $entry) {
|
||||
$this->chartData->add($entry);
|
||||
}
|
||||
|
||||
return response()->json($this->chartData->render());
|
||||
}
|
||||
}
|
@@ -248,10 +248,10 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
|
||||
.' + '.app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
|
||||
'sub_title' => Amount::formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
|
||||
.' + '.Amount::formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
|
||||
];
|
||||
$return[] = [
|
||||
'key' => sprintf('spent-in-%s', $currency->code),
|
||||
@@ -261,7 +261,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -273,7 +273,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -289,10 +289,10 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => app('amount')->formatAnything($currency, '0', false)
|
||||
.' + '.app('amount')->formatAnything($currency, '0', false),
|
||||
'sub_title' => Amount::formatAnything($currency, '0', false)
|
||||
.' + '.Amount::formatAnything($currency, '0', false),
|
||||
];
|
||||
$return[] = [
|
||||
'key' => sprintf('spent-in-%s', $currency->code),
|
||||
@@ -302,7 +302,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -314,7 +314,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
|
||||
'value_parsed' => Amount::formatAnything($currency, '0', false),
|
||||
'local_icon' => 'balance-scale',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -405,7 +405,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $info['code'],
|
||||
'currency_symbol' => $info['symbol'],
|
||||
'currency_decimal_places' => $info['decimal_places'],
|
||||
'value_parsed' => app('amount')->formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'local_icon' => 'check',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -424,7 +424,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $info['code'],
|
||||
'currency_symbol' => $info['symbol'],
|
||||
'currency_decimal_places' => $info['decimal_places'],
|
||||
'value_parsed' => app('amount')->formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'local_icon' => 'calendar-o',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -443,7 +443,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'local_icon' => 'check',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -455,7 +455,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'local_icon' => 'calendar-o',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -493,14 +493,9 @@ class BasicController extends Controller
|
||||
'currency_code' => $currencies[$currencyId]->code,
|
||||
'currency_symbol' => $currencies[$currencyId]->symbol,
|
||||
'currency_decimal_places' => $currencies[$currencyId]->decimal_places,
|
||||
'value_parsed' => app('amount')->formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
|
||||
'value_parsed' => Amount::formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
|
||||
'local_icon' => 'money',
|
||||
'sub_title' => app('amount')->formatFlat(
|
||||
$currencies[$currencyId]->symbol,
|
||||
$currencies[$currencyId]->decimal_places,
|
||||
$availableBudget,
|
||||
false
|
||||
),
|
||||
'sub_title' => Amount::formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
|
||||
];
|
||||
}
|
||||
foreach ($spent as $row) {
|
||||
@@ -529,18 +524,14 @@ class BasicController extends Controller
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_symbol' => $row['currency_symbol'],
|
||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||
'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false),
|
||||
'value_parsed' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false),
|
||||
'local_icon' => 'money',
|
||||
'sub_title' => app('amount')->formatFlat(
|
||||
$row['currency_symbol'],
|
||||
$row['currency_decimal_places'],
|
||||
$perDay,
|
||||
false
|
||||
),
|
||||
'sub_title' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $perDay, false),
|
||||
];
|
||||
}
|
||||
unset($leftToSpend);
|
||||
if (0 === count($return)) {
|
||||
$days = (int) $start->diffInDays($end, true) + 1;
|
||||
// a small trick to get every expense in this period, regardless of budget.
|
||||
$spent = $this->opsRepository->sumExpenses($start, $end, null, new Collection());
|
||||
foreach ($spent as $row) {
|
||||
@@ -563,14 +554,9 @@ class BasicController extends Controller
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_symbol' => $row['currency_symbol'],
|
||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||
'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $spentInCurrency, false),
|
||||
'value_parsed' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $spentInCurrency, false),
|
||||
'local_icon' => 'money',
|
||||
'sub_title' => app('amount')->formatFlat(
|
||||
$row['currency_symbol'],
|
||||
$row['currency_decimal_places'],
|
||||
$perDay,
|
||||
false
|
||||
),
|
||||
'sub_title' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $perDay, false),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -587,9 +573,9 @@ class BasicController extends Controller
|
||||
// 'currency_code' => $currency->code,
|
||||
// 'currency_symbol' => $currency->symbol,
|
||||
// 'currency_decimal_places' => $currency->decimal_places,
|
||||
// 'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
// 'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
// 'local_icon' => 'money',
|
||||
// 'sub_title' => app('amount')->formatFlat(
|
||||
// 'sub_title' => Amount::formatFlat(
|
||||
// $currency->symbol,
|
||||
// $currency->decimal_places,
|
||||
// '0',
|
||||
@@ -642,7 +628,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $data['currency_code'],
|
||||
'currency_symbol' => $data['currency_symbol'],
|
||||
'currency_decimal_places' => $data['currency_decimal_places'],
|
||||
'value_parsed' => app('amount')->formatFlat($data['currency_symbol'], $data['currency_decimal_places'], $data['balance'], false),
|
||||
'value_parsed' => Amount::formatFlat($data['currency_symbol'], $data['currency_decimal_places'], $data['balance'], false),
|
||||
'local_icon' => 'line-chart',
|
||||
'sub_title' => '',
|
||||
];
|
||||
@@ -656,7 +642,7 @@ class BasicController extends Controller
|
||||
'currency_code' => $this->primaryCurrency->code,
|
||||
'currency_symbol' => $this->primaryCurrency->symbol,
|
||||
'currency_decimal_places' => $this->primaryCurrency->decimal_places,
|
||||
'value_parsed' => app('amount')->formatFlat($this->primaryCurrency->symbol, $this->primaryCurrency->decimal_places, '0', false),
|
||||
'value_parsed' => Amount::formatFlat($this->primaryCurrency->symbol, $this->primaryCurrency->decimal_places, '0', false),
|
||||
'local_icon' => 'line-chart',
|
||||
'sub_title' => '',
|
||||
];
|
||||
|
@@ -54,6 +54,7 @@ class CronController extends Controller
|
||||
$return['exchange_rates'] = $this->exchangeRatesCronJob($config['force'], $config['date']);
|
||||
}
|
||||
$return['bill_notifications'] = $this->billWarningCronJob($config['force'], $config['date']);
|
||||
$return['webhooks'] = $this->webhookCronJob($config['force'], $config['date']);
|
||||
|
||||
return response()->api($return);
|
||||
}
|
||||
|
@@ -40,6 +40,7 @@ use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use Illuminate\Console\Command;
|
||||
@@ -72,6 +73,8 @@ class CorrectsPrimaryCurrencyAmounts extends Command
|
||||
/** @var UserGroupRepositoryInterface $repository */
|
||||
$repository = app(UserGroupRepositoryInterface::class);
|
||||
|
||||
Preferences::mark();
|
||||
|
||||
/** @var UserGroup $userGroup */
|
||||
foreach ($repository->getAll() as $userGroup) {
|
||||
$this->recalculateForGroup($userGroup);
|
||||
@@ -87,8 +90,7 @@ class CorrectsPrimaryCurrencyAmounts extends Command
|
||||
$this->recalculateAccounts($userGroup);
|
||||
|
||||
// do a check with the group's currency so we can skip some stuff.
|
||||
Preferences::mark();
|
||||
$currency = app('amount')->getPrimaryCurrencyByUserGroup($userGroup);
|
||||
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
|
||||
|
||||
$this->recalculatePiggyBanks($userGroup, $currency);
|
||||
$this->recalculateBudgets($userGroup, $currency);
|
||||
|
@@ -32,6 +32,7 @@ use FireflyIII\Support\Cronjobs\BillWarningCronjob;
|
||||
use FireflyIII\Support\Cronjobs\ExchangeRatesCronjob;
|
||||
use FireflyIII\Support\Cronjobs\RecurringCronjob;
|
||||
use FireflyIII\Support\Cronjobs\UpdateCheckCronjob;
|
||||
use FireflyIII\Support\Cronjobs\WebhookCronjob;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use InvalidArgumentException;
|
||||
@@ -50,6 +51,7 @@ class Cron extends Command
|
||||
{--create-recurring : Create recurring transactions. Other tasks will be skipped unless also requested.}
|
||||
{--create-auto-budgets : Create auto budgets. Other tasks will be skipped unless also requested.}
|
||||
{--send-bill-warnings : Send bill warnings. Other tasks will be skipped unless also requested.}
|
||||
{--send-webhook-messages : Sends any stray webhook messages (with a maximum of 5).}
|
||||
';
|
||||
|
||||
public function handle(): int
|
||||
@@ -58,7 +60,8 @@ class Cron extends Command
|
||||
&& !$this->option('create-recurring')
|
||||
&& !$this->option('create-auto-budgets')
|
||||
&& !$this->option('send-bill-warnings')
|
||||
&& !$this->option('check-version');
|
||||
&& !$this->option('check-version')
|
||||
&& !$this->option('send-webhook-messages');
|
||||
$date = null;
|
||||
|
||||
try {
|
||||
@@ -122,6 +125,16 @@ class Cron extends Command
|
||||
$this->friendlyError($e->getMessage());
|
||||
}
|
||||
}
|
||||
// Fire webhook messages cron job.
|
||||
if ($doAll || $this->option('send-webhook-messages')) {
|
||||
try {
|
||||
$this->webhookCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
$this->friendlyError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->friendlyInfo('More feedback on the cron jobs can be found in the log files.');
|
||||
|
||||
@@ -239,4 +252,26 @@ class Cron extends Command
|
||||
$this->friendlyPositive(sprintf('"Send bill warnings" cron ran with success: %s', $autoBudget->message));
|
||||
}
|
||||
}
|
||||
|
||||
private function webhookCronJob(bool $force, ?Carbon $date): void
|
||||
{
|
||||
$webhook = new WebhookCronjob();
|
||||
$webhook->setForce($force);
|
||||
// set date in cron job:
|
||||
if ($date instanceof Carbon) {
|
||||
$webhook->setDate($date);
|
||||
}
|
||||
|
||||
$webhook->fire();
|
||||
|
||||
if ($webhook->jobErrored) {
|
||||
$this->friendlyError(sprintf('Error in "webhook" cron: %s', $webhook->message));
|
||||
}
|
||||
if ($webhook->jobFired) {
|
||||
$this->friendlyInfo(sprintf('"Webhook" cron fired: %s', $webhook->message));
|
||||
}
|
||||
if ($webhook->jobSucceeded) {
|
||||
$this->friendlyPositive(sprintf('"Webhook" cron ran with success: %s', $webhook->message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
app/Events/Model/Bill/WarnUserAboutBill.php
Normal file
20
app/Events/Model/Bill/WarnUserAboutBill.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\Bill;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\Bill;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class WarnUserAboutBill.
|
||||
*/
|
||||
class WarnUserAboutBill extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(public Bill $bill, public string $field, public int $diff) {}
|
||||
}
|
17
app/Events/Model/Bill/WarnUserAboutOverdueSubscriptions.php
Normal file
17
app/Events/Model/Bill/WarnUserAboutOverdueSubscriptions.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\Bill;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class WarnUserAboutOverdueSubscriptions extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(public User $user, public array $overdue) {}
|
||||
|
||||
}
|
@@ -35,6 +35,6 @@ class UserGroupChangedPrimaryCurrency extends Event
|
||||
|
||||
public function __construct(public UserGroup $userGroup)
|
||||
{
|
||||
Log::debug('User group changed default currency.');
|
||||
Log::debug('User group changed primary currency.');
|
||||
}
|
||||
}
|
||||
|
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DestroyedTransactionGroup.php
|
||||
* Copyright (c) 2019 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;
|
||||
|
||||
use FireflyIII\Models\Bill;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class WarnUserAboutBill.
|
||||
*/
|
||||
class WarnUserAboutBill extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(public Bill $bill, public string $field, public int $diff) {}
|
||||
}
|
@@ -24,48 +24,118 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Events\WarnUserAboutBill;
|
||||
use FireflyIII\Notifications\User\BillReminder;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Exception;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Notifications\User\BillReminder;
|
||||
use FireflyIII\Notifications\User\SubscriptionsOverdueReminder;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
/**
|
||||
* Class BillEventHandler
|
||||
*/
|
||||
class BillEventHandler
|
||||
{
|
||||
public function warnAboutOverdueSubscriptions(WarnUserAboutOverdueSubscriptions $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)));
|
||||
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));
|
||||
|
||||
continue;
|
||||
}
|
||||
$toBeWarned[] = $item;
|
||||
}
|
||||
unset($bill);
|
||||
Log::debug(sprintf('Now %d bills 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.');
|
||||
|
||||
return;
|
||||
}
|
||||
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
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$bill = $event->bill;
|
||||
$bill = $event->bill;
|
||||
|
||||
/** @var bool $preference */
|
||||
$preference = app('preferences')->getForUser($bill->user, 'notification_bill_reminder', true)->data;
|
||||
Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data;
|
||||
|
||||
if (true === $preference) {
|
||||
app('log')->debug('Bill reminder is true!');
|
||||
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')) {
|
||||
app('log')->warning('[Bcc] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
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')) {
|
||||
app('log')->warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
Log::warning('[RFC] Could not send notification. Please validate your email settings, use the .env.example file as a guide.');
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (false === $preference) {
|
||||
app('log')->debug('User has disabled bill reminders.');
|
||||
}
|
||||
Log::debug('User has disabled bill reminders.');
|
||||
}
|
||||
}
|
||||
|
@@ -73,6 +73,7 @@ class PreferencesEventHandler
|
||||
$repository = app(PiggyBankRepositoryInterface::class);
|
||||
$repository->setUserGroup($userGroup);
|
||||
$piggyBanks = $repository->getPiggyBanks();
|
||||
Log::debug(sprintf('Resetting %d piggy bank(s).', $piggyBanks->count()));
|
||||
|
||||
/** @var PiggyBank $piggyBank */
|
||||
foreach ($piggyBanks as $piggyBank) {
|
||||
@@ -104,6 +105,8 @@ class PreferencesEventHandler
|
||||
$repository->setUserGroup($userGroup);
|
||||
$set = $repository->getBudgets();
|
||||
|
||||
Log::debug(sprintf('Resetting %d budget(s).', $set->count()));
|
||||
|
||||
/** @var Budget $budget */
|
||||
foreach ($set as $budget) {
|
||||
foreach ($budget->autoBudgets as $autoBudget) {
|
||||
|
@@ -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';
|
||||
|
@@ -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);
|
||||
|
||||
|
||||
|
@@ -117,7 +117,6 @@ class ShowController extends Controller
|
||||
|
||||
Log::debug('Start period overview');
|
||||
Timer::start('period-overview');
|
||||
|
||||
$periods = $this->getAccountPeriodOverview($account, $firstTransaction, $end);
|
||||
|
||||
Log::debug('End period overview');
|
||||
@@ -136,6 +135,7 @@ class ShowController extends Controller
|
||||
->setAccounts(new Collection([$account]))
|
||||
->setLimit($pageSize)
|
||||
->setPage($page)
|
||||
->withAttachmentInformation()
|
||||
->withAPIInformation()
|
||||
->setRange($start, $end)
|
||||
;
|
||||
|
@@ -57,7 +57,7 @@ class IndexController extends Controller
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
app('view')->share('title', (string) trans('firefly.bills'));
|
||||
app('view')->share('title', (string)trans('firefly.bills'));
|
||||
app('view')->share('mainTitleIcon', 'fa-calendar-o');
|
||||
$this->repository = app(BillRepositoryInterface::class);
|
||||
|
||||
@@ -79,7 +79,6 @@ class IndexController extends Controller
|
||||
$total = $collection->count();
|
||||
|
||||
|
||||
|
||||
$parameters = new ParameterBag();
|
||||
// sub one day from temp start so the last paid date is one day before it should be.
|
||||
$tempStart = clone $start;
|
||||
@@ -112,7 +111,7 @@ class IndexController extends Controller
|
||||
$bills = [
|
||||
0 => [ // the index is the order, not the ID.
|
||||
'object_group_id' => 0,
|
||||
'object_group_title' => (string) trans('firefly.default_group_title_name'),
|
||||
'object_group_title' => (string)trans('firefly.default_group_title_name'),
|
||||
'bills' => [],
|
||||
],
|
||||
];
|
||||
@@ -120,7 +119,7 @@ class IndexController extends Controller
|
||||
/** @var Bill $bill */
|
||||
foreach ($collection as $bill) {
|
||||
$array = $transformer->transform($bill);
|
||||
$groupOrder = (int) $array['object_group_order'];
|
||||
$groupOrder = (int)$array['object_group_order'];
|
||||
// make group array if necessary:
|
||||
$bills[$groupOrder] ??= [
|
||||
'object_group_id' => $array['object_group_id'],
|
||||
@@ -173,16 +172,28 @@ class IndexController extends Controller
|
||||
'currency_symbol' => $bill['currency_symbol'],
|
||||
'currency_decimal_places' => $bill['currency_decimal_places'],
|
||||
'avg' => '0',
|
||||
'total_left_to_pay' => '0',
|
||||
'period' => $range,
|
||||
'per_period' => '0',
|
||||
];
|
||||
|
||||
// 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']));
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// fill in per period regardless:
|
||||
$sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $this->amountPerPeriod($bill, $range));
|
||||
}
|
||||
@@ -193,7 +204,7 @@ class IndexController extends Controller
|
||||
|
||||
private function amountPerPeriod(array $bill, string $range): string
|
||||
{
|
||||
$avg = bcdiv(bcadd((string) $bill['amount_min'], (string) $bill['amount_max']), '2');
|
||||
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
|
||||
|
||||
app('log')->debug(sprintf('Amount per period for bill #%d "%s"', $bill['id'], $bill['name']));
|
||||
app('log')->debug(sprintf('Average is %s', $avg));
|
||||
@@ -206,8 +217,8 @@ class IndexController extends Controller
|
||||
'weekly' => '52.17',
|
||||
'daily' => '365.24',
|
||||
];
|
||||
$yearAmount = bcmul($avg, bcdiv($multiplies[$bill['repeat_freq']], (string) ($bill['skip'] + 1)));
|
||||
app('log')->debug(sprintf('Amount per year is %s (%s * %s / %s)', $yearAmount, $avg, $multiplies[$bill['repeat_freq']], (string) ($bill['skip'] + 1)));
|
||||
$yearAmount = bcmul($avg, bcdiv($multiplies[$bill['repeat_freq']], (string)($bill['skip'] + 1)));
|
||||
app('log')->debug(sprintf('Amount per year is %s (%s * %s / %s)', $yearAmount, $avg, $multiplies[$bill['repeat_freq']], (string)($bill['skip'] + 1)));
|
||||
|
||||
// per period:
|
||||
$division = [
|
||||
@@ -258,8 +269,8 @@ class IndexController extends Controller
|
||||
'period' => $entry['period'],
|
||||
'per_period' => '0',
|
||||
];
|
||||
$totals[$currencyId]['avg'] = bcadd($totals[$currencyId]['avg'], (string) $entry['avg']);
|
||||
$totals[$currencyId]['per_period'] = bcadd($totals[$currencyId]['per_period'], (string) $entry['per_period']);
|
||||
$totals[$currencyId]['avg'] = bcadd($totals[$currencyId]['avg'], (string)$entry['avg']);
|
||||
$totals[$currencyId]['per_period'] = bcadd($totals[$currencyId]['per_period'], (string)$entry['per_period']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,8 +282,8 @@ class IndexController extends Controller
|
||||
*/
|
||||
public function setOrder(Request $request, Bill $bill): JsonResponse
|
||||
{
|
||||
$objectGroupTitle = (string) $request->get('objectGroupTitle');
|
||||
$newOrder = (int) $request->get('order');
|
||||
$objectGroupTitle = (string)$request->get('objectGroupTitle');
|
||||
$newOrder = (int)$request->get('order');
|
||||
$this->repository->setOrder($bill, $newOrder);
|
||||
if ('' !== $objectGroupTitle) {
|
||||
$this->repository->setObjectGroup($bill, $objectGroupTitle);
|
||||
|
@@ -35,6 +35,7 @@ use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Controllers\AugumentData;
|
||||
use FireflyIII\Support\Http\Controllers\ChartGeneration;
|
||||
@@ -78,6 +79,7 @@ class AccountController extends Controller
|
||||
|
||||
/**
|
||||
* Shows the balances for all the user's expense accounts (on the front page).
|
||||
* 2025-08-06 validated for multi (primary) currency
|
||||
*
|
||||
* This chart is (multi) currency aware.
|
||||
*/
|
||||
@@ -112,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);
|
||||
$endBalances = Steam::finalAccountsBalance($accounts, $end);
|
||||
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) {
|
||||
@@ -157,7 +159,7 @@ class AccountController extends Controller
|
||||
$tempData[] = [
|
||||
'name' => $accountNames[$account->id],
|
||||
'difference' => $diff,
|
||||
'diff_float' => (float) $diff, // intentional float
|
||||
'diff_float' => (float)$diff, // intentional float
|
||||
'currency_id' => $currencies[$searchCode]->id,
|
||||
];
|
||||
}
|
||||
@@ -182,7 +184,7 @@ class AccountController extends Controller
|
||||
foreach ($currencies as $currencyId => $currency) {
|
||||
$dataSet
|
||||
= [
|
||||
'label' => (string) trans('firefly.spent'),
|
||||
'label' => (string)trans('firefly.spent'),
|
||||
'type' => 'bar',
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_code' => $currency->code,
|
||||
@@ -195,7 +197,7 @@ class AccountController extends Controller
|
||||
foreach ($tempData as $entry) {
|
||||
$currencyId = $entry['currency_id'];
|
||||
$name = $entry['name'];
|
||||
$chartData[$currencyId]['entries'][$name] = (float) $entry['difference'];
|
||||
$chartData[$currencyId]['entries'][$name] = (float)$entry['difference'];
|
||||
}
|
||||
|
||||
$data = $this->generator->multiSet($chartData);
|
||||
@@ -223,6 +225,7 @@ class AccountController extends Controller
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($account->id);
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.account.expense-budget');
|
||||
if ($cache->has()) {
|
||||
@@ -231,7 +234,10 @@ class AccountController extends Controller
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withBudgetInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
$collector->setAccounts(new Collection([$account]))
|
||||
->setRange($start, $end)
|
||||
->withBudgetInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
|
||||
;
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$chartData = [];
|
||||
$result = [];
|
||||
@@ -239,19 +245,37 @@ class AccountController extends Controller
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$budgetId = (int) $journal['budget_id'];
|
||||
$budgetId = (int)$journal['budget_id'];
|
||||
$key = sprintf('%d-%d', $budgetId, $journal['currency_id']);
|
||||
$budgetIds[] = $budgetId;
|
||||
|
||||
// currency info:
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyDecimalPlaces = $journal['currency_decimal_places'];
|
||||
$field = 'amount';
|
||||
if ($this->convertToPrimary && $this->primaryCurrency->id !== $currencyId) {
|
||||
$field = 'pc_amount';
|
||||
$currencyName = $this->primaryCurrency->name;
|
||||
$currencySymbol = $this->primaryCurrency->symbol;
|
||||
$currencyCode = $this->primaryCurrency->code;
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
|
||||
}
|
||||
|
||||
if (!array_key_exists($key, $result)) {
|
||||
$result[$key] = [
|
||||
'total' => '0',
|
||||
'budget_id' => $budgetId,
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'total' => '0',
|
||||
'budget_id' => $budgetId,
|
||||
'currency_name' => $currencyName,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
}
|
||||
$result[$key]['total'] = bcadd((string) $journal['amount'], $result[$key]['total']);
|
||||
$result[$key]['total'] = bcadd((string)$journal[$field], $result[$key]['total']);
|
||||
}
|
||||
|
||||
$names = $this->getBudgetNames($budgetIds);
|
||||
@@ -259,7 +283,7 @@ class AccountController extends Controller
|
||||
foreach ($result as $row) {
|
||||
$budgetId = $row['budget_id'];
|
||||
$name = $names[$budgetId];
|
||||
$label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$label = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']];
|
||||
}
|
||||
|
||||
@@ -289,6 +313,7 @@ class AccountController extends Controller
|
||||
$cache->addProperty($account->id);
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty('chart.account.expense-category');
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
@@ -305,22 +330,39 @@ class AccountController extends Controller
|
||||
foreach ($journals as $journal) {
|
||||
$key = sprintf('%d-%d', $journal['category_id'], $journal['currency_id']);
|
||||
if (!array_key_exists($key, $result)) {
|
||||
$result[$key] = [
|
||||
'total' => '0',
|
||||
'category_id' => (int) $journal['category_id'],
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
|
||||
// currency info:
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyDecimalPlaces = $journal['currency_decimal_places'];
|
||||
$field = 'amount';
|
||||
if ($this->convertToPrimary && $this->primaryCurrency->id !== $currencyId) {
|
||||
$field = 'pc_amount';
|
||||
$currencyName = $this->primaryCurrency->name;
|
||||
$currencySymbol = $this->primaryCurrency->symbol;
|
||||
$currencyCode = $this->primaryCurrency->code;
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
}
|
||||
|
||||
$result[$key] = [
|
||||
'total' => '0',
|
||||
'category_id' => (int)$journal['category_id'],
|
||||
'currency_name' => $currencyName,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
}
|
||||
$result[$key]['total'] = bcadd((string) $journal['amount'], $result[$key]['total']);
|
||||
$result[$key]['total'] = bcadd((string)$journal[$field], $result[$key]['total']);
|
||||
}
|
||||
$names = $this->getCategoryNames(array_keys($result));
|
||||
|
||||
foreach ($result as $row) {
|
||||
$categoryId = $row['category_id'];
|
||||
$name = $names[$categoryId] ?? '(unknown)';
|
||||
$label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$label = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']];
|
||||
}
|
||||
|
||||
@@ -341,11 +383,11 @@ class AccountController extends Controller
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$defaultSet = $repository->getAccountsByType([AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
|
||||
// Log::debug('Default set is ', $defaultSet);
|
||||
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
|
||||
$frontpage = Preferences::get('frontpageAccounts', $defaultSet);
|
||||
$frontpageArray = !is_array($frontpage->data) ? [] : $frontpage->data;
|
||||
Log::debug('Frontpage preference set is ', $frontpageArray);
|
||||
if (0 === count($frontpageArray)) {
|
||||
app('preferences')->set('frontpageAccounts', $defaultSet);
|
||||
Preferences::set('frontpageAccounts', $defaultSet);
|
||||
Log::debug('frontpage set is empty!');
|
||||
}
|
||||
$accounts = $repository->getAccountsById($frontpageArray);
|
||||
@@ -375,6 +417,7 @@ class AccountController extends Controller
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($account->id);
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.account.income-category');
|
||||
if ($cache->has()) {
|
||||
@@ -394,22 +437,39 @@ class AccountController extends Controller
|
||||
foreach ($journals as $journal) {
|
||||
$key = sprintf('%d-%d', $journal['category_id'], $journal['currency_id']);
|
||||
if (!array_key_exists($key, $result)) {
|
||||
$result[$key] = [
|
||||
'total' => '0',
|
||||
'category_id' => $journal['category_id'],
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
|
||||
// currency info:
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyDecimalPlaces = $journal['currency_decimal_places'];
|
||||
$field = 'amount';
|
||||
if ($this->convertToPrimary && $this->primaryCurrency->id !== $currencyId) {
|
||||
$field = 'pc_amount';
|
||||
$currencyName = $this->primaryCurrency->name;
|
||||
$currencySymbol = $this->primaryCurrency->symbol;
|
||||
$currencyCode = $this->primaryCurrency->code;
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
}
|
||||
|
||||
$result[$key] = [
|
||||
'total' => '0',
|
||||
'category_id' => $journal['category_id'],
|
||||
'currency_name' => $currencyName,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
}
|
||||
$result[$key]['total'] = bcadd((string) $journal['amount'], $result[$key]['total']);
|
||||
$result[$key]['total'] = bcadd((string)$journal[$field], $result[$key]['total']);
|
||||
}
|
||||
|
||||
$names = $this->getCategoryNames(array_keys($result));
|
||||
foreach ($result as $row) {
|
||||
$categoryId = $row['category_id'];
|
||||
$name = $names[$categoryId] ?? '(unknown)';
|
||||
$label = (string) trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$label = (string)trans('firefly.name_in_currency', ['name' => $name, 'currency' => $row['currency_name']]);
|
||||
$chartData[$label] = ['amount' => $row['total'], 'currency_symbol' => $row['currency_symbol'], 'currency_code' => $row['currency_code']];
|
||||
}
|
||||
$data = $this->generator->multiCurrencyPieChart($chartData);
|
||||
@@ -450,7 +510,7 @@ class AccountController extends Controller
|
||||
// This period depends on the size of the chart
|
||||
$current = clone $start;
|
||||
$current = app('navigation')->endOfX($current, $step, null);
|
||||
$format = (string) trans('config.month_and_day_js', [], $locale);
|
||||
$format = (string)trans('config.month_and_day_js', [], $locale);
|
||||
$accountCurrency = $this->accountRepository->getAccountCurrency($account);
|
||||
|
||||
$range = Steam::finalAccountBalanceInRange($account, $start, $end, $this->convertToPrimary);
|
||||
@@ -512,7 +572,7 @@ class AccountController extends Controller
|
||||
foreach ($return as $key => $info) {
|
||||
if ('balance' !== $key && 'pc_balance' !== $key) {
|
||||
// assume it's a currency:
|
||||
$setCurrency = $this->currencyRepository->findByCode((string) $key);
|
||||
$setCurrency = $this->currencyRepository->findByCode((string)$key);
|
||||
$info['currency_symbol'] = $setCurrency->symbol;
|
||||
$info['currency_code'] = $setCurrency->code;
|
||||
$info['label'] = sprintf('%s (%s)', $account->name, $setCurrency->symbol);
|
||||
@@ -525,7 +585,7 @@ class AccountController extends Controller
|
||||
if ('pc_balance' === $key) {
|
||||
$info['currency_symbol'] = $this->primaryCurrency->symbol;
|
||||
$info['currency_code'] = $this->primaryCurrency->code;
|
||||
$info['label'] = sprintf('%s (%s) (%s)', $account->name, (string) trans('firefly.sum'), $this->primaryCurrency->symbol);
|
||||
$info['label'] = sprintf('%s (%s) (%s)', $account->name, (string)trans('firefly.sum'), $this->primaryCurrency->symbol);
|
||||
}
|
||||
$chartData[] = $info;
|
||||
}
|
||||
@@ -594,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.
|
||||
@@ -640,7 +700,7 @@ class AccountController extends Controller
|
||||
$tempData[] = [
|
||||
'name' => $accountNames[$account->id],
|
||||
'difference' => $diff,
|
||||
'diff_float' => (float) $diff, // intentional float
|
||||
'diff_float' => (float)$diff, // intentional float
|
||||
'currency_id' => $currencies[$searchCode]->id,
|
||||
];
|
||||
}
|
||||
@@ -667,7 +727,7 @@ class AccountController extends Controller
|
||||
foreach ($currencies as $currencyId => $currency) {
|
||||
$dataSet
|
||||
= [
|
||||
'label' => (string) trans('firefly.earned'),
|
||||
'label' => (string)trans('firefly.earned'),
|
||||
'type' => 'bar',
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_code' => $currency->code,
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use FireflyIII\Events\RequestedSendWebhookMessages;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
@@ -33,6 +34,7 @@ use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@@ -141,6 +143,14 @@ abstract class Controller extends BaseController
|
||||
View::share('shownDemo', $shownDemo);
|
||||
View::share('current_route_name', $page);
|
||||
View::share('original_route_name', Route::currentRouteName());
|
||||
|
||||
// lottery to send any remaining webhooks:
|
||||
if (7 === random_int(1, 10)) {
|
||||
// trigger event to send them:
|
||||
Log::debug('send event RequestedSendWebhookMessages through lottery');
|
||||
event(new RequestedSendWebhookMessages());
|
||||
}
|
||||
|
||||
}
|
||||
View::share('darkMode', $darkMode);
|
||||
|
||||
|
@@ -179,14 +179,6 @@ class IndexController extends Controller
|
||||
$return[$accountId]['target'] = '0';
|
||||
$return[$accountId]['to_save'] = '0';
|
||||
}
|
||||
|
||||
// calculate new interesting fields:
|
||||
// $return[$accountId]['left'] -= $array['current_amount'];
|
||||
// $return[$accountId]['saved'] += $array['current_amount'];
|
||||
// $return[$accountId]['target'] += $array['target_amount'];
|
||||
// $return[$accountId]['to_save'] += ($array['target_amount'] - $array['current_amount']);
|
||||
// $return['account_name'] = $account['name'];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use FireflyIII\Support\Singleton\PreferencesSingleton;
|
||||
use JsonException;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
@@ -269,7 +270,9 @@ class PreferencesController extends Controller
|
||||
if ($convertToPrimary && !$this->convertToPrimary) {
|
||||
// set to true!
|
||||
Log::debug('User sets convertToPrimary to true.');
|
||||
Preferences::set('convert_to_primary', $convertToPrimary);
|
||||
Preferences::set('convert_to_primary', true);
|
||||
$singleton = PreferencesSingleton::getInstance();
|
||||
$singleton->resetPreferences();
|
||||
event(new UserGroupChangedPrimaryCurrency(auth()->user()->userGroup));
|
||||
}
|
||||
Preferences::set('convert_to_primary', $convertToPrimary);
|
||||
|
@@ -34,6 +34,7 @@ use FireflyIII\Models\RecurrenceRepetition;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\ExpandedForm;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
|
||||
use FireflyIII\Transformers\RecurrenceTransformer;
|
||||
use FireflyIII\User;
|
||||
@@ -106,8 +107,8 @@ class EditController extends Controller
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
|
||||
$array = $transformer->transform($recurrence);
|
||||
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
|
||||
$bills = app('expandedform')->makeSelectListWithEmpty($this->billRepository->getActiveBills());
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
|
||||
$bills = ExpandedForm::makeSelectListWithEmpty($this->billRepository->getActiveBills());
|
||||
|
||||
/** @var RecurrenceRepetition $repetition */
|
||||
$repetition = $recurrence->recurrenceRepetitions()->first();
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Report;
|
||||
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use Throwable;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
@@ -290,11 +291,12 @@ class BudgetController extends Controller
|
||||
$cache->addProperty('budget-period-report');
|
||||
$cache->addProperty($accounts->pluck('id')->toArray());
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
// return $cache->get();
|
||||
}
|
||||
|
||||
$periods = app('navigation')->listOfPeriods($start, $end);
|
||||
$keyFormat = app('navigation')->preferredCarbonFormat($start, $end);
|
||||
$periods = Navigation::listOfPeriods($start, $end);
|
||||
$keyFormat = Navigation::preferredCarbonFormat($start, $end);
|
||||
|
||||
// list expenses for budgets in account(s)
|
||||
$expenses = $this->opsRepository->listExpenses($start, $end, $accounts);
|
||||
|
||||
@@ -303,6 +305,17 @@ class BudgetController extends Controller
|
||||
foreach ($currency['budgets'] as $budget) {
|
||||
$count = 0;
|
||||
foreach ($budget['transaction_journals'] as $journal) {
|
||||
// #10678
|
||||
// skip transactions between two asset / liability accounts.
|
||||
if (
|
||||
in_array($journal['source_account_type'], config('firefly.valid_currency_account_types'), true)
|
||||
&& in_array($journal['destination_account_type'], config('firefly.valid_currency_account_types'), true)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
++$count;
|
||||
$key = sprintf('%d-%d', $budget['id'], $currency['currency_id']);
|
||||
$dateKey = $journal['date']->format($keyFormat);
|
||||
|
@@ -270,6 +270,7 @@ class CreateController extends Controller
|
||||
$data = $request->getRuleData();
|
||||
|
||||
$rule = $this->ruleRepos->store($data);
|
||||
session()->flash('success_url', route('rules.select-transactions', [$rule->id]));
|
||||
session()->flash('success', (string) trans('firefly.stored_new_rule', ['title' => $rule->title]));
|
||||
app('preferences')->mark();
|
||||
|
||||
|
@@ -252,6 +252,7 @@ class TagController extends Controller
|
||||
|
||||
$collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withAccountInformation()
|
||||
->setTag($tag)->withBudgetInformation()->withCategoryInformation()
|
||||
->withAttachmentInformation()
|
||||
;
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups->setPath($path);
|
||||
@@ -283,6 +284,7 @@ class TagController extends Controller
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withAccountInformation()
|
||||
->setTag($tag)->withBudgetInformation()->withCategoryInformation()
|
||||
->withAttachmentInformation()
|
||||
;
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups->setPath($path);
|
||||
|
@@ -25,16 +25,20 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface;
|
||||
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class ShowController
|
||||
@@ -57,7 +61,7 @@ class ShowController extends Controller
|
||||
$this->repository = app(TransactionGroupRepositoryInterface::class);
|
||||
$this->aleRepository = app(ALERepositoryInterface::class);
|
||||
|
||||
app('view')->share('title', (string) trans('firefly.transactions'));
|
||||
app('view')->share('title', (string)trans('firefly.transactions'));
|
||||
app('view')->share('mainTitleIcon', 'fa-exchange');
|
||||
|
||||
return $next($request);
|
||||
@@ -80,38 +84,62 @@ class ShowController extends Controller
|
||||
*/
|
||||
public function show(TransactionGroup $transactionGroup)
|
||||
{
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
|
||||
// use new group collector:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUser($admin)->setTransactionGroup($transactionGroup)->withAPIInformation();
|
||||
|
||||
$selectedGroup = $collector->getGroups()->first();
|
||||
if (null === $selectedGroup) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
// enrich
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
|
||||
|
||||
/** @var null|TransactionJournal $first */
|
||||
$first = $transactionGroup->transactionJournals()->first(['transaction_journals.*']);
|
||||
$splits = $transactionGroup->transactionJournals()->count();
|
||||
$splits = count($selectedGroup['transactions']);
|
||||
$keys = array_keys($selectedGroup['transactions']);
|
||||
$first = $selectedGroup['transactions'][array_shift($keys)];
|
||||
unset($keys);
|
||||
|
||||
if (null === $first) {
|
||||
throw new FireflyException('This transaction is broken :(.');
|
||||
}
|
||||
|
||||
$type = (string) trans(sprintf('firefly.%s', $first->transactionType->type));
|
||||
$title = 1 === $splits ? $first->description : $transactionGroup->title;
|
||||
$type = (string)trans(sprintf('firefly.%s', $first['transaction_type_type']));
|
||||
$title = 1 === $splits ? $first['description'] : $selectedGroup['title'];
|
||||
$subTitle = sprintf('%s: "%s"', $type, $title);
|
||||
|
||||
// enrich
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
|
||||
/** @var TransactionGroupTransformer $transformer */
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
$groupArray = $transformer->transformObject($transactionGroup);
|
||||
|
||||
// do some calculations:
|
||||
$amounts = $this->getAmounts($groupArray);
|
||||
$accounts = $this->getAccounts($groupArray);
|
||||
$amounts = $this->getAmounts($selectedGroup);
|
||||
$accounts = $this->getAccounts($selectedGroup);
|
||||
|
||||
foreach (array_keys($groupArray['transactions']) as $index) {
|
||||
$groupArray['transactions'][$index]['tags'] = $this->repository->getTagObjects(
|
||||
(int) $groupArray['transactions'][$index]['transaction_journal_id']
|
||||
);
|
||||
foreach (array_keys($selectedGroup['transactions']) as $index) {
|
||||
$selectedGroup['transactions'][$index]['tags'] = $this->repository->getTagObjects((int)$selectedGroup['transactions'][$index]['transaction_journal_id']);
|
||||
}
|
||||
|
||||
// get audit log entries:
|
||||
$groupLogEntries = $this->aleRepository->getForObject($transactionGroup);
|
||||
$logEntries = [];
|
||||
foreach ($transactionGroup->transactionJournals as $journal) {
|
||||
$logEntries[$journal->id] = $this->aleRepository->getForObject($journal);
|
||||
foreach ($selectedGroup['transactions'] as $journal) {
|
||||
$logEntries[$journal['transaction_journal_id']] = $this->aleRepository->getForId(TransactionJournal::class, $journal['transaction_journal_id']);
|
||||
}
|
||||
|
||||
$events = $this->repository->getPiggyEvents($transactionGroup);
|
||||
@@ -129,6 +157,7 @@ class ShowController extends Controller
|
||||
'groupLogEntries',
|
||||
'subTitle',
|
||||
'splits',
|
||||
'selectedGroup',
|
||||
'groupArray',
|
||||
'events',
|
||||
'attachments',
|
||||
@@ -142,34 +171,38 @@ class ShowController extends Controller
|
||||
{
|
||||
$amounts = [];
|
||||
foreach ($group['transactions'] as $transaction) {
|
||||
// add normal amount:
|
||||
$symbol = $transaction['currency_symbol'];
|
||||
if (!array_key_exists($symbol, $amounts)) {
|
||||
$amounts[$symbol] = [
|
||||
'amount' => '0',
|
||||
'symbol' => $symbol,
|
||||
'decimal_places' => $transaction['currency_decimal_places'],
|
||||
];
|
||||
}
|
||||
$amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], (string) $transaction['amount']);
|
||||
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount']
|
||||
&& 0 !== bccomp(
|
||||
'0',
|
||||
(string) $transaction['foreign_amount']
|
||||
)) {
|
||||
$amounts[$symbol] ??= [
|
||||
'amount' => '0',
|
||||
'symbol' => $symbol,
|
||||
'decimal_places' => $transaction['currency_decimal_places'],
|
||||
];
|
||||
$amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], (string)$transaction['amount']);
|
||||
|
||||
// add foreign amount:
|
||||
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', (string)$transaction['foreign_amount'])) {
|
||||
// same for foreign currency:
|
||||
$foreignSymbol = $transaction['foreign_currency_symbol'];
|
||||
if (!array_key_exists($foreignSymbol, $amounts)) {
|
||||
$amounts[$foreignSymbol] = [
|
||||
'amount' => '0',
|
||||
'symbol' => $foreignSymbol,
|
||||
'decimal_places' => $transaction['foreign_currency_decimal_places'],
|
||||
];
|
||||
}
|
||||
$amounts[$foreignSymbol]['amount'] = bcadd(
|
||||
$amounts[$foreignSymbol]['amount'],
|
||||
(string) $transaction['foreign_amount']
|
||||
);
|
||||
$amounts[$foreignSymbol] ??= [
|
||||
'amount' => '0',
|
||||
'symbol' => $foreignSymbol,
|
||||
'decimal_places' => $transaction['foreign_currency_decimal_places'],
|
||||
];
|
||||
$amounts[$foreignSymbol]['amount'] = bcadd($amounts[$foreignSymbol]['amount'], (string)$transaction['foreign_amount']);
|
||||
}
|
||||
// add primary currency amount
|
||||
if (null !== $transaction['pc_amount'] && $transaction['currency_id'] !== $this->primaryCurrency->id) {
|
||||
// same for foreign currency:
|
||||
$primarySymbol = $this->primaryCurrency->symbol;
|
||||
$amounts[$primarySymbol] ??= [
|
||||
'amount' => '0',
|
||||
'symbol' => $this->primaryCurrency->symbol,
|
||||
'decimal_places' => $this->primaryCurrency->decimal_places,
|
||||
];
|
||||
$amounts[$primarySymbol]['amount'] = bcadd($amounts[$primarySymbol]['amount'], (string)$transaction['pc_amount']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $amounts;
|
||||
@@ -184,16 +217,16 @@ class ShowController extends Controller
|
||||
|
||||
foreach ($group['transactions'] as $transaction) {
|
||||
$accounts['source'][] = [
|
||||
'type' => $transaction['source_type'],
|
||||
'id' => $transaction['source_id'],
|
||||
'name' => $transaction['source_name'],
|
||||
'iban' => $transaction['source_iban'],
|
||||
'type' => $transaction['source_account_type'],
|
||||
'id' => $transaction['source_account_id'],
|
||||
'name' => $transaction['source_account_name'],
|
||||
'iban' => $transaction['source_account_iban'],
|
||||
];
|
||||
$accounts['destination'][] = [
|
||||
'type' => $transaction['destination_type'],
|
||||
'id' => $transaction['destination_id'],
|
||||
'name' => $transaction['destination_name'],
|
||||
'iban' => $transaction['destination_iban'],
|
||||
'type' => $transaction['destination_account_type'],
|
||||
'id' => $transaction['destination_account_id'],
|
||||
'name' => $transaction['destination_account_name'],
|
||||
'iban' => $transaction['destination_account_iban'],
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -25,13 +25,18 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Jobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\WarnUserAboutBill;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
|
||||
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;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class WarnAboutBills
|
||||
@@ -63,7 +68,7 @@ class WarnAboutBills implements ShouldQueue
|
||||
|
||||
$this->force = false;
|
||||
|
||||
app('log')->debug(sprintf('Created new WarnAboutBills("%s")', $this->date->format('Y-m-d')));
|
||||
Log::debug(sprintf('Created new WarnAboutBills("%s")', $this->date->format('Y-m-d')));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,36 +76,42 @@ class WarnAboutBills implements ShouldQueue
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now at start of WarnAboutBills() job for %s.', $this->date->format('D d M Y')));
|
||||
$bills = Bill::all();
|
||||
Log::debug(sprintf('Now at start of WarnAboutBills() job for %s.', $this->date->format('D d M Y')));
|
||||
foreach (User::all() as $user) {
|
||||
$bills = $user->bills()->where('active', true)->get();
|
||||
$overdue = [];
|
||||
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
app('log')->debug(sprintf('Now checking bill #%d ("%s")', $bill->id, $bill->name));
|
||||
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);
|
||||
}
|
||||
app('log')->debug('Done with handle()');
|
||||
Log::debug('Done with handle()');
|
||||
|
||||
// clear cache:
|
||||
app('preferences')->mark();
|
||||
}
|
||||
|
||||
private function hasDateFields(Bill $bill): bool
|
||||
{
|
||||
if (false === $bill->active) {
|
||||
app('log')->debug('Bill is not active.');
|
||||
Log::debug('Bill is not active.');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (null === $bill->end_date && null === $bill->extension_date) {
|
||||
app('log')->debug('Bill has no date fields.');
|
||||
Log::debug('Bill has no date fields.');
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -115,7 +126,7 @@ class WarnAboutBills implements ShouldQueue
|
||||
}
|
||||
$diff = $this->getDiff($bill, $field);
|
||||
$list = config('firefly.bill_reminder_periods');
|
||||
app('log')->debug(sprintf('Difference in days for field "%s" ("%s") is %d day(s)', $field, $bill->{$field}->format('Y-m-d'), $diff));
|
||||
Log::debug(sprintf('Difference in days for field "%s" ("%s") is %d day(s)', $field, $bill->{$field}->format('Y-m-d'), $diff));
|
||||
if (in_array($diff, $list, true)) {
|
||||
return true;
|
||||
}
|
||||
@@ -128,13 +139,13 @@ class WarnAboutBills implements ShouldQueue
|
||||
$today = clone $this->date;
|
||||
$carbon = clone $bill->{$field};
|
||||
|
||||
return (int) $today->diffInDays($carbon);
|
||||
return (int)$today->diffInDays($carbon);
|
||||
}
|
||||
|
||||
private function sendWarning(Bill $bill, string $field): void
|
||||
{
|
||||
$diff = $this->getDiff($bill, $field);
|
||||
app('log')->debug('Will now send warning!');
|
||||
Log::debug('Will now send warning!');
|
||||
event(new WarnUserAboutBill($bill, $field, $diff));
|
||||
}
|
||||
|
||||
@@ -149,4 +160,49 @@ class WarnAboutBills implements ShouldQueue
|
||||
{
|
||||
$this->force = $force;
|
||||
}
|
||||
|
||||
private function getDates(Bill $bill): array
|
||||
{
|
||||
$start = clone $this->date;
|
||||
$start = Navigation::startOfPeriod($start, $bill->repeat_freq);
|
||||
$end = clone $start;
|
||||
$end = Navigation::endOfPeriod($end, $bill->repeat_freq);
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($bill->user);
|
||||
$enrichment->setStart($start);
|
||||
$enrichment->setEnd($end);
|
||||
$single = $enrichment->enrichSingle($bill);
|
||||
|
||||
return [
|
||||
'pay_dates' => $single->meta['pay_dates'] ?? [],
|
||||
'paid_dates' => $single->meta['paid_dates'] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
private function needsOverdueAlert(array $dates): bool
|
||||
{
|
||||
$count = count($dates['pay_dates']) - count($dates['paid_dates']);
|
||||
if (0 === $count || 0 === count($dates['pay_dates'])) {
|
||||
return false;
|
||||
}
|
||||
// the earliest date in the list of pay dates must be 48hrs or more ago.
|
||||
$earliest = new Carbon($dates['pay_dates'][0]);
|
||||
$earliest->startOfDay();
|
||||
Log::debug(sprintf('Earliest expected pay date is %s', $earliest->toAtomString()));
|
||||
$diff = $earliest->diffInDays($this->date);
|
||||
Log::debug(sprintf('Difference in days is %s', $diff));
|
||||
if ($diff < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
113
app/Notifications/User/SubscriptionsOverdueReminder.php
Normal file
113
app/Notifications/User/SubscriptionsOverdueReminder.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Notifications\User;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Notifications\ReturnsAvailableChannels;
|
||||
use FireflyIII\Notifications\ReturnsSettings;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\Pushover\PushoverMessage;
|
||||
|
||||
class SubscriptionsOverdueReminder extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(private array $overdue) {}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function toArray(User $notifiable): array
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function toMail(User $notifiable): MailMessage
|
||||
{
|
||||
// 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.subscriptions-overdue-warning', ['info' => $info, 'count' => $count])
|
||||
->subject($this->getSubject())
|
||||
;
|
||||
}
|
||||
|
||||
private function getSubject(): string
|
||||
{
|
||||
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
|
||||
{
|
||||
$settings = ReturnsSettings::getSettings('ntfy', 'user', $notifiable);
|
||||
$message = new Message();
|
||||
$message->topic($settings['ntfy_topic']);
|
||||
$message->title($this->getSubject());
|
||||
$message->body((string)trans('email.bill_warning_please_action'));
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function toPushover(User $notifiable): PushoverMessage
|
||||
{
|
||||
return PushoverMessage::create((string)trans('email.bill_warning_please_action'))
|
||||
->title($this->getSubject())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function toSlack(User $notifiable): SlackMessage
|
||||
{
|
||||
$url = route('bills.index');
|
||||
|
||||
return new SlackMessage()
|
||||
->warning()
|
||||
->attachment(static function ($attachment) use ($url): void {
|
||||
$attachment->title((string)trans('firefly.visit_bills'), $url);
|
||||
})
|
||||
->content($this->getSubject())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function via(User $notifiable): array
|
||||
{
|
||||
return ReturnsAvailableChannels::returnChannels('user', $notifiable);
|
||||
}
|
||||
}
|
@@ -27,6 +27,8 @@ 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\BudgetLimit\Created;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Deleted;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Updated;
|
||||
@@ -58,7 +60,6 @@ use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Events\UpdatedAccount;
|
||||
use FireflyIII\Events\UpdatedTransactionGroup;
|
||||
use FireflyIII\Events\UserChangedEmail;
|
||||
use FireflyIII\Events\WarnUserAboutBill;
|
||||
use FireflyIII\Handlers\Observer\AccountObserver;
|
||||
use FireflyIII\Handlers\Observer\AttachmentObserver;
|
||||
use FireflyIII\Handlers\Observer\AutoBudgetObserver;
|
||||
@@ -114,150 +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',
|
||||
],
|
||||
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',
|
||||
],
|
||||
];
|
||||
|
@@ -562,7 +562,13 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
|
||||
'foreign_currencies.decimal_places as foreign_currency_decimal_places',
|
||||
|
||||
// fields
|
||||
'transaction_journals.date', 'transaction_types.type', 'transaction_journals.transaction_currency_id', 'transactions.amount'])
|
||||
'transaction_journals.date',
|
||||
'transaction_types.type',
|
||||
'transaction_journals.transaction_currency_id',
|
||||
'transactions.amount',
|
||||
'transactions.native_amount as pc_amount',
|
||||
'transactions.foreign_amount',
|
||||
])
|
||||
->toArray()
|
||||
;
|
||||
|
||||
|
@@ -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 */
|
||||
|
@@ -52,4 +52,10 @@ class ALERepository implements ALERepositoryInterface
|
||||
|
||||
return $auditLogEntry;
|
||||
}
|
||||
|
||||
public function getForId(string $model, int $modelId): Collection
|
||||
{
|
||||
// all Models have an ID.
|
||||
return AuditLogEntry::where('auditable_id', $modelId)->where('auditable_type', $model)->get();
|
||||
}
|
||||
}
|
||||
|
@@ -46,5 +46,7 @@ interface ALERepositoryInterface
|
||||
{
|
||||
public function getForObject(Model $model): Collection;
|
||||
|
||||
public function getForId(string $model, int $modelId): Collection;
|
||||
|
||||
public function store(array $data): AuditLogEntry;
|
||||
}
|
||||
|
@@ -202,8 +202,11 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
'amount' => $amount,
|
||||
'destination_account_id' => $journal['destination_account_id'],
|
||||
'destination_account_name' => $journal['destination_account_name'],
|
||||
'destination_account_type' => $journal['destination_account_type'],
|
||||
'currency_id' => $journalCurrencyId,
|
||||
'source_account_id' => $journal['source_account_id'],
|
||||
'source_account_name' => $journal['source_account_name'],
|
||||
'source_account_type' => $journal['source_account_type'],
|
||||
'category_name' => $journal['category_name'],
|
||||
'description' => $journal['description'],
|
||||
'transaction_group_id' => $journal['transaction_group_id'],
|
||||
|
@@ -32,6 +32,7 @@ use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use JsonException;
|
||||
|
||||
use function Safe\json_encode;
|
||||
@@ -65,9 +66,9 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
try {
|
||||
$signature = $signatureGenerator->generate($this->message);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error('Did not send message because of a Firefly III Exception.');
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error('Did not send message because of a Firefly III Exception.');
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
$attempt = new WebhookAttempt();
|
||||
$attempt->webhookMessage()->associate($this->message);
|
||||
$attempt->status_code = 0;
|
||||
@@ -80,14 +81,14 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
return;
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('Trying to send webhook message #%d', $this->message->id));
|
||||
Log::debug(sprintf('Trying to send webhook message #%d', $this->message->id));
|
||||
|
||||
try {
|
||||
$json = json_encode($this->message->message, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
app('log')->error('Did not send message because of a JSON error.');
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error('Did not send message because of a JSON error.');
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
$attempt = new WebhookAttempt();
|
||||
$attempt->webhookMessage()->associate($this->message);
|
||||
$attempt->status_code = 0;
|
||||
@@ -115,9 +116,9 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
try {
|
||||
$res = $client->request('POST', $this->message->webhook->url, $options);
|
||||
} catch (ConnectException|RequestException $e) {
|
||||
app('log')->error('The webhook could NOT be submitted but Firefly III caught the error below.');
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error('The webhook could NOT be submitted but Firefly III caught the error below.');
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
|
||||
$logs = sprintf("%s\n%s", $e->getMessage(), $e->getTraceAsString());
|
||||
|
||||
@@ -130,9 +131,9 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
$attempt->status_code = 0;
|
||||
if (method_exists($e, 'hasResponse') && method_exists($e, 'getResponse')) {
|
||||
$attempt->status_code = $e->hasResponse() ? $e->getResponse()->getStatusCode() : 0;
|
||||
app('log')->error(sprintf('The status code of the error response is: %d', $attempt->status_code));
|
||||
Log::error(sprintf('The status code of the error response is: %d', $attempt->status_code));
|
||||
$body = (string) ($e->hasResponse() ? $e->getResponse()->getBody() : '');
|
||||
app('log')->error(sprintf('The body of the error response is: %s', $body));
|
||||
Log::error(sprintf('The body of the error response is: %s', $body));
|
||||
}
|
||||
$attempt->logs = $logs;
|
||||
$attempt->save();
|
||||
@@ -142,9 +143,9 @@ class StandardWebhookSender implements WebhookSenderInterface
|
||||
$this->message->sent = true;
|
||||
$this->message->save();
|
||||
|
||||
app('log')->debug(sprintf('Webhook message #%d was sent. Status code %d', $this->message->id, $res->getStatusCode()));
|
||||
app('log')->debug(sprintf('Webhook request body size: %d bytes', strlen($json)));
|
||||
app('log')->debug(sprintf('Response body: %s', $res->getBody()));
|
||||
Log::debug(sprintf('Webhook message #%d was sent. Status code %d', $this->message->id, $res->getStatusCode()));
|
||||
Log::debug(sprintf('Webhook request body size: %d bytes', strlen($json)));
|
||||
Log::debug(sprintf('Response body: %s', $res->getBody()));
|
||||
}
|
||||
|
||||
public function setMessage(WebhookMessage $message): void
|
||||
|
@@ -29,9 +29,9 @@ use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Singleton\PreferencesSingleton;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use NumberFormatter;
|
||||
|
||||
/**
|
||||
@@ -116,11 +116,28 @@ class Amount
|
||||
|
||||
public function convertToPrimary(?User $user = null): bool
|
||||
{
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
if (!$user instanceof User) {
|
||||
return true === Preferences::get('convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
$pref = $instance->getPreference('convert_to_primary_no_user');
|
||||
if (null === $pref) {
|
||||
$res = true === Preferences::get('convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
$instance->setPreference('convert_to_primary_no_user', $res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return $pref;
|
||||
}
|
||||
$key = sprintf('convert_to_primary_%d', $user->id);
|
||||
$pref = $instance->getPreference($key);
|
||||
if (null === $pref) {
|
||||
$res = true === Preferences::getForUser($user, 'convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
$instance->setPreference($key, $res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return true === Preferences::getForUser($user, 'convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
return $pref;
|
||||
}
|
||||
|
||||
public function getPrimaryCurrency(): TransactionCurrency
|
||||
|
@@ -28,6 +28,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Jobs\CreateAutoBudgetLimits;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class AutoBudgetCronjob
|
||||
@@ -42,22 +43,22 @@ class AutoBudgetCronjob extends AbstractCronjob
|
||||
$diff = Carbon::now()->getTimestamp() - $lastTime;
|
||||
$diffForHumans = today(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
if (0 === $lastTime) {
|
||||
app('log')->info('Auto budget cron-job has never fired before.');
|
||||
Log::info('Auto budget cron-job has never fired before.');
|
||||
}
|
||||
// less than half a day ago:
|
||||
if ($lastTime > 0 && $diff <= 43200) {
|
||||
app('log')->info(sprintf('It has been %s since the auto budget cron-job has fired.', $diffForHumans));
|
||||
Log::info(sprintf('It has been %s since the auto budget cron-job has fired.', $diffForHumans));
|
||||
if (false === $this->force) {
|
||||
app('log')->info('The auto budget cron-job will not fire now.');
|
||||
Log::info('The auto budget cron-job will not fire now.');
|
||||
$this->message = sprintf('It has been %s since the auto budget cron-job has fired. It will not fire now.', $diffForHumans);
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->info('Execution of the auto budget cron-job has been FORCED.');
|
||||
Log::info('Execution of the auto budget cron-job has been FORCED.');
|
||||
}
|
||||
|
||||
if ($lastTime > 0 && $diff > 43200) {
|
||||
app('log')->info(sprintf('It has been %s since the auto budget cron-job has fired. It will fire now!', $diffForHumans));
|
||||
Log::info(sprintf('It has been %s since the auto budget cron-job has fired. It will fire now!', $diffForHumans));
|
||||
}
|
||||
|
||||
$this->fireAutoBudget();
|
||||
@@ -66,7 +67,7 @@ class AutoBudgetCronjob extends AbstractCronjob
|
||||
|
||||
private function fireAutoBudget(): void
|
||||
{
|
||||
app('log')->info(sprintf('Will now fire auto budget cron job task for date "%s".', $this->date->format('Y-m-d')));
|
||||
Log::info(sprintf('Will now fire auto budget cron job task for date "%s".', $this->date->format('Y-m-d')));
|
||||
|
||||
/** @var CreateAutoBudgetLimits $job */
|
||||
$job = app(CreateAutoBudgetLimits::class, [$this->date]);
|
||||
@@ -80,6 +81,6 @@ class AutoBudgetCronjob extends AbstractCronjob
|
||||
$this->message = 'Auto-budget cron job fired successfully.';
|
||||
|
||||
FireflyConfig::set('last_ab_job', (int) $this->date->format('U'));
|
||||
app('log')->info('Done with auto budget cron job task.');
|
||||
Log::info('Done with auto budget cron job task.');
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,8 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Jobs\WarnAboutBills;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class BillWarningCronjob
|
||||
@@ -39,22 +41,22 @@ class BillWarningCronjob extends AbstractCronjob
|
||||
*/
|
||||
public function fire(): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
/** @var Configuration $config */
|
||||
$config = app('fireflyconfig')->get('last_bw_job', 0);
|
||||
$config = FireflyConfig::get('last_bw_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$diff = Carbon::now()->getTimestamp() - $lastTime;
|
||||
$diffForHumans = today(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
|
||||
if (0 === $lastTime) {
|
||||
app('log')->info('The bill notification cron-job has never fired before.');
|
||||
Log::info('The bill notification cron-job has never fired before.');
|
||||
}
|
||||
// less than half a day ago:
|
||||
if ($lastTime > 0 && $diff <= 43200) {
|
||||
app('log')->info(sprintf('It has been %s since the bill notification cron-job has fired.', $diffForHumans));
|
||||
Log::info(sprintf('It has been %s since the bill notification cron-job has fired.', $diffForHumans));
|
||||
if (false === $this->force) {
|
||||
app('log')->info('The cron-job will not fire now.');
|
||||
Log::info('The cron-job will not fire now.');
|
||||
$this->message = sprintf('It has been %s since the bill notification cron-job has fired. It will not fire now.', $diffForHumans);
|
||||
$this->jobFired = false;
|
||||
$this->jobErrored = false;
|
||||
@@ -63,11 +65,11 @@ class BillWarningCronjob extends AbstractCronjob
|
||||
return;
|
||||
}
|
||||
|
||||
app('log')->info('Execution of the bill notification cron-job has been FORCED.');
|
||||
Log::info('Execution of the bill notification cron-job has been FORCED.');
|
||||
}
|
||||
|
||||
if ($lastTime > 0 && $diff > 43200) {
|
||||
app('log')->info(sprintf('It has been %s since the bill notification cron-job has fired. It will fire now!', $diffForHumans));
|
||||
Log::info(sprintf('It has been %s since the bill notification cron-job has fired. It will fire now!', $diffForHumans));
|
||||
}
|
||||
|
||||
$this->fireWarnings();
|
||||
@@ -77,7 +79,7 @@ class BillWarningCronjob extends AbstractCronjob
|
||||
|
||||
private function fireWarnings(): void
|
||||
{
|
||||
app('log')->info(sprintf('Will now fire bill notification job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
|
||||
Log::info(sprintf('Will now fire bill notification job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
|
||||
|
||||
/** @var WarnAboutBills $job */
|
||||
$job = app(WarnAboutBills::class);
|
||||
@@ -91,8 +93,8 @@ class BillWarningCronjob extends AbstractCronjob
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Bill notification cron job fired successfully.';
|
||||
|
||||
app('fireflyconfig')->set('last_bw_job', (int) $this->date->format('U'));
|
||||
app('log')->info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
|
||||
app('log')->info('Done with bill notification cron job task.');
|
||||
FireflyConfig::set('last_bw_job', (int) $this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
|
||||
Log::info('Done with bill notification cron job task.');
|
||||
}
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@ namespace FireflyIII\Support\Cronjobs;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Jobs\DownloadExchangeRates;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ class ExchangeRatesCronjob extends AbstractCronjob
|
||||
public function fire(): void
|
||||
{
|
||||
/** @var Configuration $config */
|
||||
$config = app('fireflyconfig')->get('last_cer_job', 0);
|
||||
$config = FireflyConfig::get('last_cer_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$diff = Carbon::now()->getTimestamp() - $lastTime;
|
||||
$diffForHumans = today(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
@@ -80,7 +81,7 @@ class ExchangeRatesCronjob extends AbstractCronjob
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Exchange rates cron job fired successfully.';
|
||||
|
||||
app('fireflyconfig')->set('last_cer_job', (int) $this->date->format('U'));
|
||||
FireflyConfig::set('last_cer_job', (int) $this->date->format('U'));
|
||||
Log::info('Done with exchange rates job task.');
|
||||
}
|
||||
}
|
||||
|
@@ -41,7 +41,7 @@ class UpdateCheckCronjob extends AbstractCronjob
|
||||
Log::debug('Now in checkForUpdates()');
|
||||
|
||||
// should not check for updates:
|
||||
$permission = app('fireflyconfig')->get('permission_update_check', -1);
|
||||
$permission = FireflyConfig::get('permission_update_check', -1);
|
||||
$value = (int) $permission->data;
|
||||
if (1 !== $value) {
|
||||
Log::debug('Update check is not enabled.');
|
||||
|
97
app/Support/Cronjobs/WebhookCronjob.php
Normal file
97
app/Support/Cronjobs/WebhookCronjob.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* RecurringCronjob.php
|
||||
* Copyright (c) 2019 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\Support\Cronjobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\RequestedSendWebhookMessages;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class WebhookCronjob
|
||||
*/
|
||||
class WebhookCronjob extends AbstractCronjob
|
||||
{
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function fire(): void
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
/** @var Configuration $config */
|
||||
$config = FireflyConfig::get('last_webhook_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$diff = Carbon::now()->getTimestamp() - $lastTime;
|
||||
$diffForHumans = today(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
|
||||
if (0 === $lastTime) {
|
||||
Log::info('The webhook cron-job has never fired before.');
|
||||
}
|
||||
// less than ten minutes ago.
|
||||
if ($lastTime > 0 && $diff <= 600) {
|
||||
Log::info(sprintf('It has been %s since the webhook cron-job has fired.', $diffForHumans));
|
||||
if (false === $this->force) {
|
||||
Log::info('The cron-job will not fire now.');
|
||||
$this->message = sprintf('It has been %s since the webhook cron-job has fired. It will not fire now.', $diffForHumans);
|
||||
$this->jobFired = false;
|
||||
$this->jobErrored = false;
|
||||
$this->jobSucceeded = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log::info('Execution of the webhook cron-job has been FORCED.');
|
||||
}
|
||||
|
||||
if ($lastTime > 0 && $diff > 600) {
|
||||
Log::info(sprintf('It has been %s since the webhook cron-job has fired. It will fire now!', $diffForHumans));
|
||||
}
|
||||
|
||||
$this->fireWebhookmessages();
|
||||
|
||||
app('preferences')->mark();
|
||||
}
|
||||
|
||||
private function fireWebhookmessages(): void
|
||||
{
|
||||
Log::info(sprintf('Will now send webhook messages for date "%s".', $this->date->format('Y-m-d H:i:s')));
|
||||
|
||||
Log::debug('send event RequestedSendWebhookMessages through cron job.');
|
||||
event(new RequestedSendWebhookMessages());
|
||||
|
||||
// get stuff from job:
|
||||
$this->jobFired = true;
|
||||
$this->jobErrored = false;
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Send webhook messages cron job fired successfully.';
|
||||
|
||||
FireflyConfig::set('last_webhook_job', (int) $this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
|
||||
Log::info('Done with webhook cron job task.');
|
||||
}
|
||||
}
|
@@ -67,7 +67,7 @@ trait ChartGeneration
|
||||
/** @var AccountRepositoryInterface $accountRepos */
|
||||
$accountRepos = app(AccountRepositoryInterface::class);
|
||||
|
||||
$default = app('amount')->getPrimaryCurrency();
|
||||
$primary = app('amount')->getPrimaryCurrency();
|
||||
$chartData = [];
|
||||
|
||||
Log::debug(sprintf('Start of accountBalanceChart(list, %s, %s)', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
@@ -75,10 +75,10 @@ trait ChartGeneration
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Now at account #%d ("%s)', $account->id, $account->name));
|
||||
$currency = $accountRepos->getAccountCurrency($account) ?? $default;
|
||||
$usePrimary = $convertToPrimary && $default->id !== $currency->id;
|
||||
$currency = $accountRepos->getAccountCurrency($account) ?? $primary;
|
||||
$usePrimary = $convertToPrimary && $primary->id !== $currency->id;
|
||||
$field = $convertToPrimary ? 'pc_balance' : 'balance';
|
||||
$currency = $usePrimary ? $default : $currency;
|
||||
$currency = $usePrimary ? $primary : $currency;
|
||||
Log::debug(sprintf('Will use field %s', $field));
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
|
@@ -30,6 +30,7 @@ use FireflyIII\Support\Cronjobs\AutoBudgetCronjob;
|
||||
use FireflyIII\Support\Cronjobs\BillWarningCronjob;
|
||||
use FireflyIII\Support\Cronjobs\ExchangeRatesCronjob;
|
||||
use FireflyIII\Support\Cronjobs\RecurringCronjob;
|
||||
use FireflyIII\Support\Cronjobs\WebhookCronjob;
|
||||
|
||||
/**
|
||||
* Trait CronRunner
|
||||
@@ -62,6 +63,32 @@ trait CronRunner
|
||||
];
|
||||
}
|
||||
|
||||
protected function webhookCronJob(bool $force, Carbon $date): array
|
||||
{
|
||||
/** @var WebhookCronjob $webhook */
|
||||
$webhook = app(WebhookCronjob::class);
|
||||
$webhook->setForce($force);
|
||||
$webhook->setDate($date);
|
||||
|
||||
try {
|
||||
$webhook->fire();
|
||||
} catch (FireflyException $e) {
|
||||
return [
|
||||
'job_fired' => false,
|
||||
'job_succeeded' => false,
|
||||
'job_errored' => true,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'job_fired' => $webhook->jobFired,
|
||||
'job_succeeded' => $webhook->jobSucceeded,
|
||||
'job_errored' => $webhook->jobErrored,
|
||||
'message' => $webhook->message,
|
||||
];
|
||||
}
|
||||
|
||||
protected function exchangeRatesCronJob(bool $force, Carbon $date): array
|
||||
{
|
||||
/** @var ExchangeRatesCronjob $exchangeRates */
|
||||
|
@@ -35,6 +35,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Debug\Timer;
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
@@ -81,7 +82,7 @@ trait PeriodOverview
|
||||
Log::debug('Now in getAccountPeriodOverview()');
|
||||
Timer::start('account-period-total');
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
// properties for cache
|
||||
@@ -91,32 +92,29 @@ trait PeriodOverview
|
||||
$cache->addProperty('account-show-period-entries');
|
||||
$cache->addProperty($account->id);
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
// return $cache->get();
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
$spent = [];
|
||||
$earned = [];
|
||||
$transferredAway = [];
|
||||
$transferredIn = [];
|
||||
// $spent = [];
|
||||
// $earned = [];
|
||||
// $transferredAway = [];
|
||||
// $transferredIn = [];
|
||||
|
||||
// run a custom query because doing this with the collector is MEGA slow.
|
||||
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
|
||||
|
||||
// loop dates
|
||||
Log::debug(sprintf('Count of loops: %d', count($dates)));
|
||||
$loops = 0;
|
||||
// stop after 10 loops for memory reasons.
|
||||
foreach ($dates as $currentDate) {
|
||||
$title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
|
||||
if ($loops < 10) {
|
||||
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
$title = Navigation::periodShow($currentDate['start'], $currentDate['period']);
|
||||
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
@@ -138,7 +136,8 @@ trait PeriodOverview
|
||||
|
||||
private function filterTransactionsByType(TransactionTypeEnum $type, array $transactions, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
$result = [];
|
||||
$filtered = [];
|
||||
|
||||
/**
|
||||
* @var int $index
|
||||
@@ -146,38 +145,46 @@ trait PeriodOverview
|
||||
*/
|
||||
foreach ($transactions as $index => $item) {
|
||||
$date = Carbon::parse($item['date']);
|
||||
if ($item['type'] === $type->value && $date >= $start && $date <= $end) {
|
||||
$fits = $item['type'] === $type->value && $date >= $start && $date <= $end;
|
||||
if ($fits) {
|
||||
$result[] = $item;
|
||||
unset($transactions[$index]);
|
||||
}
|
||||
if (!$fits) {
|
||||
$filtered[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return [$transactions, $result];
|
||||
return [$filtered, $result];
|
||||
}
|
||||
|
||||
private function filterTransfers(string $direction, array $transactions, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
$result = [];
|
||||
$filtered = [];
|
||||
|
||||
/**
|
||||
* @var int $index
|
||||
* @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')) {
|
||||
if ('away' === $direction && -1 === bccomp((string)$item['amount'], '0')) {
|
||||
$result[] = $item;
|
||||
unset($transactions[$index]);
|
||||
|
||||
continue;
|
||||
}
|
||||
if ('in' === $direction && 1 === bccomp((string) $item['amount'], '0')) {
|
||||
if ('in' === $direction && 1 === bccomp((string)$item['amount'], '0')) {
|
||||
$result[] = $item;
|
||||
unset($transactions[$index]);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$filtered[] = $item;
|
||||
}
|
||||
|
||||
return [$transactions, $result];
|
||||
return [$filtered, $result];
|
||||
}
|
||||
|
||||
private function groupByCurrency(array $journals): array
|
||||
@@ -186,7 +193,7 @@ trait PeriodOverview
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
@@ -203,7 +210,7 @@ trait PeriodOverview
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
}
|
||||
if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId === $this->primaryCurrency->id) {
|
||||
$currencyId = (int) $foreignCurrencyId;
|
||||
$currencyId = (int)$foreignCurrencyId;
|
||||
$currencyCode = $journal['foreign_currency_code'];
|
||||
$currencyName = $journal['foreign_currency_name'];
|
||||
$currencySymbol = $journal['foreign_currency_symbol'];
|
||||
@@ -235,7 +242,7 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
// properties for entries with their amounts.
|
||||
@@ -251,7 +258,7 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
// collect all expenses in this period:
|
||||
@@ -281,7 +288,7 @@ trait PeriodOverview
|
||||
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
|
||||
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'transactions' => 0,
|
||||
@@ -327,7 +334,7 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getNoBudgetPeriodOverview(Carbon $start, Carbon $end): array
|
||||
{
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
@@ -342,7 +349,7 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
// get all expenses without a budget.
|
||||
@@ -353,7 +360,7 @@ trait PeriodOverview
|
||||
|
||||
foreach ($dates as $currentDate) {
|
||||
$set = $this->filterJournalsByDate($journals, $currentDate['start'], $currentDate['end']);
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
@@ -380,17 +387,17 @@ trait PeriodOverview
|
||||
protected function getNoCategoryPeriodOverview(Carbon $theDate): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d')));
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
$first = $this->journalRepos->firstNull();
|
||||
$start = null === $first ? new Carbon() : $first->date;
|
||||
$end = clone $theDate;
|
||||
$end = app('navigation')->endOfPeriod($end, $range);
|
||||
$end = Navigation::endOfPeriod($end, $range);
|
||||
|
||||
app('log')->debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d')));
|
||||
app('log')->debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d')));
|
||||
|
||||
// properties for cache
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
// collect all expenses in this period:
|
||||
@@ -422,7 +429,7 @@ trait PeriodOverview
|
||||
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
|
||||
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
@@ -445,7 +452,7 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getTagPeriodOverview(Tag $tag, Carbon $start, Carbon $end): array // period overview for tags.
|
||||
{
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
// properties for cache
|
||||
@@ -459,7 +466,7 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
// collect all expenses in this period:
|
||||
@@ -495,7 +502,7 @@ trait PeriodOverview
|
||||
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
|
||||
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'transactions' => 0,
|
||||
@@ -540,7 +547,7 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
$range = Navigation::getViewRange(true);
|
||||
$types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType));
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
@@ -555,7 +562,7 @@ trait PeriodOverview
|
||||
}
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
$spent = [];
|
||||
$earned = [];
|
||||
@@ -567,7 +574,7 @@ trait PeriodOverview
|
||||
$loops = 0;
|
||||
|
||||
foreach ($dates as $currentDate) {
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
|
||||
if ($loops < 10) {
|
||||
// set to correct array
|
||||
@@ -582,14 +589,14 @@ trait PeriodOverview
|
||||
}
|
||||
}
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferred),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred' => $this->groupByCurrency($transferred),
|
||||
];
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferred),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred' => $this->groupByCurrency($transferred),
|
||||
];
|
||||
++$loops;
|
||||
}
|
||||
|
||||
@@ -605,7 +612,7 @@ trait PeriodOverview
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if ($account->id === (int) $journal['source_account_id']) {
|
||||
if ($account->id === (int)$journal['source_account_id']) {
|
||||
$return[] = $journal;
|
||||
}
|
||||
}
|
||||
@@ -622,7 +629,7 @@ trait PeriodOverview
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if ($account->id === (int) $journal['destination_account_id']) {
|
||||
if ($account->id === (int)$journal['destination_account_id']) {
|
||||
$return[] = $journal;
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,7 @@ use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Location;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
@@ -41,6 +42,7 @@ use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Override;
|
||||
|
||||
@@ -51,37 +53,30 @@ use Override;
|
||||
*/
|
||||
class AccountEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private array $ids;
|
||||
private array $accountTypeIds;
|
||||
private array $accountTypes;
|
||||
private array $ids = [];
|
||||
private array $accountTypeIds = [];
|
||||
private array $accountTypes = [];
|
||||
private Collection $collection;
|
||||
private array $currencies;
|
||||
private array $locations;
|
||||
private array $meta;
|
||||
private array $currencies = [];
|
||||
private array $locations = [];
|
||||
private array $meta = [];
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private array $notes;
|
||||
private array $openingBalances;
|
||||
private array $notes = [];
|
||||
private array $openingBalances = [];
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private array $lastActivities;
|
||||
private ?Carbon $date = null;
|
||||
private bool $convertToPrimary = false;
|
||||
private array $balances = [];
|
||||
private array $lastActivities = [];
|
||||
private ?Carbon $date = null;
|
||||
private bool $convertToPrimary;
|
||||
private array $balances = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
|
||||
/**
|
||||
* TODO The account enricher must do conversion from and to the primary currency.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->ids = [];
|
||||
$this->openingBalances = [];
|
||||
$this->currencies = [];
|
||||
$this->accountTypeIds = [];
|
||||
$this->accountTypes = [];
|
||||
$this->meta = [];
|
||||
$this->notes = [];
|
||||
$this->lastActivities = [];
|
||||
$this->locations = [];
|
||||
$this->primaryCurrency = Amount::getPrimaryCurrency();
|
||||
$this->convertToPrimary = Amount::convertToPrimary();
|
||||
}
|
||||
@@ -113,6 +108,7 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
$this->collectLastActivities();
|
||||
$this->collectLocations();
|
||||
$this->collectOpeningBalances();
|
||||
$this->collectObjectGroups();
|
||||
$this->collectBalances();
|
||||
$this->appendCollectedData();
|
||||
|
||||
@@ -247,6 +243,9 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
'longitude' => null,
|
||||
'zoom_level' => null,
|
||||
],
|
||||
'object_group_id' => null,
|
||||
'object_group_order' => null,
|
||||
'object_group_title' => null,
|
||||
'opening_balance_date' => null,
|
||||
'opening_balance_amount' => null,
|
||||
'account_number' => null,
|
||||
@@ -254,6 +253,14 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
'last_activity' => $this->lastActivities[$id] ?? null,
|
||||
];
|
||||
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
|
||||
}
|
||||
|
||||
// if location, add location:
|
||||
if (array_key_exists($id, $this->locations)) {
|
||||
$meta['location'] = $this->locations[$id];
|
||||
@@ -347,6 +354,28 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
$this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
|
||||
}
|
||||
|
||||
private function collectObjectGroups(): void
|
||||
{
|
||||
$set = DB::table('object_groupables')
|
||||
->whereIn('object_groupable_id', $this->ids)
|
||||
->where('object_groupable_type', Account::class)
|
||||
->get(['object_groupable_id', 'object_group_id'])
|
||||
;
|
||||
|
||||
$ids = array_unique($set->pluck('object_group_id')->toArray());
|
||||
|
||||
foreach ($set as $entry) {
|
||||
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
|
||||
}
|
||||
|
||||
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
|
||||
foreach ($groups as $group) {
|
||||
$group['id'] = (int)$group['id'];
|
||||
$group['order'] = (int)$group['order'];
|
||||
$this->objectGroups[(int)$group['id']] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
public function setDate(?Carbon $date): void
|
||||
{
|
||||
$this->date = $date;
|
||||
|
@@ -43,7 +43,7 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private bool $convertToPrimary = false;
|
||||
private bool $convertToPrimary;
|
||||
private array $ids = [];
|
||||
private array $currencyIds = [];
|
||||
private array $currencies = [];
|
||||
@@ -157,7 +157,7 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
$currency = $this->currencies[$currencyId];
|
||||
$meta = [
|
||||
'currency' => $currency,
|
||||
'spent_in_budgets' => $this->spentInsideBudgets[$id] ?? [],
|
||||
'spent_in_budgets' => $this->spentInBudgets[$id] ?? [],
|
||||
'pc_spent_in_budgets' => $this->pcSpentInBudgets[$id] ?? [],
|
||||
'spent_outside_budgets' => $this->spentOutsideBudgets[$id] ?? [],
|
||||
'pc_spent_outside_budgets' => $this->pcSpentOutsideBudgets[$id] ?? [],
|
||||
|
@@ -8,6 +8,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Models\AutoBudget;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
|
||||
@@ -15,23 +16,26 @@ use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BudgetEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection;
|
||||
private bool $convertToPrimary = true;
|
||||
private bool $convertToPrimary;
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private array $ids = [];
|
||||
private array $notes = [];
|
||||
private array $autoBudgets = [];
|
||||
private array $currencies = [];
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $spent = [];
|
||||
private array $pcSpent = [];
|
||||
private array $ids = [];
|
||||
private array $notes = [];
|
||||
private array $autoBudgets = [];
|
||||
private array $currencies = [];
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $spent = [];
|
||||
private array $pcSpent = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -46,6 +50,8 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
$this->collectNotes();
|
||||
$this->collectAutoBudgets();
|
||||
$this->collectExpenses();
|
||||
$this->collectObjectGroups();
|
||||
|
||||
$this->appendCollectedData();
|
||||
|
||||
return $this->collection;
|
||||
@@ -98,12 +104,25 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
$this->collection = $this->collection->map(function (Budget $item) {
|
||||
$id = (int)$item->id;
|
||||
$meta = [
|
||||
'notes' => $this->notes[$id] ?? null,
|
||||
'currency' => $this->currencies[$id] ?? null,
|
||||
'auto_budget' => $this->autoBudgets[$id] ?? null,
|
||||
'spent' => $this->spent[$id] ?? null,
|
||||
'pc_spent' => $this->pcSpent[$id] ?? null,
|
||||
'object_group_id' => null,
|
||||
'object_group_order' => null,
|
||||
'object_group_title' => null,
|
||||
'notes' => $this->notes[$id] ?? null,
|
||||
'currency' => $this->currencies[$id] ?? null,
|
||||
'auto_budget' => $this->autoBudgets[$id] ?? null,
|
||||
'spent' => $this->spent[$id] ?? null,
|
||||
'pc_spent' => $this->pcSpent[$id] ?? null,
|
||||
];
|
||||
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
|
||||
}
|
||||
|
||||
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
@@ -154,4 +173,26 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
private function collectObjectGroups(): void
|
||||
{
|
||||
$set = DB::table('object_groupables')
|
||||
->whereIn('object_groupable_id', $this->ids)
|
||||
->where('object_groupable_type', Budget::class)
|
||||
->get(['object_groupable_id', 'object_group_id'])
|
||||
;
|
||||
|
||||
$ids = array_unique($set->pluck('object_group_id')->toArray());
|
||||
|
||||
foreach ($set as $entry) {
|
||||
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
|
||||
}
|
||||
|
||||
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
|
||||
foreach ($groups as $group) {
|
||||
$group['id'] = (int)$group['id'];
|
||||
$group['order'] = (int)$group['order'];
|
||||
$this->objectGroups[(int)$group['id']] = $group;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,6 +29,8 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
private Collection $budgets;
|
||||
private array $expenses = [];
|
||||
private array $pcExpenses = [];
|
||||
private array $currencyIds = [];
|
||||
private array $currencies = [];
|
||||
private bool $convertToPrimary = true;
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
|
||||
@@ -42,6 +44,7 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->collectIds();
|
||||
$this->collectCurrencies();
|
||||
$this->collectNotes();
|
||||
$this->collectBudgets();
|
||||
$this->appendCollectedData();
|
||||
@@ -71,14 +74,19 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
$this->start = $this->collection->min('start_date');
|
||||
$this->end = $this->collection->max('end_date');
|
||||
$this->start = $this->collection->min('start_date');
|
||||
$this->end = $this->collection->max('end_date');
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($this->collection as $limit) {
|
||||
$this->ids[] = (int)$limit->id;
|
||||
$id = (int)$limit->id;
|
||||
$this->ids[] = $id;
|
||||
if (0 !== (int)$limit->transaction_currency_id) {
|
||||
$this->currencyIds[$id] = (int)$limit->transaction_currency_id;
|
||||
}
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
$this->ids = array_unique($this->ids);
|
||||
$this->currencyIds = array_unique($this->currencyIds);
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
@@ -98,10 +106,15 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
{
|
||||
$this->collection = $this->collection->map(function (BudgetLimit $item) {
|
||||
$id = (int)$item->id;
|
||||
$currencyId = (int)$item->transaction_currency_id;
|
||||
if (0 === $currencyId) {
|
||||
$currencyId = $this->primaryCurrency->id;
|
||||
}
|
||||
$meta = [
|
||||
'notes' => $this->notes[$id] ?? null,
|
||||
'spent' => $this->expenses[$id] ?? [],
|
||||
'pc_spent' => $this->pcExpenses[$id] ?? [],
|
||||
'currency' => $this->currencies[$currencyId],
|
||||
];
|
||||
$item->meta = $meta;
|
||||
|
||||
@@ -133,4 +146,13 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function collectCurrencies(): void
|
||||
{
|
||||
$this->currencies[$this->primaryCurrency->id] = $this->primaryCurrency;
|
||||
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->whereNot('id', $this->primaryCurrency->id)->get();
|
||||
foreach ($currencies as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -25,15 +25,15 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private Collection $collection;
|
||||
private array $ids;
|
||||
private array $currencyIds = [];
|
||||
private array $currencies = [];
|
||||
private array $accountIds = [];
|
||||
private array $accountCurrencies = [];
|
||||
private array $notes = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $ids = [];
|
||||
private array $currencyIds = [];
|
||||
private array $currencies = [];
|
||||
private array $accountIds = [];
|
||||
// private array $accountCurrencies = [];
|
||||
private array $notes = [];
|
||||
private array $mappedObjects = [];
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private array $amounts = [];
|
||||
private array $amounts = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -116,12 +116,12 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
|
||||
/** @var AccountMeta $item */
|
||||
foreach ($set as $item) {
|
||||
$accountId = (int)$item->account_id;
|
||||
$currencyId = (int)$item->data;
|
||||
$accountId = (int)$item->account_id;
|
||||
$currencyId = (int)$item->data;
|
||||
if (!array_key_exists($currencyId, $this->currencies)) {
|
||||
$this->currencies[$currencyId] = TransactionCurrency::find($currencyId);
|
||||
}
|
||||
$this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
|
||||
// $this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
|
||||
}
|
||||
|
||||
// get account info.
|
||||
|
@@ -25,7 +25,7 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
|
||||
private array $journalIds = [];
|
||||
private array $groupIds = [];
|
||||
private array $accountIds = [];
|
||||
private array $piggybankIds = [];
|
||||
private array $piggyBankIds = [];
|
||||
private array $accountCurrencies = [];
|
||||
private array $currencies = [];
|
||||
// private bool $convertToPrimary = false;
|
||||
@@ -72,7 +72,7 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
|
||||
foreach ($this->collection as $event) {
|
||||
$this->ids[] = (int)$event->id;
|
||||
$this->journalIds[(int)$event->id] = (int)$event->transaction_journal_id;
|
||||
$this->piggybankIds[(int)$event->id] = (int)$event->piggy_bank_id;
|
||||
$this->piggyBankIds[(int)$event->id] = (int)$event->piggy_bank_id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
// collect groups with journal info.
|
||||
@@ -84,7 +84,7 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
|
||||
}
|
||||
|
||||
// collect account info.
|
||||
$set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->piggybankIds)->get(['piggy_bank_id', 'account_id']);
|
||||
$set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->piggyBankIds)->get(['piggy_bank_id', 'account_id']);
|
||||
foreach ($set as $item) {
|
||||
$id = (int)$item->piggy_bank_id;
|
||||
if (!array_key_exists($id, $this->accountIds)) {
|
||||
@@ -93,7 +93,6 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
|
||||
}
|
||||
|
||||
// get account currency preference for ALL.
|
||||
// TODO This method does a find in a loop.
|
||||
$set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
|
||||
|
||||
/** @var AccountMeta $item */
|
||||
|
@@ -173,7 +173,7 @@ class RecurringEnrichment implements EnrichmentInterface
|
||||
|
||||
$this->transactions[$id][$transactionId] = [
|
||||
'id' => (string)$transactionId,
|
||||
'recurrence_id' => $id,
|
||||
// 'recurrence_id' => $id,
|
||||
'transaction_currency_id' => (int)$transaction->transaction_currency_id,
|
||||
'foreign_currency_id' => null === $transaction->foreign_currency_id ? null : (int)$transaction->foreign_currency_id,
|
||||
'source_id' => (int)$transaction->source_id,
|
||||
@@ -309,7 +309,7 @@ class RecurringEnrichment implements EnrichmentInterface
|
||||
if (true === $this->convertToPrimary && $currencyId !== (int)$this->primaryCurrency->id) {
|
||||
$pcAmount = $converter->convert($this->currencies[$currencyId], $this->primaryCurrency, today(), $transaction['amount']);
|
||||
}
|
||||
if (null !== $transaction['foreign_amount']) {
|
||||
if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
|
||||
$foreignCurrencyId = $transaction['foreign_currency_id'];
|
||||
if ($foreignCurrencyId !== $this->primaryCurrency->id) {
|
||||
$pcForeignAmount = $converter->convert($this->currencies[$foreignCurrencyId], $this->primaryCurrency, today(), $transaction['foreign_amount']);
|
||||
|
@@ -27,15 +27,15 @@ class SubscriptionEnrichment implements EnrichmentInterface
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private Collection $collection;
|
||||
private bool $convertToPrimary = false;
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $subscriptionIds = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $paidDates = [];
|
||||
private array $notes = [];
|
||||
private array $payDates = [];
|
||||
private bool $convertToPrimary;
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $subscriptionIds = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $paidDates = [];
|
||||
private array $notes = [];
|
||||
private array $payDates = [];
|
||||
private TransactionCurrency $primaryCurrency;
|
||||
private BillDateCalculator $calculator;
|
||||
|
||||
@@ -56,6 +56,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
|
||||
$this->collectPaidDates();
|
||||
$this->collectPayDates();
|
||||
|
||||
|
||||
// TODO clean me up.
|
||||
|
||||
$notes = $this->notes;
|
||||
|
@@ -45,26 +45,20 @@ use Override;
|
||||
|
||||
class TransactionGroupEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private array $attachmentCount;
|
||||
private array $attachmentCount = [];
|
||||
private Collection $collection;
|
||||
private readonly array $dateFields;
|
||||
private array $journalIds;
|
||||
private array $locations;
|
||||
private array $metaData; // @phpstan-ignore-line
|
||||
private array $notes; // @phpstan-ignore-line
|
||||
private array $tags;
|
||||
private array $journalIds = [];
|
||||
private array $locations = [];
|
||||
private array $metaData = [];
|
||||
private array $notes = [];
|
||||
private array $tags = [];
|
||||
private User $user;
|
||||
private readonly TransactionCurrency $primaryCurrency;
|
||||
private UserGroup $userGroup;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->notes = [];
|
||||
$this->journalIds = [];
|
||||
$this->tags = [];
|
||||
$this->metaData = [];
|
||||
$this->locations = [];
|
||||
$this->attachmentCount = [];
|
||||
$this->dateFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date'];
|
||||
$this->primaryCurrency = Amount::getPrimaryCurrency();
|
||||
}
|
||||
|
@@ -110,8 +110,8 @@ class BillDateCalculator
|
||||
$currentStart = clone $nextExpectedMatch;
|
||||
|
||||
++$loop;
|
||||
if ($loop > 12) {
|
||||
Log::debug('Loop is more than 12, so we break.');
|
||||
if ($loop > 31) {
|
||||
Log::debug('Loop is more than 31, so we break.');
|
||||
|
||||
break;
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ namespace FireflyIII\Support;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Support\Singleton\PreferencesSingleton;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Contracts\Encryption\EncryptException;
|
||||
@@ -283,6 +284,12 @@ class Preferences
|
||||
*/
|
||||
public function lastActivity(): string
|
||||
{
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
$pref = $instance->getPreference('last_activity');
|
||||
if (null !== $pref) {
|
||||
// Log::debug(sprintf('Found last activity in singleton: %s', $pref));
|
||||
return $pref;
|
||||
}
|
||||
$lastActivity = microtime();
|
||||
$preference = $this->get('lastActivity', microtime());
|
||||
|
||||
@@ -292,13 +299,17 @@ class Preferences
|
||||
if (is_array($lastActivity)) {
|
||||
$lastActivity = implode(',', $lastActivity);
|
||||
}
|
||||
$setting = hash('sha256', (string) $lastActivity);
|
||||
$instance->setPreference('last_activity', $setting);
|
||||
|
||||
return hash('sha256', (string) $lastActivity);
|
||||
return $setting;
|
||||
}
|
||||
|
||||
public function mark(): void
|
||||
{
|
||||
$this->set('lastActivity', microtime());
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
$instance->setPreference('last_activity', microtime());
|
||||
Session::forget('first');
|
||||
}
|
||||
|
||||
|
41
app/Support/Singleton/PreferencesSingleton.php
Normal file
41
app/Support/Singleton/PreferencesSingleton.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Singleton;
|
||||
|
||||
class PreferencesSingleton
|
||||
{
|
||||
private static ?PreferencesSingleton $instance = null;
|
||||
|
||||
private array $preferences = [];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
// Private constructor to prevent direct instantiation.
|
||||
}
|
||||
|
||||
public static function getInstance(): self
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function resetPreferences(): void
|
||||
{
|
||||
$this->preferences = [];
|
||||
}
|
||||
|
||||
public function setPreference(string $key, mixed $value): void
|
||||
{
|
||||
$this->preferences[$key] = $value;
|
||||
}
|
||||
|
||||
public function getPreference(string $key): mixed
|
||||
{
|
||||
return $this->preferences[$key] ?? null;
|
||||
}
|
||||
}
|
@@ -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
|
||||
*/
|
||||
|
@@ -108,7 +108,9 @@ class AccountTransformer extends AbstractTransformer
|
||||
'type' => strtolower($accountType),
|
||||
'account_role' => $accountRole,
|
||||
|
||||
// TODO object group
|
||||
'object_group_id' => $account->meta['object_group_id'],
|
||||
'object_group_order' => $account->meta['object_group_order'],
|
||||
'object_group_title' => $account->meta['object_group_title'],
|
||||
|
||||
// currency information, structured for 6.3.0.
|
||||
'object_has_currency_setting' => $hasCurrencySettings,
|
||||
|
@@ -55,7 +55,6 @@ class AttachmentTransformer extends AbstractTransformer
|
||||
'updated_at' => $attachment->updated_at->toAtomString(),
|
||||
'attachable_id' => (string) $attachment->attachable_id,
|
||||
'attachable_type' => str_replace('FireflyIII\Models\\', '', $attachment->attachable_type),
|
||||
'md5' => $attachment->md5,
|
||||
'hash' => $attachment->md5,
|
||||
'filename' => $attachment->filename,
|
||||
'download_url' => route('api.v1.attachments.download', [$attachment->id]),
|
||||
|
@@ -93,7 +93,6 @@ class BillTransformer extends AbstractTransformer
|
||||
'object_group_order' => $bill->meta['object_group_order'],
|
||||
'object_group_title' => $bill->meta['object_group_title'],
|
||||
|
||||
|
||||
'paid_dates' => $bill->meta['paid_dates'],
|
||||
'pay_dates' => $bill->meta['pay_dates'],
|
||||
'next_expected_match' => $bill->meta['nem']?->toAtomString(),
|
||||
|
@@ -64,10 +64,7 @@ class BudgetLimitTransformer extends AbstractTransformer
|
||||
public function transform(BudgetLimit $budgetLimit): array
|
||||
{
|
||||
|
||||
$currency = $budgetLimit->transactionCurrency;
|
||||
if (null === $currency) {
|
||||
$currency = $this->primaryCurrency;
|
||||
}
|
||||
$currency = $budgetLimit->meta['currency'];
|
||||
$amount = Steam::bcround($budgetLimit->amount, $currency->decimal_places);
|
||||
$pcAmount = null;
|
||||
if ($this->convertToPrimary && $currency->id === $this->primaryCurrency->id) {
|
||||
|
@@ -86,8 +86,9 @@ class BudgetTransformer extends AbstractTransformer
|
||||
'notes' => $budget->meta['notes'],
|
||||
'auto_budget_type' => $abType,
|
||||
'auto_budget_period' => $abPeriod,
|
||||
|
||||
// TODO object group
|
||||
'object_group_id' => $budget->meta['object_group_id'],
|
||||
'object_group_order' => $budget->meta['object_group_order'],
|
||||
'object_group_title' => $budget->meta['object_group_title'],
|
||||
|
||||
// new currency settings.
|
||||
'object_has_currency_setting' => null !== $budget->meta['currency'],
|
||||
@@ -105,8 +106,8 @@ class BudgetTransformer extends AbstractTransformer
|
||||
|
||||
'auto_budget_amount' => $abAmount,
|
||||
'pc_auto_budget_amount' => $abPrimary,
|
||||
'spent' => $this->beautify($budget->meta['spent']),
|
||||
'pc_spent' => $this->beautify($budget->meta['pc_spent']),
|
||||
'spent' => null === $budget->meta['spent'] ? null : $this->beautify($budget->meta['spent']),
|
||||
'pc_spent' => null === $budget->meta['pc_spent'] ? null : $this->beautify($budget->meta['pc_spent']),
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'self',
|
||||
|
@@ -67,7 +67,7 @@ class RecurrenceTransformer extends AbstractTransformer
|
||||
'nr_of_repetitions' => $reps,
|
||||
'notes' => $recurrence->meta['notes'],
|
||||
'repetitions' => $recurrence->meta['repetitions'],
|
||||
'new_transactions' => $recurrence->meta['transactions'],
|
||||
'transactions' => $recurrence->meta['transactions'],
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'self',
|
||||
|
47
changelog.md
47
changelog.md
@@ -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
|
||||
|
74
composer.lock
generated
74
composer.lock
generated
@@ -939,16 +939,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filp/whoops",
|
||||
"version": "2.18.3",
|
||||
"version": "2.18.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filp/whoops.git",
|
||||
"reference": "59a123a3d459c5a23055802237cb317f609867e5"
|
||||
"reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filp/whoops/zipball/59a123a3d459c5a23055802237cb317f609867e5",
|
||||
"reference": "59a123a3d459c5a23055802237cb317f609867e5",
|
||||
"url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d",
|
||||
"reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -998,7 +998,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/filp/whoops/issues",
|
||||
"source": "https://github.com/filp/whoops/tree/2.18.3"
|
||||
"source": "https://github.com/filp/whoops/tree/2.18.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1006,7 +1006,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-16T00:02:10+00:00"
|
||||
"time": "2025-08-08T12:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
@@ -1879,16 +1879,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v12.21.0",
|
||||
"version": "v12.22.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b"
|
||||
"reference": "d33ee45184126f32f593d4b809a846ed88a1dc43"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
|
||||
"reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/d33ee45184126f32f593d4b809a846ed88a1dc43",
|
||||
"reference": "d33ee45184126f32f593d4b809a846ed88a1dc43",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2090,7 +2090,7 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2025-07-22T15:41:55+00:00"
|
||||
"time": "2025-08-08T13:58:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/passport",
|
||||
@@ -10347,16 +10347,16 @@
|
||||
},
|
||||
{
|
||||
"name": "driftingly/rector-laravel",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/driftingly/rector-laravel.git",
|
||||
"reference": "ac61de4f267c23249d175d7fc9149fd01528567d"
|
||||
"reference": "5be95811801fc06126dd844beaeb6a41721ba3d3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/ac61de4f267c23249d175d7fc9149fd01528567d",
|
||||
"reference": "ac61de4f267c23249d175d7fc9149fd01528567d",
|
||||
"url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/5be95811801fc06126dd844beaeb6a41721ba3d3",
|
||||
"reference": "5be95811801fc06126dd844beaeb6a41721ba3d3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10376,9 +10376,9 @@
|
||||
"description": "Rector upgrades rules for Laravel Framework",
|
||||
"support": {
|
||||
"issues": "https://github.com/driftingly/rector-laravel/issues",
|
||||
"source": "https://github.com/driftingly/rector-laravel/tree/2.0.5"
|
||||
"source": "https://github.com/driftingly/rector-laravel/tree/2.0.6"
|
||||
},
|
||||
"time": "2025-05-14T17:30:41+00:00"
|
||||
"time": "2025-08-08T22:10:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fakerphp/faker",
|
||||
@@ -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",
|
||||
|
@@ -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-07',
|
||||
'build_time' => 1754540556,
|
||||
'version' => 'develop/2025-08-09',
|
||||
'build_time' => 1754763222,
|
||||
'api_version' => '2.1.0', // field is no longer used.
|
||||
'db_version' => 26,
|
||||
|
||||
|
42
package-lock.json
generated
42
package-lock.json
generated
@@ -3148,9 +3148,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
|
||||
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
|
||||
"version": "24.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz",
|
||||
"integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -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"
|
||||
},
|
||||
@@ -4486,9 +4486,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001731",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
|
||||
"integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==",
|
||||
"version": "1.0.30001733",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz",
|
||||
"integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5700,9 +5700,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.198",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.198.tgz",
|
||||
"integrity": "sha512-G5COfnp3w+ydVu80yprgWSfmfQaYRh9DOxfhAxstLyetKaLyl55QrNjx8C38Pc/C+RaDmb1M0Lk8wPEMQ+bGgQ==",
|
||||
"version": "1.5.199",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz",
|
||||
"integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -7838,9 +7838,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/launch-editor": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.0.tgz",
|
||||
"integrity": "sha512-R/PIF14L6e2eHkhvQPu7jDRCr0msfCYCxbYiLgkkAGi0dVPWuM+RrsPu0a5dpuNe0KWGL3jpAkOlv53xGfPheQ==",
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz",
|
||||
"integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11524,9 +11524,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz",
|
||||
"integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.1.tgz",
|
||||
"integrity": "sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11534,7 +11534,7 @@
|
||||
"fdir": "^6.4.6",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.40.0",
|
||||
"rollup": "^4.43.0",
|
||||
"tinyglobby": "^0.2.14"
|
||||
},
|
||||
"bin": {
|
||||
|
@@ -138,6 +138,14 @@ return [
|
||||
'new_journals_subject' => 'Firefly III has created a new transaction|Firefly III has created :count new transactions',
|
||||
'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.
|
||||
'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',
|
||||
|
@@ -1864,6 +1864,7 @@ return [
|
||||
'remove_budgeted_amount' => 'Remove budgeted amount in :currency',
|
||||
|
||||
// bills:
|
||||
'left_to_pay_active_bills' => 'active, expected and not yet paid subscriptions',
|
||||
'left_to_pay_lc' => 'left to pay',
|
||||
'less_than_expected' => 'less than expected',
|
||||
'more_than_expected' => 'more than expected',
|
||||
@@ -1874,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',
|
||||
|
@@ -56,8 +56,12 @@
|
||||
<div class="input-group-text"> <em class="fa-solid fa-user"></em> </div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="input-group mb-3"> <input type="password" name="password" class="form-control" placeholder="{{ trans('form.password') }}" @if(true===$IS_DEMO_SITE)value="{{ $DEMO_PASSWORD }}"@endif autocomplete="current-password">
|
||||
<div class="input-group-text"> <em class="fa-solid fa-lock"></em> </div>
|
||||
<div class="input-group mb-3">
|
||||
<input type="password" id="password" name="password" class="form-control" placeholder="{{ trans('form.password') }}" @if(true===$IS_DEMO_SITE)value="{{ $DEMO_PASSWORD }}"@endif autocomplete="current-password">
|
||||
<div class="input-group-text">
|
||||
<em class="fa-solid fa-lock"></em>
|
||||
<i class="fa-solid fa-eye-slash fa-eye" id="togglePassword"></i>
|
||||
</div>
|
||||
</div> <!--begin::Row-->
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
@@ -80,3 +84,21 @@
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
@section('scripts')
|
||||
<script nonce="{{ $JS_NONCE }}">
|
||||
const togglePassword = document.querySelector('#togglePassword');
|
||||
const password = document.querySelector('#password');
|
||||
togglePassword.addEventListener('click', () => {
|
||||
const type = password.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||
if('text' === type) {
|
||||
togglePassword.classList.add('fa-eye');
|
||||
togglePassword.classList.remove('fa-eye-slash');
|
||||
}
|
||||
if('password' === type) {
|
||||
togglePassword.classList.add('fa-eye-slash');
|
||||
togglePassword.classList.remove('fa-eye');
|
||||
}
|
||||
password.setAttribute('type', type);
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
@@ -158,8 +158,8 @@
|
||||
<td style="width:33%;">{{ 'amount'|_ }}</td>
|
||||
<td>
|
||||
{{ formatAmountBySymbol(limit.amount, limit.transactionCurrency.symbol, limit.transactionCurrency.decimal_places) }}
|
||||
{% if convertToPrimary and 0 != limit.pc_amount %}
|
||||
({{ formatAmountBySymbol(limit.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
|
||||
{% if convertToPrimary and null != limit.native_amount %}
|
||||
({{ formatAmountBySymbol(limit.native_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@@ -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
|
@@ -189,6 +189,21 @@
|
||||
<td class="hidden-sm hidden-xs"> </td><!-- repeats -->
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if '0' != sum.total_left_to_pay %}
|
||||
<tr>
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- handle -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- buttons -->
|
||||
<td colspan="2" style="text-align: right;"> <!-- title -->
|
||||
<small>{{ 'sum'|_ }} ({{ sum.currency_name }}) ({{ 'left_to_pay_active_bills'|_ }})</small>
|
||||
</td>
|
||||
<td style="text-align: right;"> <!-- amount -->
|
||||
{{ formatAmountBySymbol(sum.total_left_to_pay, sum.currency_symbol, sum.currency_decimal_places) }}
|
||||
</td>
|
||||
<td> </td> <!-- paid in period -->
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- next expected match -->
|
||||
<td class="hidden-sm hidden-xs"> </td><!-- repeats -->
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if '0' != sum.per_period %}
|
||||
<tr>
|
||||
<td class="hidden-sm hidden-xs"> </td> <!-- handle -->
|
||||
|
@@ -346,9 +346,11 @@
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li><a href="{{ route('transactions.edit', [group.id]) }}"><span
|
||||
class="fa fa-fw fa-pencil"></span> {{ 'edit'|_ }}</a></li>
|
||||
{% if transaction.transaction_type_type != 'Reconciliation' and transaction.transaction_type_type != 'Opening balance' and transaction.transaction_type_type != 'Liability credit' %}
|
||||
{% if transaction.transaction_type_type != 'Opening balance' and transaction.transaction_type_type != 'Liability credit' %}
|
||||
<li><a href="{{ route('transactions.delete', [group.id]) }}"><span
|
||||
class="fa fa-fw fa-trash"></span> {{ 'delete'|_ }}</a></li>
|
||||
{% endif %}
|
||||
{% if transaction.transaction_type_type != 'Reconciliation' and transaction.transaction_type_type != 'Opening balance' and transaction.transaction_type_type != 'Liability credit' %}
|
||||
<li><a href="#" data-id="{{ group.id }}" class="clone-transaction"><span
|
||||
class="fa fa-copy fa-fw"></span> {{ 'clone'|_ }}</a></li>
|
||||
<li><a href="#" data-id="{{ group.id }}" class="clone-transaction-and-edit"><span
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -6,12 +6,15 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<p>
|
||||
<div class="btn-group">
|
||||
<a href="{{ route('rule-groups.create') }}" id="new_rule_group" class="btn btn-success">{{ 'new_rule_group'|_ }}</a>
|
||||
</p>
|
||||
<a href="{{ route('rules.create') }}" class="btn btn-success new_rule">{{ 'new_rule'|_ }}</a>
|
||||
</div>
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% if ruleGroups|length == 1 and ruleGroups[0].rules.count() == 0 %}
|
||||
{% include 'partials.empty' with {objectType: 'default', type: 'rules',route: route('rules.create')} %}
|
||||
{# make FF ignore demo for now. #}
|
||||
@@ -62,6 +65,10 @@
|
||||
<p>
|
||||
<em>{{ ruleGroup.description }}</em>
|
||||
</p>
|
||||
<p>
|
||||
<a href="{{ route('rules.create', ruleGroup.id) }}"
|
||||
class="btn btn-success new_rule">{{ 'new_rule'|_ }}</a>
|
||||
</p>
|
||||
|
||||
{% if ruleGroup.rules.count() > 0 %}
|
||||
<table class="table table-hover table-striped group-rules">
|
||||
|
@@ -64,7 +64,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:40%;">{{ trans('list.type') }}</td>
|
||||
<td>{{ first.transactiontype.type|_ }}</td>
|
||||
<td>{{ first.transaction_type_type|_ }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ trans('list.description') }}</td>
|
||||
@@ -113,7 +113,7 @@
|
||||
<div class="box-body no-padding">
|
||||
<table class="table table-hover">
|
||||
<tbody>
|
||||
{% if first.transactiontype.type != 'Withdrawal' or splits == 1 %}
|
||||
{% if first.transaction_type_type != 'Withdrawal' or splits == 1 %}
|
||||
<tr>
|
||||
<td style="width:40%;">
|
||||
{{ trans_choice('firefly.source_accounts', accounts['source']|length ) }}
|
||||
@@ -134,7 +134,7 @@
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if first.transactiontype.type != 'Deposit' or splits == 1 %}
|
||||
{% if first.transaction_type_type != 'Deposit' or splits == 1 %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ trans_choice('firefly.destination_accounts', accounts['destination']|length ) }}
|
||||
@@ -159,17 +159,17 @@
|
||||
<td style="width:30%;">{{ 'total_amount'|_ }}</td>
|
||||
<td>
|
||||
{% for amount in amounts %}
|
||||
{% if first.transactiontype.type == 'Withdrawal' %}
|
||||
{% if first.transaction_type_type == 'Withdrawal' %}
|
||||
{{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %}
|
||||
{% elseif first.transactiontype.type == 'Deposit' %}
|
||||
{% elseif first.transaction_type_type == 'Deposit' %}
|
||||
{{ formatAmountBySymbol(amount.amount,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %}
|
||||
{% elseif first.transactiontype.type == 'Transfer' %}
|
||||
{% elseif first.transaction_type_type == 'Transfer' %}
|
||||
<span class="text-info money-transfer">
|
||||
{{ formatAmountBySymbol(amount.amount, amount.symbol, amount.decimal_places, false) }}{% if loop.index0 != amounts|length -1 %}, {% endif %}
|
||||
</span>
|
||||
{% elseif first.transactiontype.type == 'Opening balance' %}
|
||||
{% elseif first.transaction_type_type == 'Opening balance' %}
|
||||
{# Opening balance stored amount is always negative: find out which way the money goes #}
|
||||
{% if groupArray.transactions[0].source_type == 'Initial balance account' %}
|
||||
{% if groupArray.transactions[0].source_account_type == 'Initial balance account' %}
|
||||
{{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }}
|
||||
{% else %}
|
||||
{{ formatAmountBySymbol(amount.amount,amount.symbol, amount.decimal_places) }}
|
||||
@@ -202,7 +202,7 @@
|
||||
{% set boxSize = 4 %}
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
{% for index,journal in groupArray.transactions %}
|
||||
{% for index,journal in selectedGroup.transactions %}
|
||||
<div class="col-lg-{{ boxSize }}">
|
||||
<div class="box">
|
||||
<div class="box-header with-border">
|
||||
@@ -289,48 +289,74 @@
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
|
||||
<!-- type is: "{{ first.transactiontype.type }}" -->
|
||||
{% if 'Cash account' == journal.source_type %}
|
||||
<!-- type is: "{{ first.transaction_type_type }}" -->
|
||||
{% if 'Cash account' == journal.source_account_type %}
|
||||
<span class="text-success">({{ 'cash'|_ }})</span>
|
||||
{% else %}
|
||||
<a href="{{ route('accounts.show', journal.source_id) }}"
|
||||
title="{{ journal.source_iban|default(journal.source_name) }}">{{ journal.source_name }}</a> →
|
||||
<a href="{{ route('accounts.show', journal.source_account_id) }}"
|
||||
title="{{ journal.source_iban|default(journal.source_account_name) }}">{{ journal.source_account_name }}</a> →
|
||||
{% endif %}
|
||||
|
||||
{% if first.transactiontype.type == 'Withdrawal' %}
|
||||
{% if first.transaction_type_type == 'Withdrawal' %}
|
||||
{{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_decimal_places) }}
|
||||
{% elseif first.transactiontype.type == 'Deposit' %}
|
||||
{% elseif first.transaction_type_type == 'Deposit' %}
|
||||
{{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_decimal_places) }}
|
||||
{% elseif first.transactiontype.type == 'Transfer' or first.transactiontype.type == 'Opening balance' %}
|
||||
{% elseif first.transaction_type_type == 'Transfer' or first.transaction_type_type == 'Opening balance' %}
|
||||
<span class="text-info money-transfer">
|
||||
{{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_decimal_places, false) }}
|
||||
</span>
|
||||
{% elseif first.transactiontype.type == 'Liability credit' %}
|
||||
{% elseif first.transaction_type_type == 'Liability credit' %}
|
||||
<span class="text-info money-transfer">
|
||||
{{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_decimal_places, false) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<!-- do primary currency amount -->
|
||||
{% if null != journal.pc_amount and primaryCurrency.id != journal.currency_id %}
|
||||
{% if first.transaction_type_type == 'Withdrawal' %}
|
||||
({{ formatAmountBySymbol(journal.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
|
||||
{% elseif first.transaction_type_type == 'Deposit' %}
|
||||
({{ formatAmountBySymbol(journal.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
|
||||
{% elseif first.transaction_type_type == 'Transfer' %}
|
||||
<span class="text-info money-transfer">
|
||||
({{ formatAmountBySymbol(journal.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places, false) }})
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<!-- do foreign amount -->
|
||||
{% if null != journal.foreign_amount %}
|
||||
{% if first.transactiontype.type == 'Withdrawal' %}
|
||||
{% if first.transaction_type_type == 'Withdrawal' %}
|
||||
({{ formatAmountBySymbol(journal.foreign_amount*-1, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places) }})
|
||||
{% elseif first.transactiontype.type == 'Deposit' %}
|
||||
{% elseif first.transaction_type_type == 'Deposit' %}
|
||||
({{ formatAmountBySymbol(journal.foreign_amount, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places) }})
|
||||
{% elseif first.transactiontype.type == 'Transfer' %}
|
||||
{% elseif first.transaction_type_type == 'Transfer' %}
|
||||
<span class="text-info money-transfer">
|
||||
({{ formatAmountBySymbol(journal.foreign_amount, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places, false) }})
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<!-- do foreign PC amount -->
|
||||
{% if null != journal.pc_foreign_amount %}
|
||||
{% if first.transaction_type_type == 'Withdrawal' %}
|
||||
({{ formatAmountBySymbol(journal.pc_foreign_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
|
||||
{% elseif first.transaction_type_type == 'Deposit' %}
|
||||
({{ formatAmountBySymbol(journal.pc_foreign_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
|
||||
{% elseif first.transaction_type_type == 'Transfer' %}
|
||||
<span class="text-info money-transfer">
|
||||
({{ formatAmountBySymbol(journal.pc_foreign_amount, primaryCurrency.symbol, primaryCurrency.decimal_places, false) }})
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
→
|
||||
{% if 'Cash account' == journal.destination_type %}
|
||||
{% if 'Cash account' == journal.destination_account_type %}
|
||||
<span class="text-success">({{ 'cash'|_ }})</span>
|
||||
{% else %}
|
||||
<a href="{{ route('accounts.show', journal.destination_id) }}"
|
||||
title="{{ journal.destination_iban|default(journal.destination_name) }}">{{ journal.destination_name }}</a>
|
||||
<a href="{{ route('accounts.show', journal.destination_account_id) }}"
|
||||
title="{{ journal.destination_iban|default(journal.destination_account_name) }}">{{ journal.destination_account_name }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -346,7 +372,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if null != journal.budget_id and first.transactiontype.type == 'Withdrawal' %}
|
||||
{% if null != journal.budget_id and first.transaction_type_type == 'Withdrawal' %}
|
||||
<tr>
|
||||
<td style="width:40%;">{{ 'budget'|_ }}</td>
|
||||
<td>
|
||||
@@ -354,7 +380,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if null != journal.bill_id and first.transactiontype.type == 'Withdrawal' %}
|
||||
{% if null != journal.bill_id and first.transaction_type_type == 'Withdrawal' %}
|
||||
<tr>
|
||||
<td style="width:40%;">{{ 'bill'|_ }}</td>
|
||||
<td>
|
||||
|
@@ -6,8 +6,14 @@
|
||||
@endcomponent
|
||||
@endslot
|
||||
|
||||
{{-- Body --}}
|
||||
{{ $slot }}
|
||||
{{-- Body --}}
|
||||
{{ trans('email.greeting') }}
|
||||
|
||||
{{ $slot }}
|
||||
|
||||
{{ trans('email.closing') }}
|
||||
|
||||
{{ trans('email.signature')}}
|
||||
|
||||
{{-- Subcopy --}}
|
||||
@isset($subcopy)
|
||||
|
265
routes/api.php
265
routes/api.php
@@ -24,256 +24,6 @@ declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ ____ ___ .______ ______ __ __ .___________. _______ _______.
|
||||
* \ \ / / |__ \ | _ \ / __ \ | | | | | || ____| / |
|
||||
* \ \/ / ) | | |_) | | | | | | | | | `---| |----`| |__ | (----`
|
||||
* \ / / / | / | | | | | | | | | | | __| \ \
|
||||
* \ / / /_ | |\ \----.| `--' | | `--' | | | | |____.----) |
|
||||
* \__/ |____| | _| `._____| \______/ \______/ |__| |_______|_______/
|
||||
*/
|
||||
|
||||
// AUTOCOMPLETE ROUTES
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V2\Controllers\Autocomplete',
|
||||
'prefix' => 'v2/autocomplete',
|
||||
'as' => 'api.v2.autocomplete.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('accounts', ['uses' => 'AccountController@accounts', 'as' => 'accounts']);
|
||||
// Route::get('categories', ['uses' => 'CategoryController@categories', 'as' => 'categories']);
|
||||
// Route::get('tags', ['uses' => 'TagController@tags', 'as' => 'tags']);
|
||||
// Route::get('transaction-descriptions', ['uses' => 'TransactionController@transactionDescriptions', 'as' => 'transaction-descriptions']);
|
||||
}
|
||||
);
|
||||
|
||||
// USER GROUP ROUTES
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V2\Controllers\UserGroup',
|
||||
'prefix' => 'v2/user-groups',
|
||||
'as' => 'api.v2.user-groups.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
Route::post('', ['uses' => 'StoreController@store', 'as' => 'store']);
|
||||
Route::get('{userGroup}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
// Route::put('{userGroup}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
// Route::post('{userGroup}/use', ['uses' => 'UpdateController@useUserGroup', 'as' => 'use']);
|
||||
// Route::put('{userGroup}/update-membership', ['uses' => 'UpdateController@updateMembership', 'as' => 'updateMembership']);
|
||||
// Route::delete('{userGroup}', ['uses' => 'DestroyController@destroy', 'as' => 'destroy']);
|
||||
}
|
||||
);
|
||||
|
||||
// CHART ROUTES
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V2\Controllers\Chart',
|
||||
'prefix' => 'v2/chart',
|
||||
'as' => 'api.v2.chart.',
|
||||
],
|
||||
static function (): void {
|
||||
// Route::get('account/dashboard', ['uses' => 'AccountController@dashboard', 'as' => 'account.dashboard']);
|
||||
// Route::get('budget/dashboard', ['uses' => 'BudgetController@dashboard', 'as' => 'budget.dashboard']);
|
||||
// Route::get('category/dashboard', ['uses' => 'CategoryController@dashboard', 'as' => 'category.dashboard']);
|
||||
Route::get('balance/balance', ['uses' => 'BalanceController@balance', 'as' => 'balance.balance']);
|
||||
}
|
||||
);
|
||||
|
||||
// CURRENCY ROUTES
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V2\Controllers\Model\TransactionCurrency',
|
||||
'prefix' => 'v2/currencies',
|
||||
'as' => 'api.v2.currencies.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
Route::get('{currency_code}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
// Route::post('', ['uses' => 'StoreController@store', 'as' => 'store']);
|
||||
//
|
||||
// Route::put('{userGroup}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
// Route::post('{userGroup}/use', ['uses' => 'UpdateController@useUserGroup', 'as' => 'use']);
|
||||
// Route::put('{userGroup}/update-membership', ['uses' => 'UpdateController@updateMembership', 'as' => 'updateMembership']);
|
||||
// Route::delete('{userGroup}', ['uses' => 'DestroyController@destroy', 'as' => 'destroy']);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// V2 API route for Summary boxes
|
||||
// BASIC
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\Summary',
|
||||
// 'prefix' => 'v2/summary',
|
||||
// 'as' => 'api.v2.summary.',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// // Route::get('basic', ['uses' => 'BasicController@basic', 'as' => 'basic']);
|
||||
// }
|
||||
// );
|
||||
// // V2 API route for all kinds of Transaction lists.
|
||||
// // A lot of endpoints involve transactions. So any time Firefly III needs to list transactions
|
||||
// // it's coming from these endpoints.
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\Transaction\List',
|
||||
// 'prefix' => 'v2',
|
||||
// 'as' => 'api.v2.',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// // basic list
|
||||
// // Route::get('transactions', ['uses' => 'TransactionController@list', 'as' => 'transactions.list']);
|
||||
//
|
||||
// // list by parent or related object.
|
||||
// // note how the check is done on the user group, not the user itself.
|
||||
// // Route::get('accounts/{userGroupAccount}/transactions', ['uses' => 'AccountController@list', 'as' => 'accounts.transactions']);
|
||||
// }
|
||||
// );
|
||||
|
||||
// V2 API routes for auto complete
|
||||
//
|
||||
// // V2 API route for net worth endpoint(s);
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\Summary',
|
||||
// 'prefix' => 'v2/net-worth',
|
||||
// 'as' => 'api.v2.net-worth.',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// // Route::get('', ['uses' => 'NetWorthController@get', 'as' => 'index']);
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// // // V2 API route for accounts.
|
||||
// // Route::group(
|
||||
// // [
|
||||
// // 'namespace' => 'FireflyIII\Api\V2\Controllers\Model\Account',
|
||||
// // 'prefix' => 'v2/accounts',
|
||||
// // 'as' => 'api.v2.accounts.',
|
||||
// // ],
|
||||
// // static function (): void {
|
||||
// // Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
// // Route::get('{account}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
// // Route::put('{account}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
// // }
|
||||
// // );
|
||||
//
|
||||
// // V2 API route for subscriptions.
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\Model\Bill',
|
||||
// 'prefix' => 'v2/subscriptions',
|
||||
// 'as' => 'api.v2.subscriptions.',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// // Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
// // Route::get('{userGroupBill}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
// // Route::get('sum/paid', ['uses' => 'SumController@paid', 'as' => 'sum.paid']);
|
||||
// // Route::get('sum/unpaid', ['uses' => 'SumController@unpaid', 'as' => 'sum.unpaid']);
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// // V2 API route for piggy banks.
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\Model\PiggyBank',
|
||||
// 'prefix' => 'v2/piggy-banks',
|
||||
// 'as' => 'api.v2.piggy-banks.',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// // Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// // V2 API route for transaction currencies
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\Model\Currency',
|
||||
// 'prefix' => 'v2/currencies',
|
||||
// 'as' => 'api.v2.currencies.',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// // Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// // V2 API route for transactions
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\Model\Transaction',
|
||||
// 'prefix' => 'v2/transactions',
|
||||
// 'as' => 'api.v2.transactions.',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// // Route::post('', ['uses' => 'StoreController@post', 'as' => 'store']);
|
||||
// // Route::get('{userGroupTransaction}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
// // Route::put('{userGroupTransaction}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
// }
|
||||
// );
|
||||
// // infinite (transactions) list:
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\Transaction\List',
|
||||
// 'prefix' => 'v2/infinite/transactions',
|
||||
// 'as' => 'api.v2.infinite.transactions.',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// // Route::get('', ['uses' => 'TransactionController@infiniteList', 'as' => 'list']);
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// // V2 API route for budgets and budget limits:
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\Model',
|
||||
// 'prefix' => 'v2/budgets',
|
||||
// 'as' => 'api.v2.budgets',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// // Route::get('', ['uses' => 'Budget\IndexController@index', 'as' => 'index']);
|
||||
// // Route::get('{budget}', ['uses' => 'Budget\ShowController@show', 'as' => 'show']);
|
||||
// // Route::get('{budget}/limits', ['uses' => 'BudgetLimit\IndexController@index', 'as' => 'budget-limits.index']);
|
||||
// // Route::get('sum/budgeted', ['uses' => 'Budget\SumController@budgeted', 'as' => 'sum.budgeted']);
|
||||
// // Route::get('sum/spent', ['uses' => 'Budget\SumController@spent', 'as' => 'sum.spent']);
|
||||
// // Route::get('{budget}/budgeted', ['uses' => 'Budget\ShowController@budgeted', 'as' => 'budget.budgeted']);
|
||||
// // Route::get('{budget}/spent', ['uses' => 'Budget\ShowController@spent', 'as' => 'budget.spent']);
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// // V2 API route for system
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\System',
|
||||
// 'prefix' => 'v2',
|
||||
// 'as' => 'api.v2.system.',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// // Route::get('preferences/{preference}', ['uses' => 'PreferencesController@get', 'as' => 'preferences.get']);
|
||||
// }
|
||||
// );
|
||||
//
|
||||
|
||||
|
||||
// V2 JSON API ROUTES
|
||||
// JsonApiRoute::server('v2')->prefix('v2')
|
||||
// ->resources(function (ResourceRegistrar $server): void {
|
||||
// // ACCOUNTS
|
||||
// $server->resource('accounts', AccountController::class)
|
||||
// ->relationships(function (Relationships $relations): void {
|
||||
// $relations->hasOne('user')->readOnly();
|
||||
// })
|
||||
// ;
|
||||
//
|
||||
// // USERS
|
||||
// $server->resource('users', JsonApiController::class)->readOnly()->relationships(function (Relationships $relations): void {
|
||||
// $relations->hasMany('accounts')->readOnly();
|
||||
// });
|
||||
// })
|
||||
// ;
|
||||
|
||||
/*
|
||||
* ____ ____ __ .______ ______ __ __ .___________. _______ _______.
|
||||
* \ \ / / /_ | | _ \ / __ \ | | | | | || ____| / |
|
||||
@@ -330,6 +80,21 @@ Route::group(
|
||||
);
|
||||
|
||||
// CHART ROUTES.
|
||||
|
||||
// chart balance
|
||||
|
||||
// CHART ROUTES
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V2\Controllers\Chart',
|
||||
'prefix' => 'v1/chart/balance',
|
||||
'as' => 'api.v1.chart.balance',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('balance', ['uses' => 'BalanceController@balance', 'as' => 'balance.balance']);
|
||||
}
|
||||
);
|
||||
|
||||
// Chart accounts
|
||||
Route::group(
|
||||
[
|
||||
|
Reference in New Issue
Block a user