mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-10 06:32:05 +00:00
Compare commits
21 Commits
98be3a1414
...
develop-20
Author | SHA1 | Date | |
---|---|---|---|
|
5a55593e34 | ||
|
8979e5ad5a | ||
|
3ab65c27ac | ||
|
a3cac6fd0f | ||
|
1acb5d8681 | ||
|
f24cdc7897 | ||
|
a2b611253b | ||
|
252459c29b | ||
|
3582baf9f7 | ||
|
b03ecab035 | ||
|
547a4e9dbb | ||
|
62e33a51bd | ||
|
8b0ee7e20a | ||
|
0e0ec89b26 | ||
|
ec08485c2b | ||
|
d91d30c8f0 | ||
|
634a43c361 | ||
|
f2f86e1139 | ||
|
a1c870c962 | ||
|
b254074867 | ||
|
2ddc012549 |
@@ -4,6 +4,7 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
|
||||
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
|
||||
|
||||
## 2025
|
||||
- codearena-bot
|
||||
- Nicky De Maeyer
|
||||
- Denis Iskandarov
|
||||
- Lompi
|
||||
|
@@ -84,7 +84,7 @@ class AccountController extends Controller
|
||||
$data = $request->getData();
|
||||
$types = $data['types'];
|
||||
$query = $data['query'];
|
||||
$date = $data['date'] ?? today(config('app.timezone'));
|
||||
$date = $data['date'];
|
||||
$return = [];
|
||||
$timer = Timer::getInstance();
|
||||
$timer->start(sprintf('AC accounts "%s"', $query));
|
||||
|
@@ -589,8 +589,6 @@ class BasicController extends Controller
|
||||
|
||||
private function getNetWorthInfo(Carbon $end): array
|
||||
{
|
||||
$end->endOfDay();
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
Log::debug(sprintf('getNetWorthInfo up until "%s".', $end->format('Y-m-d H:i:s')));
|
||||
|
@@ -48,10 +48,12 @@ class AutocompleteRequest extends FormRequest
|
||||
// remove 'initial balance' from allowed types. its internal
|
||||
$array = array_diff($array, [AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::RECONCILIATION->value]);
|
||||
|
||||
$date = $this->getCarbonDate('date') ?? today(config('app.timezone'));
|
||||
|
||||
return [
|
||||
'types' => $array,
|
||||
'query' => $this->convertString('query'),
|
||||
'date' => $this->getCarbonDate('date'),
|
||||
'date' => $date->endOfDay(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -55,7 +55,6 @@ class DateRequest extends FormRequest
|
||||
// sanity check on dates:
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
if ($start->diffInYears($end, true) > 5) {
|
||||
|
@@ -137,9 +137,12 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
;
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$journals = array_reverse($journals, true);
|
||||
// this call is correct.
|
||||
Log::debug(sprintf('getAuditReport: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
|
||||
$dayBeforeBalance = Steam::finalAccountBalance($account, $date);
|
||||
|
||||
Log::debug(sprintf('getAuditReport: Call accountsBalancesOptimized with date/time "%s"', $date->toIso8601String()));
|
||||
// 2025-10-08 replace with accountsBalancesOptimized.
|
||||
// $dayBeforeBalance = Steam::finalAccountBalance($account, $date);
|
||||
$dayBeforeBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id];
|
||||
|
||||
$startBalance = $dayBeforeBalance['balance'];
|
||||
$primaryCurrency = app('amount')->getPrimaryCurrencyByUserGroup($account->user->userGroup);
|
||||
$currency = $accountRepository->getAccountCurrency($account) ?? $primaryCurrency;
|
||||
@@ -176,12 +179,14 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
// call is correct.
|
||||
Log::debug(sprintf('getAuditReport end: Call finalAccountBalance with date/time "%s"', $this->end->toIso8601String()));
|
||||
|
||||
// 2025-10-08 replace with accountsBalancesOptimized:
|
||||
return [
|
||||
'journals' => $journals,
|
||||
'currency' => $currency,
|
||||
'exists' => 0 !== count($journals),
|
||||
'end' => $this->end->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)),
|
||||
'endBalance' => Steam::finalAccountBalance($account, $this->end)['balance'],
|
||||
// 'endBalance' => Steam::finalAccountBalance($account, $this->end)['balance'],
|
||||
'endBalance' => Steam::accountsBalancesOptimized(new Collection()->push($account), $this->end)[$account->id]['balance'],
|
||||
'dayBefore' => $date->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)),
|
||||
'dayBeforeBalance' => $dayBeforeBalance,
|
||||
];
|
||||
|
@@ -62,12 +62,13 @@ trait AccountCollection
|
||||
if (null === $account) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2025-10-08 replace with accountsBalancesOptimized
|
||||
// the balance must be found BEFORE the transaction date.
|
||||
// so sub one second. This is not perfect, but works well enough.
|
||||
$date = clone $transaction['date'];
|
||||
$date->subSecond();
|
||||
Log::debug(sprintf('accountBalanceIs: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
|
||||
$balance = Steam::finalAccountBalance($account, $date);
|
||||
// so inclusive = false
|
||||
Log::debug(sprintf('accountBalanceIs: Call accountsBalancesOptimized with date/time "%s"', $transaction['date']->toIso8601String()));
|
||||
$balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $transaction['date'], convertToPrimary: null, inclusive: false)[$account->id];
|
||||
// $balance = Steam::finalAccountBalance($account, $date);
|
||||
$result = bccomp((string) $balance['balance'], $value);
|
||||
Log::debug(sprintf('"%s" vs "%s" is %d', $balance['balance'], $value, $result));
|
||||
|
||||
|
@@ -25,8 +25,6 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Helpers\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Deprecated;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
@@ -130,55 +128,4 @@ class NetWorth implements NetWorthInterface
|
||||
$this->accountRepository->setUserGroup($userGroup);
|
||||
|
||||
}
|
||||
|
||||
#[Deprecated]
|
||||
public function sumNetWorthByCurrency(Carbon $date): array
|
||||
{
|
||||
/**
|
||||
* Collect accounts
|
||||
*/
|
||||
$accounts = $this->getAccounts();
|
||||
$return = [];
|
||||
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';
|
||||
|
||||
// always subtract virtual balance.
|
||||
$virtualBalance = $account->virtual_balance;
|
||||
if ('' !== $virtualBalance) {
|
||||
$balance = bcsub($balance, (string) $virtualBalance);
|
||||
}
|
||||
|
||||
$return[$currency->id] ??= [
|
||||
'id' => (string) $currency->id,
|
||||
'name' => $currency->name,
|
||||
'symbol' => $currency->symbol,
|
||||
'code' => $currency->code,
|
||||
'decimal_places' => $currency->decimal_places,
|
||||
'sum' => '0',
|
||||
];
|
||||
$return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], (string) $balance);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getAccounts(): Collection
|
||||
{
|
||||
$accounts = $this->accountRepository->getAccountsByType(
|
||||
[AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value]
|
||||
);
|
||||
$filtered = new Collection();
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
if (1 === (int) $this->accountRepository->getMetaValue($account, 'include_net_worth')) {
|
||||
$filtered->push($account);
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,6 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Helpers\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Deprecated;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
@@ -49,12 +48,4 @@ interface NetWorthInterface
|
||||
public function setUser(Authenticatable|User|null $user): void;
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void;
|
||||
|
||||
/**
|
||||
* TODO move to repository
|
||||
*
|
||||
* Same as above but cleaner function with less dependencies.
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function sumNetWorthByCurrency(Carbon $date): array;
|
||||
}
|
||||
|
@@ -74,32 +74,30 @@ class IndexController extends Controller
|
||||
*/
|
||||
public function inactive(Request $request, string $objectType)
|
||||
{
|
||||
$inactivePage = true;
|
||||
$subTitle = (string) trans(sprintf('firefly.%s_accounts_inactive', $objectType));
|
||||
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType));
|
||||
$types = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType));
|
||||
$collection = $this->repository->getInactiveAccountsByType($types);
|
||||
$total = $collection->count();
|
||||
$page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page');
|
||||
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
|
||||
$accounts = $collection->slice(($page - 1) * $pageSize, $pageSize);
|
||||
$inactivePage = true;
|
||||
$subTitle = (string) trans(sprintf('firefly.%s_accounts_inactive', $objectType));
|
||||
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType));
|
||||
$types = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType));
|
||||
$collection = $this->repository->getInactiveAccountsByType($types);
|
||||
$total = $collection->count();
|
||||
$page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page');
|
||||
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
|
||||
$accounts = $collection->slice(($page - 1) * $pageSize, $pageSize);
|
||||
unset($collection);
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = clone session('start', today(config('app.timezone'))->startOfMonth());
|
||||
$start = clone session('start', today(config('app.timezone'))->startOfMonth());
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
|
||||
// #10618 go to the end of the previous day.
|
||||
$start->subSecond();
|
||||
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
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);
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
Log::debug(sprintf('inactive start: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
[
|
||||
$startBalances,
|
||||
$endBalances,
|
||||
] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$activities = Steam::getLastActivities($ids);
|
||||
|
||||
|
||||
$accounts->each(
|
||||
@@ -119,7 +117,7 @@ class IndexController extends Controller
|
||||
);
|
||||
|
||||
// make paginator:
|
||||
$accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page);
|
||||
$accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page);
|
||||
$accounts->setPath(route('accounts.inactive.index', [$objectType]));
|
||||
|
||||
return view('accounts.index', compact('objectType', 'inactivePage', 'subTitleIcon', 'subTitle', 'page', 'accounts'));
|
||||
@@ -169,14 +167,12 @@ class IndexController extends Controller
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
|
||||
// #10618 go to the end of the previous day.
|
||||
$start->subSecond();
|
||||
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
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);
|
||||
Log::debug(sprintf('index: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
[
|
||||
$startBalances,
|
||||
$endBalances,
|
||||
] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$activities = Steam::getLastActivities($ids);
|
||||
|
||||
|
||||
|
@@ -39,6 +39,7 @@ use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
|
||||
@@ -113,13 +114,20 @@ class ReconcileController extends Controller
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
|
||||
$startDate = clone $start;
|
||||
$startDate->subDay()->endOfDay(); // this is correct, subday endofday ends at 23:59:59
|
||||
// $startDate = clone $start;
|
||||
// $startDate->subDay()->endOfDay(); // this is correct, subday endofday ends at 23:59:59
|
||||
// both are validated and are correct.
|
||||
Log::debug(sprintf('reconcile: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String()));
|
||||
Log::debug(sprintf('reconcile2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
|
||||
$startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
|
||||
$endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
|
||||
// Log::debug(sprintf('reconcile: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String()));
|
||||
// Log::debug(sprintf('reconcile2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
|
||||
// $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
|
||||
// $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
|
||||
|
||||
// 2025-10-08 replace with accountsBalancesOptimized
|
||||
// no longer need to do subday->endofday on $start, set inclusive = false for the same effect.
|
||||
$startBalance = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $start, convertToPrimary: null, inclusive: false)[$account->id]['balance'], $currency->decimal_places);
|
||||
$endBalance = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id]['balance'], $currency->decimal_places);
|
||||
|
||||
|
||||
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type));
|
||||
$subTitle = (string) trans('firefly.reconcile_account', ['account' => $account->name]);
|
||||
|
||||
|
@@ -169,8 +169,9 @@ class ShowController extends Controller
|
||||
$now = $end;
|
||||
}
|
||||
|
||||
Log::debug(sprintf('show: Call finalAccountBalance with date/time "%s"', $now->toIso8601String()));
|
||||
$balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToPrimary, $accountCurrency);
|
||||
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
|
||||
$balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $now)[$account->id];
|
||||
// $balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToPrimary, $accountCurrency);
|
||||
|
||||
return view(
|
||||
'accounts.show',
|
||||
@@ -237,8 +238,12 @@ class ShowController extends Controller
|
||||
$chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
|
||||
$showAll = true;
|
||||
// correct
|
||||
Log::debug(sprintf('showAll: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
|
||||
$balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $end), $account, $this->convertToPrimary, $accountCurrency);
|
||||
Log::debug(sprintf('showAll: Call accountsBalancesOptimized with date/time "%s"', $end->toIso8601String()));
|
||||
|
||||
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
|
||||
// $balances = Steam::finalAccountBalance($account, $end);
|
||||
// $balances = Steam::filterAccountBalance($balances, $account, $this->convertToPrimary, $accountCurrency);
|
||||
$balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id];
|
||||
|
||||
return view(
|
||||
'accounts.show',
|
||||
|
@@ -116,10 +116,11 @@ class AccountController extends Controller
|
||||
$accountNames = $this->extractNames($accounts);
|
||||
|
||||
// grab all balances
|
||||
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);
|
||||
Log::debug(sprintf('expenseAccounts: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
[
|
||||
$startBalances,
|
||||
$endBalances,
|
||||
] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
Log::debug('Done collecting balances');
|
||||
// loop the accounts, then check for balance and currency info.
|
||||
foreach ($accounts as $account) {
|
||||
@@ -521,9 +522,9 @@ class AccountController extends Controller
|
||||
$range = Steam::filterAccountBalances($range, $account, $this->convertToPrimary, $accountCurrency);
|
||||
Log::debug('Get and filter balance for entire range end');
|
||||
// temp, get end balance.
|
||||
Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
|
||||
Steam::finalAccountBalance($account, $end);
|
||||
Log::debug('END temp get end balance done');
|
||||
// Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
|
||||
// Steam::finalAccountBalance($account, $end);
|
||||
// Log::debug('END temp get end balance done');
|
||||
|
||||
$previous = array_values($range)[0];
|
||||
$accountCurrency ??= $this->primaryCurrency; // do this AFTER getting the balances.
|
||||
@@ -666,10 +667,11 @@ class AccountController extends Controller
|
||||
$accountNames = $this->extractNames($accounts);
|
||||
|
||||
// grab all balances
|
||||
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);
|
||||
Log::debug(sprintf('revAccounts: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
[
|
||||
$startBalances,
|
||||
$endBalances,
|
||||
] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
|
||||
|
||||
// loop the accounts, then check for balance and currency info.
|
||||
|
@@ -38,6 +38,7 @@ use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Controllers\DateCalculation;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class BoxController.
|
||||
@@ -165,7 +166,7 @@ class BoxController extends Controller
|
||||
$allAccounts = $accountRepository->getActiveAccountsByType(
|
||||
[AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value]
|
||||
);
|
||||
app('log')->debug(sprintf('Found %d accounts.', $allAccounts->count()));
|
||||
Log::debug(sprintf('Found %d accounts.', $allAccounts->count()));
|
||||
|
||||
// filter list on preference of being included.
|
||||
$filtered = $allAccounts->filter(
|
||||
@@ -173,7 +174,7 @@ class BoxController extends Controller
|
||||
$includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth');
|
||||
$result = null === $includeNetWorth || '1' === $includeNetWorth;
|
||||
if (false === $result) {
|
||||
app('log')->debug(sprintf('Will not include "%s" in net worth charts.', $account->name));
|
||||
Log::debug(sprintf('Will not include "%s" in net worth charts.', $account->name));
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
@@ -197,10 +197,16 @@ class ReconcileController extends Controller
|
||||
|
||||
$currency = $this->accountRepos->getAccountCurrency($account) ?? $this->primaryCurrency;
|
||||
// correct
|
||||
Log::debug(sprintf('transactions: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String()));
|
||||
Log::debug(sprintf('transactions2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
|
||||
$startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
|
||||
$endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
|
||||
Log::debug(sprintf('transactions: Call accountsBalancesOptimized with date/time "%s"', $startDate->toIso8601String()));
|
||||
Log::debug(sprintf('transactions2: Call accountsBalancesOptimized with date/time "%s"', $end->toIso8601String()));
|
||||
|
||||
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized
|
||||
// $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
|
||||
// $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
|
||||
|
||||
$startBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $startDate)[$account->id];
|
||||
$endBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id];
|
||||
|
||||
|
||||
// get the transactions
|
||||
$selectionStart = clone $start;
|
||||
|
@@ -37,6 +37,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Update\JournalUpdateService;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Controllers\ModelInformation;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
@@ -45,6 +46,7 @@ use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
|
||||
@@ -228,11 +230,13 @@ class ConvertController extends Controller
|
||||
foreach ($accountList as $account) {
|
||||
$date = today()->endOfDay();
|
||||
Log::debug(sprintf('getLiabilities: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
|
||||
$balance = Steam::finalAccountBalance($account, $date)['balance'];
|
||||
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
|
||||
// $balance = Steam::finalAccountBalance($account, $date)['balance'];
|
||||
$balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0';
|
||||
$currency = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency;
|
||||
$role = 'l_'.$account->accountType->type;
|
||||
$key = (string) trans('firefly.opt_group_'.$role);
|
||||
$grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')';
|
||||
$role = sprintf('l_%s', $account->accountType->type);
|
||||
$key = (string) trans(sprintf('firefly.opt_group_%s', $role));
|
||||
$grouped[$key][$account->id] = sprintf('%s (%s)', $account->name, Amount::formatAnything($currency, $balance, false));
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
@@ -252,15 +256,18 @@ class ConvertController extends Controller
|
||||
foreach ($accountList as $account) {
|
||||
$date = today()->endOfDay();
|
||||
Log::debug(sprintf('getAssetAccounts: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
|
||||
$balance = Steam::finalAccountBalance($account, $date)['balance'];
|
||||
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
|
||||
// $balance = Steam::finalAccountBalance($account, $date)['balance'];
|
||||
$balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0';
|
||||
|
||||
$currency = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency;
|
||||
$role = (string) $this->accountRepository->getMetaValue($account, 'account_role');
|
||||
if ('' === $role) {
|
||||
$role = 'no_account_type';
|
||||
}
|
||||
|
||||
$key = (string) trans('firefly.opt_group_'.$role);
|
||||
$grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')';
|
||||
$key = (string) trans(sprintf('firefly.opt_group_%s', $role));
|
||||
$grouped[$key][$account->id] = sprintf('%s (%s)', $account->name, Amount::formatAnything($currency, $balance, false));
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
|
@@ -50,10 +50,11 @@ 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: 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(sprintf('getAccountReport: accountsBalancesInRange("%s", "%s")', $yesterday->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
[
|
||||
$startSet,
|
||||
$endSet,
|
||||
] = Steam::accountsBalancesInRange($accounts, $yesterday, $end);
|
||||
Log::debug('Start of accountreport');
|
||||
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
|
@@ -375,7 +375,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
|
||||
{
|
||||
Log::debug(sprintf('leftOnAccount("%s","%s","%s")', $piggyBank->name, $account->name, $date->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('leftOnAccount: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
|
||||
$balance = Steam::finalAccountBalance($account, $date)['balance'];
|
||||
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
|
||||
$balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'];
|
||||
// $balance = Steam::finalAccountBalance($account, $date)['balance'];
|
||||
|
||||
Log::debug(sprintf('Balance is: %s', $balance));
|
||||
|
||||
|
@@ -139,7 +139,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
|
||||
/** @var Rule $entry */
|
||||
foreach ($set as $entry) {
|
||||
if ($entry->order !== $count) {
|
||||
app('log')->debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count));
|
||||
Log::debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count));
|
||||
$entry->order = $count;
|
||||
$entry->save();
|
||||
}
|
||||
@@ -167,7 +167,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
|
||||
if ($action->order !== $index) {
|
||||
$action->order = $index;
|
||||
$action->save();
|
||||
app('log')->debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index));
|
||||
Log::debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index));
|
||||
}
|
||||
++$index;
|
||||
}
|
||||
@@ -189,7 +189,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
|
||||
if ($order !== $index) {
|
||||
$trigger->order = $index;
|
||||
$trigger->save();
|
||||
app('log')->debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index));
|
||||
Log::debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index));
|
||||
}
|
||||
++$index;
|
||||
}
|
||||
@@ -235,6 +235,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
|
||||
public function getActiveStoreRules(RuleGroup $group): Collection
|
||||
{
|
||||
return $group->rules()
|
||||
->orderBy('rules.order', 'ASC')
|
||||
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
->where('rule_triggers.trigger_type', 'user_action')
|
||||
->where('rule_triggers.trigger_value', 'store-journal')
|
||||
@@ -275,23 +276,23 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
|
||||
if (null === $filter) {
|
||||
return $groups;
|
||||
}
|
||||
app('log')->debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter));
|
||||
Log::debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter));
|
||||
|
||||
return $groups->map(
|
||||
static function (RuleGroup $group) use ($filter) { // @phpstan-ignore-line
|
||||
app('log')->debug(sprintf('Now filtering group #%d', $group->id));
|
||||
Log::debug(sprintf('Now filtering group #%d', $group->id));
|
||||
// filter the rules in the rule group:
|
||||
$group->rules = $group->rules->filter(
|
||||
static function (Rule $rule) use ($filter) {
|
||||
app('log')->debug(sprintf('Now filtering rule #%d', $rule->id));
|
||||
Log::debug(sprintf('Now filtering rule #%d', $rule->id));
|
||||
foreach ($rule->ruleTriggers as $trigger) {
|
||||
if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) {
|
||||
app('log')->debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter));
|
||||
Log::debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
app('log')->debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter));
|
||||
Log::debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -331,23 +332,23 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
|
||||
if (null === $filter) {
|
||||
return $groups;
|
||||
}
|
||||
app('log')->debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter));
|
||||
Log::debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter));
|
||||
|
||||
return $groups->map(
|
||||
static function (RuleGroup $group) use ($filter) { // @phpstan-ignore-line
|
||||
app('log')->debug(sprintf('Now filtering group #%d', $group->id));
|
||||
Log::debug(sprintf('Now filtering group #%d', $group->id));
|
||||
// filter the rules in the rule group:
|
||||
$group->rules = $group->rules->filter(
|
||||
static function (Rule $rule) use ($filter) {
|
||||
app('log')->debug(sprintf('Now filtering rule #%d', $rule->id));
|
||||
Log::debug(sprintf('Now filtering rule #%d', $rule->id));
|
||||
foreach ($rule->ruleTriggers as $trigger) {
|
||||
if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) {
|
||||
app('log')->debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter));
|
||||
Log::debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
app('log')->debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter));
|
||||
Log::debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -414,7 +415,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
|
||||
->decrement('order')
|
||||
;
|
||||
$ruleGroup->order = $newOrder;
|
||||
app('log')->debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder));
|
||||
Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder));
|
||||
$ruleGroup->save();
|
||||
|
||||
return;
|
||||
@@ -425,7 +426,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
|
||||
->increment('order')
|
||||
;
|
||||
$ruleGroup->order = $newOrder;
|
||||
app('log')->debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder));
|
||||
Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder));
|
||||
$ruleGroup->save();
|
||||
}
|
||||
|
||||
|
@@ -49,14 +49,14 @@ class RemoteUserGuard implements Guard
|
||||
{
|
||||
/** @var null|Request $request */
|
||||
$request = $app->get('request');
|
||||
Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $request?->getMethod(), $request?->getRequestUri()));
|
||||
// Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $request?->getMethod(), $request?->getRequestUri()));
|
||||
$this->application = $app;
|
||||
$this->user = null;
|
||||
}
|
||||
|
||||
public function authenticate(): void
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
if ($this->user instanceof User) {
|
||||
Log::debug(sprintf('%s is found: #%d, "%s".', $this->user::class, $this->user->id, $this->user->email));
|
||||
|
||||
@@ -104,21 +104,21 @@ class RemoteUserGuard implements Guard
|
||||
|
||||
public function check(): bool
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
return $this->user() instanceof User;
|
||||
}
|
||||
|
||||
public function guest(): bool
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
return !$this->check();
|
||||
}
|
||||
|
||||
public function hasUser(): bool
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
throw new FireflyException('Did not implement RemoteUserGuard::hasUser()');
|
||||
}
|
||||
@@ -128,14 +128,14 @@ class RemoteUserGuard implements Guard
|
||||
*/
|
||||
public function id(): int|string|null
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
return $this->user?->id;
|
||||
}
|
||||
|
||||
public function setUser(Authenticatable|User|null $user): void // @phpstan-ignore-line
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
|
||||
@@ -146,7 +146,7 @@ class RemoteUserGuard implements Guard
|
||||
|
||||
public function user(): ?User
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
$user = $this->user;
|
||||
if (!$user instanceof User) {
|
||||
Log::debug('User is NULL');
|
||||
@@ -164,14 +164,14 @@ class RemoteUserGuard implements Guard
|
||||
*/
|
||||
public function validate(array $credentials = []): bool
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
throw new FireflyException('Did not implement RemoteUserGuard::validate()');
|
||||
}
|
||||
|
||||
public function viaRemember(): bool
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
// Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@@ -289,8 +289,10 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
{
|
||||
$this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
|
||||
if ($this->start instanceof Carbon && $this->end instanceof Carbon) {
|
||||
$this->startBalances = Steam::accountsBalancesOptimized($this->collection, $this->start, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$this->endBalances = Steam::accountsBalancesOptimized($this->collection, $this->end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
[
|
||||
$this->startBalances,
|
||||
$this->endBalances,
|
||||
] = Steam::accountsBalancesInRange($this->collection, $this->start, $this->end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -402,21 +402,21 @@ trait ConvertsDataTypes
|
||||
*/
|
||||
protected function getCarbonDate(string $field): ?Carbon
|
||||
{
|
||||
$result = null;
|
||||
$data = (string)$this->get($field);
|
||||
Log::debug(sprintf('Date string is "%s"', $data));
|
||||
|
||||
Log::debug(sprintf('Date string is "%s"', (string)$this->get($field)));
|
||||
if ('' === $data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = '' !== (string)$this->get($field) ? new Carbon((string)$this->get($field), config('app.timezone')) : null;
|
||||
return new Carbon($data, config('app.timezone'));
|
||||
} catch (InvalidFormatException) {
|
||||
// @ignoreException
|
||||
Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
|
||||
}
|
||||
if (!$result instanceof Carbon) {
|
||||
Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
|
||||
Log::debug(sprintf('Exception when parsing date "%s".', $data));
|
||||
}
|
||||
|
||||
return $result;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -49,9 +49,9 @@ use function Safe\preg_replace;
|
||||
*/
|
||||
class Steam
|
||||
{
|
||||
public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
|
||||
public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array
|
||||
{
|
||||
Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s"', $accounts->count(), $date->toIso8601String()));
|
||||
Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s" (inclusive: %s)', $accounts->count(), $date->toIso8601String(), var_export($inclusive, true)));
|
||||
$result = [];
|
||||
$convertToPrimary ??= Amount::convertToPrimary();
|
||||
$primary ??= Amount::getPrimaryCurrency();
|
||||
@@ -61,14 +61,15 @@ class Steam
|
||||
$arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray())
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
|
||||
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
|
||||
->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s'))
|
||||
->groupBy(['transactions.account_id', 'transaction_currencies.code'])
|
||||
->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray()
|
||||
;
|
||||
|
||||
Log::debug('Array of sums: ', $arrayOfSums);
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
// this array is PER account, so we wait a bit before we change code here.
|
||||
$return = [
|
||||
'pc_balance' => '0',
|
||||
'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
|
||||
@@ -76,19 +77,19 @@ class Steam
|
||||
$currency = $currencies[$account->id];
|
||||
|
||||
// second array
|
||||
$accountSum = array_filter($arrayOfSums, fn ($entry) => $entry['account_id'] === $account->id);
|
||||
if (0 === count($accountSum)) {
|
||||
$accountSums = array_filter($arrayOfSums, fn ($entry) => $entry['account_id'] === $account->id);
|
||||
if (0 === count($accountSums)) {
|
||||
$result[$account->id] = $return;
|
||||
|
||||
continue;
|
||||
}
|
||||
$accountSum = array_values($accountSum)[0];
|
||||
$sumOfAmount = (string)$accountSum['sum_of_amount'];
|
||||
$sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
|
||||
$sumsByCode = [
|
||||
$accountSum['code'] => $sumOfAmount,
|
||||
];
|
||||
|
||||
$sumsByCode = [];
|
||||
foreach ($accountSums as $accountSum) {
|
||||
// $accountSum = array_values($accountSum)[0];
|
||||
$sumOfAmount = (string)$accountSum['sum_of_amount'];
|
||||
$sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
|
||||
$sumsByCode[$accountSum['code']] = $sumOfAmount;
|
||||
}
|
||||
// Log::debug('All balances are (joined)', $others);
|
||||
// if there is no request to convert, take this as "balance" and "pc_balance".
|
||||
$return['balance'] = $sumsByCode[$currency->code] ?? '0';
|
||||
@@ -96,6 +97,7 @@ class Steam
|
||||
unset($return['pc_balance']);
|
||||
// Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
|
||||
}
|
||||
|
||||
// if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
|
||||
if ($convertToPrimary) {
|
||||
$return['pc_balance'] = $this->convertAllBalances($sumsByCode, $primary, $date);
|
||||
@@ -119,12 +121,23 @@ class Steam
|
||||
}
|
||||
$final = array_merge($return, $sumsByCode);
|
||||
$result[$account->id] = $final;
|
||||
// Log::debug('Final balance is', $final);
|
||||
Log::debug(sprintf('Final balance for account #%d is', $account->id), $final);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls accountsBalancesOptimized for the given accounts and makes sure that inclusive is set to false, so it properly gets the balance of a range.
|
||||
*/
|
||||
public function accountsBalancesInRange(Collection $accounts, Carbon $start, Carbon $end, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
|
||||
{
|
||||
return [
|
||||
$this->accountsBalancesOptimized($accounts, $start, $primary, $convertToPrimary, inclusive: false),
|
||||
$this->accountsBalancesOptimized($accounts, $end, $primary, $convertToPrimary),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers
|
||||
*/
|
||||
@@ -276,7 +289,9 @@ class Steam
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns smaller than or equal to, so be careful with END OF DAY.
|
||||
* @deprecated
|
||||
* By default this method returns "smaller than or equal to", so be careful with END OF DAY.
|
||||
* If you need end of day balance, use "inclusive = false".
|
||||
*
|
||||
* Returns the balance of an account at exact moment given. Array with at least one value.
|
||||
* Always returns:
|
||||
@@ -288,18 +303,17 @@ class Steam
|
||||
* --> "pc_balance": balance in the user's primary currency, with all amounts converted to the primary currency.
|
||||
* "EUR": balance in EUR (or whatever currencies the account has balance in)
|
||||
*/
|
||||
public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
|
||||
public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array
|
||||
{
|
||||
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($account->id);
|
||||
$cache->addProperty($date);
|
||||
if ($cache->has()) {
|
||||
Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
|
||||
|
||||
Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s, inclusive:%s)', $account->id, $date->format('Y-m-d H:i:s'), var_export($inclusive, true)));
|
||||
// return $cache->get();
|
||||
}
|
||||
// Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
|
||||
if (null === $convertToPrimary) {
|
||||
$convertToPrimary = Amount::convertToPrimary($account->user);
|
||||
}
|
||||
@@ -312,7 +326,6 @@ class Steam
|
||||
$accountCurrency = $account->meta['currency'];
|
||||
}
|
||||
if (!$currencyPresent) {
|
||||
|
||||
$accountCurrency = $this->getAccountCurrency($account);
|
||||
}
|
||||
$hasCurrency = null !== $accountCurrency;
|
||||
@@ -325,11 +338,11 @@ class Steam
|
||||
$array = $account->transactions()
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
|
||||
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
|
||||
->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s'))
|
||||
->get(['transaction_currencies.code', 'transactions.amount'])->toArray()
|
||||
;
|
||||
$others = $this->groupAndSumTransactions($array, 'code', 'amount');
|
||||
// Log::debug('All balances are (joined)', $others);
|
||||
Log::debug('All balances are (joined)', $others);
|
||||
// if there is no request to convert, take this as "balance" and "pc_balance".
|
||||
$return['balance'] = $others[$currency->code] ?? '0';
|
||||
if (!$convertToPrimary) {
|
||||
@@ -338,7 +351,7 @@ class Steam
|
||||
}
|
||||
// if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
|
||||
if ($convertToPrimary) {
|
||||
$return['pc_balance'] = $this->convertAllBalances($others, $primary, $date); // todo sum all and convert.
|
||||
$return['pc_balance'] = $this->convertAllBalances($others, $primary, $date);
|
||||
// Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance']));
|
||||
}
|
||||
|
||||
@@ -358,18 +371,21 @@ class Steam
|
||||
// Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
|
||||
}
|
||||
$final = array_merge($return, $others);
|
||||
// Log::debug('Final balance is', $final);
|
||||
Log::debug('Final balance is', $final);
|
||||
$cache->store($final);
|
||||
|
||||
return $final;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the balance for the given account in the range, with daily precision.
|
||||
*/
|
||||
public function finalAccountBalanceInRange(Account $account, Carbon $start, Carbon $end, bool $convertToPrimary): array
|
||||
{
|
||||
// expand period.
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
Log::debug(sprintf('finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('called finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
|
||||
// set up cache
|
||||
$cache = new CacheProperties();
|
||||
@@ -379,28 +395,21 @@ class Steam
|
||||
$cache->addProperty($convertToPrimary);
|
||||
$cache->addProperty($end);
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
Log::debug('Return cached finalAccountBalanceInRange');
|
||||
// return $cache->get();
|
||||
}
|
||||
|
||||
$balances = [];
|
||||
$formatted = $start->format('Y-m-d');
|
||||
/*
|
||||
* To make sure the start balance is correct, we need to get the balance at the exact end of the previous day.
|
||||
* Since we just did "startOfDay" we can do subDay()->endOfDay() to get the correct moment.
|
||||
* THAT will be the start balance.
|
||||
*/
|
||||
$request = clone $start;
|
||||
$request->subDay()->endOfDay();
|
||||
Log::debug('Get first balance to start.');
|
||||
Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String()));
|
||||
$startBalance = $this->finalAccountBalance($account, $request);
|
||||
// 2025-10-08 replaced finalAccountBalance with accountsBalancesOptimized:
|
||||
$primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
|
||||
$startBalance = $this->accountsBalancesOptimized(new Collection()->push($account), $start, $primaryCurrency, $convertToPrimary, false)[$account->id];
|
||||
$accountCurrency = $this->getAccountCurrency($account);
|
||||
$hasCurrency = $accountCurrency instanceof TransactionCurrency;
|
||||
$currency = $accountCurrency ?? $primaryCurrency;
|
||||
Log::debug(sprintf('Currency is %s', $currency->code));
|
||||
|
||||
|
||||
// set start balances:
|
||||
$startBalance[$currency->code] ??= '0';
|
||||
if ($hasCurrency) {
|
||||
@@ -542,7 +551,7 @@ class Steam
|
||||
try {
|
||||
$hostName = gethostbyaddr($ipAddress);
|
||||
} catch (Exception $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
Log::error($e->getMessage());
|
||||
$hostName = $ipAddress;
|
||||
}
|
||||
|
||||
@@ -757,7 +766,7 @@ class Steam
|
||||
$current = $converter->convert($currency, $primary, $date, $amount);
|
||||
Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $primary->code, $current));
|
||||
}
|
||||
$total = bcadd((string) $current, $total);
|
||||
$total = bcadd((string)$current, $total);
|
||||
}
|
||||
|
||||
return $total;
|
||||
|
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Support\System;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
@@ -63,10 +64,10 @@ class OAuthKeys
|
||||
$privateKey = '';
|
||||
$publicKey = '';
|
||||
// better check if keys are in the database:
|
||||
if (app('fireflyconfig')->has(self::PRIVATE_KEY) && app('fireflyconfig')->has(self::PUBLIC_KEY)) {
|
||||
if (FireflyConfig::has(self::PRIVATE_KEY) && FireflyConfig::has(self::PUBLIC_KEY)) {
|
||||
try {
|
||||
$privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data;
|
||||
$publicKey = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data;
|
||||
$privateKey = (string)FireflyConfig::get(self::PRIVATE_KEY)?->data;
|
||||
$publicKey = (string)FireflyConfig::get(self::PUBLIC_KEY)?->data;
|
||||
} catch (ContainerExceptionInterface|FireflyException|NotFoundExceptionInterface $e) {
|
||||
app('log')->error(sprintf('Could not validate keysInDatabase(): %s', $e->getMessage()));
|
||||
app('log')->error($e->getTraceAsString());
|
||||
@@ -87,8 +88,8 @@ class OAuthKeys
|
||||
*/
|
||||
public static function restoreKeysFromDB(): bool
|
||||
{
|
||||
$privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data;
|
||||
$publicKey = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data;
|
||||
$privateKey = (string)FireflyConfig::get(self::PRIVATE_KEY)?->data;
|
||||
$publicKey = (string)FireflyConfig::get(self::PUBLIC_KEY)?->data;
|
||||
|
||||
try {
|
||||
$privateContent = Crypt::decrypt($privateKey);
|
||||
@@ -98,8 +99,8 @@ class OAuthKeys
|
||||
app('log')->error($e->getMessage());
|
||||
|
||||
// delete config vars from DB:
|
||||
app('fireflyconfig')->delete(self::PRIVATE_KEY);
|
||||
app('fireflyconfig')->delete(self::PUBLIC_KEY);
|
||||
FireflyConfig::delete(self::PRIVATE_KEY);
|
||||
FireflyConfig::delete(self::PUBLIC_KEY);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -115,8 +116,8 @@ class OAuthKeys
|
||||
{
|
||||
$private = storage_path('oauth-private.key');
|
||||
$public = storage_path('oauth-public.key');
|
||||
app('fireflyconfig')->set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private)));
|
||||
app('fireflyconfig')->set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public)));
|
||||
FireflyConfig::set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private)));
|
||||
FireflyConfig::set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public)));
|
||||
}
|
||||
|
||||
public static function verifyKeysRoutine(): void
|
||||
|
@@ -30,6 +30,7 @@ use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Search\OperatorQuerySearch;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use League\CommonMark\GithubFlavoredMarkdownConverter;
|
||||
@@ -155,9 +156,12 @@ class General extends AbstractExtension
|
||||
}
|
||||
|
||||
/** @var Carbon $date */
|
||||
$date = session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$date = now();
|
||||
Log::debug(sprintf('twig balance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
|
||||
$info = Steam::finalAccountBalance($account, $date);
|
||||
|
||||
// 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
|
||||
$info = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id];
|
||||
// $info = Steam::finalAccountBalance($account, $date);
|
||||
$currency = Steam::getAccountCurrency($account);
|
||||
$primary = Amount::getPrimaryCurrency();
|
||||
$convertToPrimary = Amount::convertToPrimary();
|
||||
|
@@ -506,9 +506,23 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
*/
|
||||
private function fireGroup(RuleGroup $group): void
|
||||
{
|
||||
Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $group->rules->count()));
|
||||
|
||||
$rules = $group->rules()->orderBy('order', 'ASC')->get();
|
||||
$rules = [];
|
||||
if ($group->relationLoaded('rules')) {
|
||||
Log::debug('Group rules have been pre-loaded, do not reload them.');
|
||||
$rules = $group->rules;
|
||||
}
|
||||
if (!$group->relationLoaded('rules')) {
|
||||
Log::debug('Group rules have NOT been pre-loaded, load them NOW.');
|
||||
$rules = $group->rules()
|
||||
->orderBy('rules.order', 'ASC')
|
||||
// ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
// ->where('rule_triggers.trigger_type', 'user_action')
|
||||
// ->where('rule_triggers.trigger_value', 'store-journal')
|
||||
->where('rules.active', true)
|
||||
->get(['rules.*'])
|
||||
;
|
||||
}
|
||||
Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $rules->count()));
|
||||
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
|
@@ -78,8 +78,8 @@ return [
|
||||
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => 'develop/2025-10-07',
|
||||
'build_time' => 1759859933,
|
||||
'version' => 'develop/2025-10-08',
|
||||
'build_time' => 1759931348,
|
||||
'api_version' => '2.1.0', // field is no longer used.
|
||||
'db_version' => 28, // field is no longer used.
|
||||
|
||||
|
@@ -53,6 +53,10 @@ class TransactionCurrencySeeder extends Seeder
|
||||
$currencies[] = ['code' => 'BRL', 'name' => 'Brazilian real', 'symbol' => 'R$', 'decimal_places' => 2];
|
||||
$currencies[] = ['code' => 'CAD', 'name' => 'Canadian dollar', 'symbol' => 'C$', 'decimal_places' => 2];
|
||||
$currencies[] = ['code' => 'MXN', 'name' => 'Mexican peso', 'symbol' => 'MX$', 'decimal_places' => 2];
|
||||
+ $currencies[] = ['code' => 'PEN', 'name' => 'Peruvian Sol', 'symbol' => 'S/', 'decimal_places' => 2];
|
||||
+ $currencies[] = ['code' => 'ARS', 'name' => 'Argentinian Peso', 'symbol' => '$', 'decimal_places' => 2];
|
||||
+ $currencies[] = ['code' => 'COP', 'name' => 'Colombian Peso', 'symbol' => '$', 'decimal_places' => 2];
|
||||
+ $currencies[] = ['code' => 'CLP', 'name' => 'Chilean Peso', 'symbol' => '$', 'decimal_places' => 2];
|
||||
|
||||
// oceanian currencies
|
||||
$currencies[] = ['code' => 'IDR', 'name' => 'Indonesian rupiah', 'symbol' => 'Rp', 'decimal_places' => 2];
|
||||
|
28
package-lock.json
generated
28
package-lock.json
generated
@@ -4521,9 +4521,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001748",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz",
|
||||
"integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==",
|
||||
"version": "1.0.30001749",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz",
|
||||
"integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5736,9 +5736,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.232",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.232.tgz",
|
||||
"integrity": "sha512-ENirSe7wf8WzyPCibqKUG1Cg43cPaxH4wRR7AJsX7MCABCHBIOFqvaYODSLKUuZdraxUTHRE/0A2Aq8BYKEHOg==",
|
||||
"version": "1.5.233",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.233.tgz",
|
||||
"integrity": "sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -7908,13 +7908,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
|
||||
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
|
||||
"integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.11.5"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/loader-utils": {
|
||||
@@ -10290,9 +10294,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
|
Reference in New Issue
Block a user