Compare commits

..

29 Commits

Author SHA1 Message Date
James Cole
399d7968f5 Fix another null. 2024-12-25 08:30:00 +01:00
James Cole
966b68f42e Merge branch 'v6.2' of github.com:firefly-iii/firefly-iii into v6.2 2024-12-25 08:29:50 +01:00
James Cole
134c551c12 Remove some comments, add others. 2024-12-25 08:23:17 +01:00
github-actions
9aeca15355 Auto commit for release 'branch-v6.2' on 2024-12-25 2024-12-25 08:17:59 +01:00
James Cole
6c6d31830b Fix nullpointer 2024-12-25 08:14:07 +01:00
github-actions
e8cc321898 Auto commit for release 'branch-v6.2' on 2024-12-25 2024-12-25 07:13:41 +01:00
James Cole
e73fe06f7e Expand currency view in accounts. 2024-12-25 07:10:02 +01:00
James Cole
98b579c042 Fix native display. 2024-12-24 19:03:47 +01:00
James Cole
7b3a5c1afd Frontpage seems to be multi currency aware. 2024-12-24 16:56:31 +01:00
James Cole
7e2e49e129 Convert more charts. 2024-12-24 10:29:07 +01:00
James Cole
e8ef630424 Expand native amount things. 2024-12-24 06:34:12 +01:00
James Cole
8805bcf6f6 Various updates to display native/foreign amounts. 2024-12-23 17:32:15 +01:00
James Cole
ff5c9a3aa0 Merge branch 'v6.2' of github.com:firefly-iii/firefly-iii into v6.2 2024-12-23 14:35:34 +01:00
James Cole
3a274dcaa7 Little debug page with routes. 2024-12-23 14:35:27 +01:00
github-actions
ddfededf02 Auto commit for release 'branch-v6.2' on 2024-12-23 2024-12-23 08:40:29 +01:00
James Cole
e1785898ba Add notes to audit report. 2024-12-23 08:35:33 +01:00
James Cole
ae09200f42 Fix import 2024-12-23 08:26:35 +01:00
James Cole
847984f678 New rule triggers for account balance. 2024-12-23 08:22:54 +01:00
James Cole
42305672ac Merge branch 'v6.2' of github.com:firefly-iii/firefly-iii into v6.2 2024-12-23 07:14:12 +01:00
James Cole
25a56d9f72 Chart no longer as a line chart. 2024-12-23 07:13:59 +01:00
github-actions
8f9f08b96f Auto commit for release 'branch-v6.2' on 2024-12-23 2024-12-23 07:01:44 +01:00
James Cole
3c65b46aa5 Merge branch 'v6.2' of github.com:firefly-iii/firefly-iii into v6.2
# Conflicts:
#	app/Support/Steam.php
2024-12-23 06:56:31 +01:00
James Cole
1cf9c76329 Fix array bug. 2024-12-23 06:55:14 +01:00
github-actions
c0499df4ec Auto commit for release 'branch-v6.2' on 2024-12-22 2024-12-22 20:37:54 +01:00
James Cole
d90ac519f7 Clean up balance methods. 2024-12-22 20:32:58 +01:00
James Cole
a0e92b6969 Clean up variety of account balance methods. 2024-12-22 19:42:06 +01:00
James Cole
df49dd23e2 Try to convert balance to native, still internal debates. 2024-12-22 16:41:55 +01:00
James Cole
d4525da6bc Merge branch 'v6.2' of github.com:firefly-iii/firefly-iii into v6.2 2024-12-22 15:54:06 +01:00
James Cole
25b11bd20b Add preference for native amounts. 2024-12-22 15:53:59 +01:00
67 changed files with 4480 additions and 4156 deletions

View File

@@ -30,6 +30,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
@@ -89,11 +90,11 @@ class AccountController extends Controller
$currency = $this->repository->getAccountCurrency($account) ?? $defaultCurrency;
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
$balance = app('steam')->balance($account, $date);
$balance = Steam::finalAccountBalance($account, $date);
$nameWithBalance = sprintf(
'%s (%s)',
$account->name,
app('amount')->formatAnything($currency, $balance, false)
app('amount')->formatAnything($currency, $balance['balance'], false)
);
}

View File

@@ -116,13 +116,13 @@ class AccountController extends Controller
];
// TODO this code is also present in the V2 chart account controller so this method is due to be deprecated.
$currentStart = clone $start;
$range = app('steam')->balanceInRange($account, $start, clone $end);
$range = app('steam')->finalAccountBalanceInRange($account, $start, clone $end);
// 2022-10-11 this method no longer converts to float.
$previous = array_values($range)[0];
while ($currentStart <= $end) {
$format = $currentStart->format('Y-m-d');
$label = $currentStart->toAtomString();
$balance = array_key_exists($format, $range) ? $range[$format] : $previous;
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
$previous = $balance;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance;

View File

@@ -27,19 +27,21 @@ namespace FireflyIII\Api\V1\Controllers\Summary;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Data\DateRequest;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
/**
* Class BasicController
@@ -120,48 +122,53 @@ class BasicController extends Controller
private function getBalanceInformation(Carbon $start, Carbon $end): array
{
// some config settings
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$default = app('amount')->getDefaultCurrency();
// prep some arrays:
$incomes = [];
$expenses = [];
$sums = [];
$return = [];
$incomes = [];
$expenses = [];
$sums = [];
$return = [];
// collect income of user using the new group collector.
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->setPage($this->parameters->get('page'))->setTypes([TransactionTypeEnum::DEPOSIT->value]);
$set = $collector->getExtractedJournals();
$set = $collector->getExtractedJournals();
/** @var array $transactionJournal */
foreach ($set as $transactionJournal) {
$currencyId = (int) $transactionJournal['currency_id'];
/** @var array $journal */
foreach ($set as $journal) {
$currencyId = $convertToNative ? $default->id : (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
$incomes[$currencyId] ??= '0';
$incomes[$currencyId] = bcadd(
$incomes[$currencyId],
bcmul($transactionJournal['amount'], '-1')
bcmul($amount, '-1')
);
$sums[$currencyId] ??= '0';
$sums[$currencyId] = bcadd($sums[$currencyId], bcmul($transactionJournal['amount'], '-1'));
$sums[$currencyId] = bcadd($sums[$currencyId], bcmul($amount, '-1'));
}
// collect expenses of user using the new group collector.
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->setPage($this->parameters->get('page'))->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$set = $collector->getExtractedJournals();
$set = $collector->getExtractedJournals();
/** @var array $transactionJournal */
foreach ($set as $transactionJournal) {
$currencyId = (int) $transactionJournal['currency_id'];
/** @var array $journal */
foreach ($set as $journal) {
$currencyId = $convertToNative ? $default->id : (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
$expenses[$currencyId] ??= '0';
$expenses[$currencyId] = bcadd($expenses[$currencyId], $transactionJournal['amount']);
$expenses[$currencyId] = bcadd($expenses[$currencyId], $amount);
$sums[$currencyId] ??= '0';
$sums[$currencyId] = bcadd($sums[$currencyId], $transactionJournal['amount']);
$sums[$currencyId] = bcadd($sums[$currencyId], $amount);
}
// format amounts:
$keys = array_keys($sums);
$keys = array_keys($sums);
foreach ($keys as $currencyId) {
$currency = $this->currencyRepos->find($currencyId);
if (null === $currency) {
@@ -274,19 +281,22 @@ class BasicController extends Controller
$available = $this->abRepository->getAvailableBudgetWithCurrency($start, $end);
$budgets = $this->budgetRepository->getActiveBudgets();
$spent = $this->opsRepository->sumExpenses($start, $end, null, $budgets);
$days = (int) $today->diffInDays($end, true) + 1;
Log::debug(sprintf('Now in getLeftToSpendInfo("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
foreach ($spent as $row) {
// either an amount was budgeted or 0 is available.
$amount = (string) ($available[$row['currency_id']] ?? '0');
$currencyId = $row['currency_id'];
$amount = (string) ($available[$currencyId] ?? '0');
$spentInCurrency = $row['sum'];
$leftToSpend = bcadd($amount, $spentInCurrency);
$days = (int) $today->diffInDays($end, true) + 1;
$perDay = '0';
if (0 !== $days && bccomp($leftToSpend, '0') > -1) {
$perDay = bcdiv($leftToSpend, (string) $days);
}
Log::debug(sprintf('Spent %s %s', $row['currency_code'], $row['sum']));
$return[] = [
'key' => sprintf('left-to-spend-in-%s', $row['currency_code']),
'title' => trans('firefly.box_left_to_spend_in_currency', ['currency' => $row['currency_symbol']]),
@@ -311,9 +321,11 @@ class BasicController extends Controller
private function getNetWorthInfo(Carbon $start, Carbon $end): array
{
Log::debug('getNetWorthInfo');
/** @var User $user */
$user = auth()->user();
$date = today(config('app.timezone'))->startOfDay();
$date = now(config('app.timezone'));
// start and end in the future? use $end
if ($this->notInDateRange($date, $start, $end)) {
/** @var Carbon $date */
@@ -323,9 +335,7 @@ class BasicController extends Controller
/** @var NetWorthInterface $netWorthHelper */
$netWorthHelper = app(NetWorthInterface::class);
$netWorthHelper->setUser($user);
$allAccounts = $this->accountRepository->getActiveAccountsByType(
[AccountType::ASSET, AccountType::DEFAULT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::DEBT]
);
$allAccounts = $this->accountRepository->getActiveAccountsByType([AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value]);
// filter list on preference of being included.
$filtered = $allAccounts->filter(
@@ -360,6 +370,7 @@ class BasicController extends Controller
'sub_title' => '',
];
}
Log::debug('End of getNetWorthInfo');
return $return;
}

View File

@@ -92,11 +92,11 @@ class AccountController extends Controller
*/
private function renderAccountData(array $params, Account $account): void
{
$currency = $this->repository->getAccountCurrency($account);
$currency = $this->repository->getAccountCurrency($account);
if (null === $currency) {
$currency = $this->default;
}
$currentSet = [
$currentSet = [
'label' => $account->name,
// the currency that belongs to the account.
@@ -117,23 +117,22 @@ class AccountController extends Controller
'entries' => [],
'native_entries' => [],
];
$currentStart = clone $params['start'];
$range = app('steam')->balanceInRange($account, $params['start'], clone $params['end'], $currency);
$rangeConverted = app('steam')->balanceInRangeConverted($account, $params['start'], clone $params['end'], $this->default);
$currentStart = clone $params['start'];
$range = app('steam')->finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $currency);
$previous = array_values($range)[0];
$previousConverted = array_values($rangeConverted)[0];
$previous = array_values($range)[0]['balance'];
$previousNative = array_values($range)[0]['native_balance'];
while ($currentStart <= $params['end']) {
$format = $currentStart->format('Y-m-d');
$label = $currentStart->toAtomString();
$balance = array_key_exists($format, $range) ? $range[$format] : $previous;
$balanceConverted = array_key_exists($format, $rangeConverted) ? $rangeConverted[$format] : $previousConverted;
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
$balanceNative = array_key_exists($format, $range) ? $range[$format]['balance_native'] : $previousNative;
$previous = $balance;
$previousConverted = $balanceConverted;
$previousNative = $balanceNative;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance;
$currentSet['native_entries'][$label] = $balanceConverted;
$currentSet['native_entries'][$label] = $balanceNative;
}
$this->chartData->add($currentSet);
}

View File

@@ -108,6 +108,7 @@ class RecalculateNativeAmounts extends Command
private function recalculatePiggyBanks(UserGroup $userGroup, TransactionCurrency $currency): void
{
$converter = new ExchangeRateConverter();
$converter->setUserGroup($userGroup);
$converter->setIgnoreSettings(true);
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUserGroup($userGroup);
@@ -210,11 +211,19 @@ class RecalculateNativeAmounts extends Command
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (DatabaseBuilder $q) use ($currency): void {
$q->whereNot('transactions.transaction_currency_id', $currency->id)
->orWhereNot('transactions.foreign_currency_id', $currency->id)
;
->where(function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
})->orWhere(function (DatabaseBuilder $q3) use ($currency): void {
$q3->whereNot('transactions.transaction_currency_id', $currency->id)->whereNot('transactions.foreign_currency_id', $currency->id);
});
})
// ->where(static function (DatabaseBuilder $q) use ($currency): void {
// $q->whereNot('transactions.transaction_currency_id', $currency->id)
// ->whereNot('transactions.foreign_currency_id', $currency->id)
// ;
// })
->get(['transactions.id'])
;
TransactionObserver::$recalculate = false;

View File

@@ -31,6 +31,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Support\Collection;
/**
@@ -82,6 +83,9 @@ class MonthReportGenerator implements ReportGeneratorInterface
'create_date',
'update_date',
// more
'notes',
// date fields.
'interest_date',
'book_date',
@@ -125,12 +129,12 @@ class MonthReportGenerator implements ReportGeneratorInterface
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end)->withAccountInformation()
->withBudgetInformation()->withCategoryInformation()->withBillInformation()
->withBudgetInformation()->withCategoryInformation()->withBillInformation()->withNotes()
;
$journals = $collector->getExtractedJournals();
$journals = array_reverse($journals, true);
$dayBeforeBalance = app('steam')->balance($account, $date);
$startBalance = $dayBeforeBalance;
$dayBeforeBalance = Steam::finalAccountBalance($account, $date);
$startBalance = $dayBeforeBalance['balance'];
$defaultCurrency = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
$currency = $accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
@@ -169,7 +173,7 @@ class MonthReportGenerator implements ReportGeneratorInterface
'currency' => $currency,
'exists' => 0 !== count($journals),
'end' => $this->end->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)),
'endBalance' => app('steam')->balance($account, $this->end),
'endBalance' => Steam::finalAccountBalance($account, $this->end)['balance'],
'dayBefore' => $date->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)),
'dayBeforeBalance' => $dayBeforeBalance,
];

View File

@@ -48,6 +48,7 @@ class AvailableBudgetObserver
$availableBudget->native_amount = null;
if ($availableBudget->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($availableBudget->user->userGroup);
$converter->setIgnoreSettings(true);
$availableBudget->native_amount = $converter->convert($availableBudget->transactionCurrency, $userCurrency, today(), $availableBudget->amount);
}

View File

@@ -71,14 +71,16 @@ class TransactionObserver
$transaction->native_amount = null;
$transaction->native_foreign_amount = null;
// first normal amount
if ($transaction->transactionCurrency->id !== $userCurrency->id) {
if ($transaction->transactionCurrency->id !== $userCurrency->id && (null === $transaction->foreign_currency_id || (null !== $transaction->foreign_currency_id && $transaction->foreign_currency_id !== $userCurrency->id))) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($transaction->transactionJournal->user->userGroup);
$converter->setIgnoreSettings(true);
$transaction->native_amount = $converter->convert($transaction->transactionCurrency, $userCurrency, $transaction->transactionJournal->date, $transaction->amount);
}
// then foreign amount
if ($transaction->foreignCurrency?->id !== $userCurrency->id && null !== $transaction->foreign_amount && null !== $transaction->foreignCurrency) {
$converter = new ExchangeRateConverter();
$converter->setUserGroup($transaction->transactionJournal->user->userGroup);
$converter->setIgnoreSettings(true);
$transaction->native_foreign_amount = $converter->convert($transaction->foreignCurrency, $userCurrency, $transaction->transactionJournal->date, $transaction->foreign_amount);
}

View File

@@ -25,8 +25,11 @@ declare(strict_types=1);
namespace FireflyIII\Helpers\Collector\Extensions;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Trait AccountCollection
@@ -228,4 +231,76 @@ trait AccountCollection
return $this;
}
#[\Override]
public function accountBalanceIs(string $direction, string $operator, string $value): GroupCollectorInterface
{
Log::warning(sprintf('GroupCollector will be SLOW: accountBalanceIs: "%s" "%s" "%s"', $direction, $operator, $value));
/**
* @param int $index
* @param array $object
*
* @return bool
*/
$filter = static function (array $object) use ($direction, $operator, $value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
$key = sprintf('%s_account_id', $direction);
$accountId = $transaction[$key] ?? 0;
if (0 === $accountId) {
return false;
}
// in theory, this could lead to finding other users accounts.
$balance = Steam::finalAccountBalance(Account::find($accountId), $transaction['date']);
$result = bccomp($balance['balance'], $value);
Log::debug(sprintf('"%s" vs "%s" is %d', $balance['balance'], $value, $result));
switch ($operator) {
default:
Log::error(sprintf('GroupCollector: accountBalanceIs: unknown operator "%s"', $operator));
return false;
case '==':
Log::debug('Expect result to be 0 (equal)');
return 0 === $result;
case '!=':
Log::debug('Expect result to be -1 or 1 (not equal)');
return 0 !== $result;
case '>':
Log::debug('Expect result to be 1 (greater then)');
return 1 === $result;
case '>=':
Log::debug('Expect result to be 0 or 1 (greater then or equal)');
return -1 !== $result;
case '<':
Log::debug('Expect result to be -1 (less than)');
return -1 === $result;
case '<=':
Log::debug('Expect result to be -1 or 0 (less than or equal)');
return 1 !== $result;
}
// if($balance['balance'] $operator $value) {
// }
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
}

View File

@@ -131,6 +131,7 @@ class GroupCollector implements GroupCollectorInterface
// currency info:
'source.amount as amount',
'source.native_amount as native_amount',
'source.transaction_currency_id as currency_id',
'currency.code as currency_code',
'currency.name as currency_name',
@@ -139,6 +140,7 @@ class GroupCollector implements GroupCollectorInterface
// foreign currency info
'source.foreign_amount as foreign_amount',
'source.native_foreign_amount as native_foreign_amount',
'source.foreign_currency_id as foreign_currency_id',
'foreign_currency.code as foreign_currency_code',
'foreign_currency.name as foreign_currency_name',
@@ -744,7 +746,7 @@ class GroupCollector implements GroupCollectorInterface
$currentCollection = $collection;
$countFilters = count($this->postFilters);
$countCollection = count($currentCollection);
if (0 === $countFilters && 0 === $countCollection) {
if (0 === $countFilters) {
return $currentCollection;
}
app('log')->debug(sprintf('GroupCollector: postFilterCollection has %d filter(s) and %d transaction(s).', count($this->postFilters), count($currentCollection)));
@@ -872,6 +874,16 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
* Limit results to a specific currency, only normal one.
*/
public function setNormalCurrency(TransactionCurrency $currency): GroupCollectorInterface
{
$this->query->where('source.transaction_currency_id', $currency->id);
return $this;
}
public function setEndRow(int $endRow): self
{
$this->endRow = $endRow;

View File

@@ -457,6 +457,11 @@ interface GroupCollectorInterface
*/
public function setCurrency(TransactionCurrency $currency): self;
/**
* Limit results to a specific currency, either foreign or normal one.
*/
public function setNormalCurrency(TransactionCurrency $currency): self;
/**
* Set destination accounts.
*/
@@ -732,4 +737,6 @@ interface GroupCollectorInterface
public function yearIs(string $year): self;
public function yearIsNot(string $year): self;
public function accountBalanceIs(string $direction, string $operator, string $value): self;
}

View File

@@ -33,7 +33,8 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
@@ -65,82 +66,53 @@ class NetWorth implements NetWorthInterface
public function byAccounts(Collection $accounts, Carbon $date): array
{
// start in the past, end in the future? use $date
$ids = implode(',', $accounts->pluck('id')->toArray());
$cache = new CacheProperties();
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$ids = implode(',', $accounts->pluck('id')->toArray());
$cache = new CacheProperties();
$cache->addProperty($date);
$cache->addProperty($convertToNative);
$cache->addProperty('net-worth-by-accounts');
$cache->addProperty($ids);
if ($cache->has()) {
return $cache->get();
// return $cache->get();
}
app('log')->debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d')));
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
$default = app('amount')->getDefaultCurrency();
$converter = new ExchangeRateConverter();
// default "native" currency has everything twice, for consistency.
$netWorth = [
'native' => [
'balance' => '0',
'native_balance' => '0',
'currency_id' => $default->id,
'currency_code' => $default->code,
'currency_name' => $default->name,
'currency_symbol' => $default->symbol,
'currency_decimal_places' => $default->decimal_places,
'native_currency_id' => $default->id,
'native_currency_code' => $default->code,
'native_currency_name' => $default->name,
'native_currency_symbol' => $default->symbol,
'native_currency_decimal_places' => $default->decimal_places,
],
];
$balances = app('steam')->balancesByAccountsConverted($accounts, $date);
Log::debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d H:i:s')));
$default = Amount::getDefaultCurrency();
$netWorth = [];
$balances = Steam::finalAccountsBalance($accounts, $date);
/** @var Account $account */
foreach ($accounts as $account) {
app('log')->debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name));
$currency = $this->getRepository()->getAccountCurrency($account);
if (null === $currency) {
$currency = app('amount')->getDefaultCurrency();
}
$currencyCode = $currency->code;
$balance = '0';
$nativeBalance = '0';
Log::debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name));
$currency = $this->getRepository()->getAccountCurrency($account) ?? $default;
$useNative = $convertToNative && $default->id !== $currency->id;
$currency = $useNative ? $default : $currency;
$currencyCode = $currency->code;
$balance = '0';
$nativeBalance = '0';
if (array_key_exists($account->id, $balances)) {
$balance = $balances[$account->id]['balance'] ?? '0';
$nativeBalance = $balances[$account->id]['native_balance'] ?? '0';
}
app('log')->debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance));
// always subtract virtual balance
$virtualBalance = $account->virtual_balance;
if ('' !== $virtualBalance) {
$balance = bcsub($balance, $virtualBalance);
$nativeVirtualBalance = $converter->convert($default, $currency, $account->created_at, $virtualBalance);
$nativeBalance = bcsub($nativeBalance, $nativeVirtualBalance);
}
Log::debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance));
// always subtract virtual balance again.
$balance = '' !== (string) $account->virtual_balance ? bcsub($balance, $account->virtual_balance) : $balance;
$nativeBalance = '' !== (string) $account->native_virtual_balance ? bcsub($nativeBalance, $account->native_virtual_balance) : $nativeBalance;
$amountToUse = $useNative ? $nativeBalance : $balance;
Log::debug(sprintf('Will use %s %s', $currencyCode, $amountToUse));
$netWorth[$currencyCode] ??= [
'balance' => '0',
'native_balance' => '0',
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => (string) $default->id,
'native_currency_code' => $default->code,
'native_currency_name' => $default->name,
'native_currency_symbol' => $default->symbol,
'native_currency_decimal_places' => $default->decimal_places,
'balance' => '0',
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
];
$netWorth[$currencyCode]['balance'] = bcadd($balance, $netWorth[$currencyCode]['balance']);
$netWorth[$currencyCode]['native_balance'] = bcadd($nativeBalance, $netWorth[$currencyCode]['native_balance']);
$netWorth['native']['balance'] = bcadd($nativeBalance, $netWorth['native']['balance']);
$netWorth['native']['native_balance'] = bcadd($nativeBalance, $netWorth['native']['native_balance']);
$netWorth[$currencyCode]['balance'] = bcadd($amountToUse, $netWorth[$currencyCode]['balance']);
}
$cache->store($netWorth);
$converter->summarize();
return $netWorth;
}
@@ -187,10 +159,10 @@ class NetWorth implements NetWorthInterface
*/
$accounts = $this->getAccounts();
$return = [];
$balances = app('steam')->balancesByAccounts($accounts, $date);
$balances = Steam::finalAccountsBalance($accounts, $date);
foreach ($accounts as $account) {
$currency = $this->getRepository()->getAccountCurrency($account);
$balance = $balances[$account->id] ?? '0';
$balance = $balances[$account->id]['balance'] ?? '0';
// always subtract virtual balance.
$virtualBalance = $account->virtual_balance;

View File

@@ -27,6 +27,7 @@ use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Controllers\BasicDataSupport;
use Illuminate\Contracts\View\Factory;
@@ -89,16 +90,18 @@ class IndexController extends Controller
$start->subDay();
$ids = $accounts->pluck('id')->toArray();
$startBalances = app('steam')->balancesByAccounts($accounts, $start);
$endBalances = app('steam')->balancesByAccounts($accounts, $end);
$startBalances = app('steam')->finalAccountsBalance($accounts, $start);
$endBalances = app('steam')->finalAccountsBalance($accounts, $end);
$activities = app('steam')->getLastActivities($ids);
$accounts->each(
function (Account $account) use ($activities, $startBalances, $endBalances): void {
$currency = $this->repository->getAccountCurrency($account);
$account->lastActivityDate = $this->isInArrayDate($activities, $account->id);
$account->startBalance = $this->isInArray($startBalances, $account->id);
$account->endBalance = $this->isInArray($endBalances, $account->id);
$account->difference = bcsub($account->endBalance, $account->startBalance);
$account->startBalances = $this->getBalance($account, $currency, $startBalances);
$account->endBalances = $this->getBalance($account, $currency, $endBalances);
$account->differences = $this->subtract($account->startBalances, $account->endBalances);
$account->interest = app('steam')->bcround($this->repository->getMetaValue($account, 'interest'), 4);
$account->interestPeriod = (string) trans(sprintf('firefly.interest_calc_%s', $this->repository->getMetaValue($account, 'interest_period')));
$account->accountTypeString = (string) trans(sprintf('firefly.account_type_%s', $account->accountType->type));
@@ -149,20 +152,21 @@ class IndexController extends Controller
$start->subDay();
$ids = $accounts->pluck('id')->toArray();
$startBalances = app('steam')->balancesByAccounts($accounts, $start);
$endBalances = app('steam')->balancesByAccounts($accounts, $end);
$startBalances = app('steam')->finalAccountsBalance($accounts, $start);
$endBalances = app('steam')->finalAccountsBalance($accounts, $end);
$activities = app('steam')->getLastActivities($ids);
$accounts->each(
function (Account $account) use ($activities, $startBalances, $endBalances): void {
$interest = (string) $this->repository->getMetaValue($account, 'interest');
$interest = '' === $interest ? '0' : $interest;
$currency = $this->repository->getAccountCurrency($account);
// See reference nr. 68
$account->startBalances = $this->getBalance($account, $currency, $startBalances);
$account->endBalances = $this->getBalance($account, $currency, $endBalances);
$account->differences = $this->subtract($account->startBalances, $account->endBalances);
$account->lastActivityDate = $this->isInArrayDate($activities, $account->id);
$account->startBalance = $this->isInArray($startBalances, $account->id);
$account->endBalance = $this->isInArray($endBalances, $account->id);
$account->difference = bcsub($account->endBalance, $account->startBalance);
$account->interest = app('steam')->bcround($interest, 4);
$account->interestPeriod = (string) trans(
sprintf('firefly.interest_calc_%s', $this->repository->getMetaValue($account, 'interest_period'))
@@ -171,7 +175,10 @@ class IndexController extends Controller
$account->location = $this->repository->getLocation($account);
$account->liability_direction = $this->repository->getMetaValue($account, 'liability_direction');
$account->current_debt = $this->repository->getMetaValue($account, 'current_debt') ?? '-';
$account->currency = $currency;
$account->iban = implode(' ', str_split((string) $account->iban, 4));
}
);
// make paginator:
@@ -186,4 +193,31 @@ class IndexController extends Controller
return view('accounts.index', compact('objectType', 'inactiveCount', 'subTitleIcon', 'subTitle', 'page', 'accounts'));
}
private function getBalance(Account $account, ?TransactionCurrency $currency, array $balances): array
{
if (!array_key_exists($account->id, $balances)) {
return [];
}
$set = $balances[$account->id];
if ($this->convertToNative && $this->defaultCurrency->id === $currency?->id) {
unset($set['native_balance'], $set[$this->defaultCurrency->code]);
}
if ($this->convertToNative && null !== $currency && $this->defaultCurrency->id !== $currency->id) {
unset($set['balance']);
}
return $set;
}
private function subtract(array $startBalances, array $endBalances)
{
$result = [];
foreach ($endBalances as $key => $value) {
$result[$key] = bcsub($value, $startBalances[$key] ?? '0');
}
return $result;
}
}

View File

@@ -34,6 +34,7 @@ use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
@@ -110,8 +111,8 @@ class ReconcileController extends Controller
$startDate = clone $start;
$startDate->subDay();
$startBalance = app('steam')->bcround(app('steam')->balance($account, $startDate), $currency->decimal_places);
$endBalance = app('steam')->bcround(app('steam')->balance($account, $end), $currency->decimal_places);
$startBalance = Steam::finalAccountBalance($account, $startDate)['balance'];
$endBalance = Steam::finalAccountBalance($account, $end)['balance'];
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type));
$subTitle = (string) trans('firefly.reconcile_account', ['account' => $account->name]);

View File

@@ -30,6 +30,7 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\PeriodOverview;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
@@ -128,7 +129,7 @@ class ShowController extends Controller
$groups->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]));
$showAll = false;
$balance = app('steam')->balance($account, $end);
$balance = Steam::finalAccountBalance($account, $end)['balance'] ?? '0'; // TODO fix me
return view(
'accounts.show',
@@ -190,7 +191,7 @@ class ShowController extends Controller
$groups->setPath(route('accounts.show.all', [$account->id]));
$chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$showAll = true;
$balance = app('steam')->balance($account, $end);
$balance = Steam::finalAccountBalance($account, $end)['balance'];
return view(
'accounts.show',

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
@@ -35,6 +36,8 @@ use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\AugumentData;
use FireflyIII\Support\Http\Controllers\ChartGeneration;
use FireflyIII\Support\Http\Controllers\DateCalculation;
@@ -80,6 +83,8 @@ class AccountController extends Controller
*/
public function expenseAccounts(): JsonResponse
{
Log::debug('RevenueAccounts');
/** @var Carbon $start */
$start = clone session('start', today(config('app.timezone'))->startOfMonth());
@@ -88,6 +93,7 @@ class AccountController extends Controller
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($this->convertToNative);
$cache->addProperty('chart.account.expense-accounts');
if ($cache->has()) {
return response()->json($cache->get());
@@ -98,38 +104,67 @@ class AccountController extends Controller
$currencies = [];
$chartData = [];
$tempData = [];
$default = Amount::getDefaultCurrency();
// grab all accounts and names
$accounts = $this->accountRepository->getAccountsByType([AccountType::EXPENSE]);
$accounts = $this->accountRepository->getAccountsByType([AccountTypeEnum::EXPENSE->value]);
$accountNames = $this->extractNames($accounts);
// grab all balances
$startBalances = app('steam')->balancesPerCurrencyByAccounts($accounts, $start);
$endBalances = app('steam')->balancesPerCurrencyByAccounts($accounts, $end);
$startBalances = app('steam')->finalAccountsBalance($accounts, $start);
$endBalances = app('steam')->finalAccountsBalance($accounts, $end);
// loop the end balances. This is an array for each account ($expenses)
foreach ($endBalances as $accountId => $expenses) {
$accountId = (int) $accountId;
// loop each expense entry (each entry can be a different currency).
foreach ($expenses as $currencyId => $endAmount) {
$currencyId = (int) $currencyId;
// loop the accounts, then check for balance and currency info.
foreach ($accounts as $account) {
Log::debug(sprintf('Now in account #%d ("%s")', $account->id, $account->name));
$expenses = $endBalances[$account->id] ?? false;
if (false === $expenses) {
Log::error(sprintf('Found no end balance for account #%d', $account->id));
continue;
}
/**
* @var string $key
* @var string $endBalance
*/
foreach ($expenses as $key => $endBalance) {
if (!$this->convertToNative && 'native_balance' === $key) {
Log::debug(sprintf('[a] Will skip expense array "%s"', $key));
continue;
}
if ($this->convertToNative && 'native_balance' !== $key) {
Log::debug(sprintf('[b] Will skip expense array "%s"', $key));
continue;
}
Log::debug(sprintf('Will process expense array "%s" with amount %s', $key, $endBalance));
$searchCode = $this->convertToNative ? $default->code : $key;
Log::debug(sprintf('Search code is %s', $searchCode));
// see if there is an accompanying start amount.
// grab the difference and find the currency.
$startAmount = (string) ($startBalances[$accountId][$currencyId] ?? '0');
$diff = bcsub((string) $endAmount, $startAmount);
$currencies[$currencyId] ??= $this->currencyRepository->find($currencyId);
$startBalance = ($startBalances[$account->id][$key] ?? '0');
Log::debug(sprintf('Start balance is %s', $startBalance));
$diff = bcsub($endBalance, $startBalance);
$currencies[$searchCode] ??= $this->currencyRepository->findByCode($searchCode);
if (0 !== bccomp($diff, '0')) {
// store the values in a temporary array.
$tempData[] = [
'name' => $accountNames[$accountId],
'name' => $accountNames[$account->id],
'difference' => $diff,
'diff_float' => (float) $diff, // intentional float
'currency_id' => $currencyId,
'currency_id' => $currencies[$searchCode]->id,
];
}
}
}
// recreate currencies, but on ID instead of code.
$newCurrencies = [];
foreach ($currencies as $currency) {
$newCurrencies[$currency->id] = $currency;
}
$currencies = $newCurrencies;
// sort temp array by amount.
$amounts = array_column($tempData, 'diff_float');
@@ -156,7 +191,7 @@ class AccountController extends Controller
foreach ($tempData as $entry) {
$currencyId = $entry['currency_id'];
$name = $entry['name'];
$chartData[$currencyId]['entries'][$name] = $entry['difference'];
$chartData[$currencyId]['entries'][$name] = (float) $entry['difference'];
}
$data = $this->generator->multiSet($chartData);
@@ -300,7 +335,7 @@ class AccountController extends Controller
{
$start = clone session('start', today(config('app.timezone'))->startOfMonth());
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
$defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray();
$defaultSet = $repository->getAccountsByType([AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
Log::debug('Default set is ', $defaultSet);
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
$frontpageArray = !is_array($frontpage->data) ? [] : $frontpage->data;
@@ -435,11 +470,11 @@ class AccountController extends Controller
if ('1D' === $step) {
// per day the entire period, balance for every day.
$format = (string) trans('config.month_and_day_js', [], $locale);
$range = app('steam')->balanceInRange($account, $start, $end, $currency);
$range = app('steam')->finalAccountBalanceInRange($account, $start, $end);
$previous = array_values($range)[0];
while ($end >= $current) {
$theDate = $current->format('Y-m-d');
$balance = $range[$theDate] ?? $previous;
$balance = $range[$theDate]['balance'] ?? $previous;
$label = $current->isoFormat($format);
$entries[$label] = (float) $balance;
$previous = $balance;
@@ -449,7 +484,7 @@ class AccountController extends Controller
if ('1W' === $step || '1M' === $step || '1Y' === $step) {
while ($end >= $current) {
Log::debug(sprintf('Current is: %s', $current->format('Y-m-d')));
$balance = (float) app('steam')->balance($account, $current, $currency);
$balance = Steam::finalAccountBalance($account, $current)[$currency->code] ?? '0';
$label = app('navigation')->periodShow($current, $step);
$entries[$label] = $balance;
$current = app('navigation')->addPeriod($current, $step, 0);
@@ -489,6 +524,7 @@ class AccountController extends Controller
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($this->convertToNative);
$cache->addProperty('chart.account.revenue-accounts');
if ($cache->has()) {
return response()->json($cache->get());
@@ -499,39 +535,71 @@ class AccountController extends Controller
$currencies = [];
$chartData = [];
$tempData = [];
$default = Amount::getDefaultCurrency();
// grab all accounts and names
$accounts = $this->accountRepository->getAccountsByType([AccountType::REVENUE]);
$accounts = $this->accountRepository->getAccountsByType([AccountTypeEnum::REVENUE->value]);
$accountNames = $this->extractNames($accounts);
// grab all balances
$startBalances = app('steam')->balancesPerCurrencyByAccounts($accounts, $start);
$endBalances = app('steam')->balancesPerCurrencyByAccounts($accounts, $end);
$startBalances = app('steam')->finalAccountsBalance($accounts, $start);
$endBalances = app('steam')->finalAccountsBalance($accounts, $end);
// loop the end balances. This is an array for each account ($expenses)
foreach ($endBalances as $accountId => $expenses) {
$accountId = (int) $accountId;
// loop each expense entry (each entry can be a different currency).
foreach ($expenses as $currencyId => $endAmount) {
$currencyId = (int) $currencyId;
// loop the accounts, then check for balance and currency info.
foreach ($accounts as $account) {
Log::debug(sprintf('Now in account #%d ("%s")', $account->id, $account->name));
$expenses = $endBalances[$account->id] ?? false;
if (false === $expenses) {
Log::error(sprintf('Found no end balance for account #%d', $account->id));
continue;
}
/**
* @var string $key
* @var string $endBalance
*/
foreach ($expenses as $key => $endBalance) {
if (!$this->convertToNative && 'native_balance' === $key) {
Log::debug(sprintf('[a] Will skip expense array "%s"', $key));
continue;
}
if ($this->convertToNative && 'native_balance' !== $key) {
Log::debug(sprintf('[b] Will skip expense array "%s"', $key));
continue;
}
Log::debug(sprintf('Will process expense array "%s" with amount %s', $key, $endBalance));
$searchCode = $this->convertToNative ? $default->code : $key;
Log::debug(sprintf('Search code is %s', $searchCode));
// see if there is an accompanying start amount.
// grab the difference and find the currency.
$startAmount = (string) ($startBalances[$accountId][$currencyId] ?? '0');
$diff = bcsub((string) $endAmount, $startAmount);
$currencies[$currencyId] ??= $this->currencyRepository->find($currencyId);
$startBalance = ($startBalances[$account->id][$key] ?? '0');
Log::debug(sprintf('Start balance is %s', $startBalance));
$diff = bcsub($endBalance, $startBalance);
$currencies[$searchCode] ??= $this->currencyRepository->findByCode($searchCode);
if (0 !== bccomp($diff, '0')) {
// store the values in a temporary array.
$tempData[] = [
'name' => $accountNames[$accountId],
'name' => $accountNames[$account->id],
'difference' => $diff,
'diff_float' => (float) $diff, // intentional float
'currency_id' => $currencyId,
'currency_id' => $currencies[$searchCode]->id,
];
}
}
}
// recreate currencies, but on ID instead of code.
$newCurrencies = [];
foreach ($currencies as $currency) {
$newCurrencies[$currency->id] = $currency;
}
$currencies = $newCurrencies;
// sort temp array by amount.
$amounts = array_column($tempData, 'diff_float');
array_multisort($amounts, SORT_ASC, $tempData);

View File

@@ -38,10 +38,12 @@ use FireflyIII\Repositories\Budget\NoBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Chart\Budget\FrontpageChartGenerator;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Controllers\AugumentData;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class BudgetController.
@@ -374,25 +376,27 @@ class BudgetController extends Controller
*/
public function frontpage(): JsonResponse
{
$start = session('start', today(config('app.timezone'))->startOfMonth());
$end = session('end', today(config('app.timezone'))->endOfMonth());
$start = session('start', today(config('app.timezone'))->startOfMonth());
$end = session('end', today(config('app.timezone'))->endOfMonth());
// chart properties for cache:
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($this->convertToNative);
$cache->addProperty('chart.budget.frontpage');
if ($cache->has()) {
return response()->json($cache->get());
// return response()->json($cache->get());
}
$chartGenerator = app(FrontpageChartGenerator::class);
Log::debug('Regenerate frontpage chart from scratch.');
$chartGenerator = app(FrontpageChartGenerator::class);
$chartGenerator->setUser(auth()->user());
$chartGenerator->setStart($start);
$chartGenerator->setEnd($end);
$chartGenerator->convertToNative = $this->convertToNative;
$chartGenerator->default = Amount::getDefaultCurrency();
$chartData = $chartGenerator->generate();
$data = $this->generator->multiSet($chartData);
$chartData = $chartGenerator->generate();
$data = $this->generator->multiSet($chartData);
$cache->store($data);
return response()->json($data);

View File

@@ -49,8 +49,7 @@ class CategoryController extends Controller
use ChartGeneration;
use DateCalculation;
/** @var GeneratorInterface Chart generation methods. */
protected $generator;
protected GeneratorInterface $generator;
/**
* CategoryController constructor.
@@ -111,9 +110,10 @@ class CategoryController extends Controller
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($this->convertToNative);
$cache->addProperty('chart.category.frontpage');
if ($cache->has()) {
return response()->json($cache->get());
// return response()->json($cache->get());
}
$frontpageGenerator = new FrontpageChartGenerator($start, $end);

View File

@@ -23,12 +23,15 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\RequestInformation;
use FireflyIII\Support\Http\Controllers\UserNavigation;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\View;
use Route;
/**
@@ -50,6 +53,8 @@ abstract class Controller extends BaseController
protected string $dateTimeFormat;
protected string $monthAndDayFormat;
protected bool $convertToNative = false;
protected ?TransactionCurrency $defaultCurrency;
protected string $monthFormat;
protected string $redirectUrl = '/';
@@ -61,13 +66,13 @@ abstract class Controller extends BaseController
// is site a demo site?
$isDemoSiteConfig = app('fireflyconfig')->get('is_demo_site', config('firefly.configuration.is_demo_site', false));
$isDemoSite = (bool) $isDemoSiteConfig->data;
app('view')->share('IS_DEMO_SITE', $isDemoSite);
app('view')->share('DEMO_USERNAME', config('firefly.demo_username'));
app('view')->share('DEMO_PASSWORD', config('firefly.demo_password'));
app('view')->share('FF_VERSION', config('firefly.version'));
View::share('IS_DEMO_SITE', $isDemoSite);
View::share('DEMO_USERNAME', config('firefly.demo_username'));
View::share('DEMO_PASSWORD', config('firefly.demo_password'));
View::share('FF_VERSION', config('firefly.version'));
// is webhooks enabled?
app('view')->share('featuringWebhooks', true === config('firefly.feature_flags.webhooks') && true === config('firefly.allow_webhooks'));
View::share('featuringWebhooks', true === config('firefly.feature_flags.webhooks') && true === config('firefly.allow_webhooks'));
// share custom auth guard info.
$authGuard = config('firefly.authentication_guard');
@@ -75,17 +80,17 @@ abstract class Controller extends BaseController
// overrule v2 layout back to v1.
if ('true' === request()->get('force_default_layout') && 'v2' === config('view.layout')) {
app('view')->getFinder()->setPaths([realpath(base_path('resources/views'))]); // @phpstan-ignore-line
View::getFinder()->setPaths([realpath(base_path('resources/views'))]); // @phpstan-ignore-line
}
app('view')->share('authGuard', $authGuard);
app('view')->share('logoutUrl', $logoutUrl);
View::share('authGuard', $authGuard);
View::share('logoutUrl', $logoutUrl);
// upload size
$maxFileSize = app('steam')->phpBytes((string) ini_get('upload_max_filesize'));
$maxPostSize = app('steam')->phpBytes((string) ini_get('post_max_size'));
$maxFileSize = Steam::phpBytes((string) ini_get('upload_max_filesize'));
$maxPostSize = Steam::phpBytes((string) ini_get('post_max_size'));
$uploadSize = min($maxFileSize, $maxPostSize);
app('view')->share('uploadSize', $uploadSize);
View::share('uploadSize', $uploadSize);
// share is alpha, is beta
$isAlpha = false;
@@ -98,31 +103,35 @@ abstract class Controller extends BaseController
$isBeta = true;
}
app('view')->share('FF_IS_ALPHA', $isAlpha);
app('view')->share('FF_IS_BETA', $isBeta);
View::share('FF_IS_ALPHA', $isAlpha);
View::share('FF_IS_BETA', $isBeta);
$this->middleware(
function ($request, $next): mixed {
$locale = app('steam')->getLocale();
$locale = Steam::getLocale();
// translations for specific strings:
$this->monthFormat = (string) trans('config.month_js', [], $locale);
$this->monthAndDayFormat = (string) trans('config.month_and_day_js', [], $locale);
$this->dateTimeFormat = (string) trans('config.date_time_js', [], $locale);
$darkMode = 'browser';
$this->defaultCurrency =null;
// get shown-intro-preference:
if (auth()->check()) {
$language = app('steam')->getLanguage();
$locale = app('steam')->getLocale();
$this->defaultCurrency = app('amount')->getDefaultCurrency();
$language = Steam::getLanguage();
$locale = Steam::getLocale();
$darkMode = app('preferences')->get('darkMode', 'browser')->data;
$this->convertToNative =app('preferences')->get('convert_to_native', false)->data;
$page = $this->getPageName();
$shownDemo = $this->hasSeenDemo();
app('view')->share('language', $language);
app('view')->share('locale', $locale);
app('view')->share('shownDemo', $shownDemo);
app('view')->share('current_route_name', $page);
app('view')->share('original_route_name', Route::currentRouteName());
View::share('language', $language);
View::share('locale', $locale);
View::share('convertToNative', $this->convertToNative);
View::share('shownDemo', $shownDemo);
View::share('current_route_name', $page);
View::share('original_route_name', Route::currentRouteName());
}
app('view')->share('darkMode', $darkMode);
View::share('darkMode', $darkMode);
return $next($request);
}

View File

@@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Models\AccountType;
@@ -40,8 +39,10 @@ use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use Illuminate\View\View;
use Monolog\Handler\RotatingFileHandler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class DebugController
@@ -59,6 +60,65 @@ class DebugController extends Controller
$this->middleware(IsDemoUser::class)->except(['displayError']);
}
public function routes(): never
{
if (!auth()->user()->hasRole('owner')) {
throw new NotFoundHttpException();
}
$routes = Route::getRoutes();
$return = [];
/** @var \Illuminate\Routing\Route $route */
foreach ($routes as $route) {
// skip API and other routes.
if (
str_starts_with($route->uri(), 'api')
|| str_starts_with($route->uri(), '_debugbar')
|| str_starts_with($route->uri(), '_ignition')
|| str_starts_with($route->uri(), 'oauth')
|| str_starts_with($route->uri(), 'sanctum')
) {
continue;
}
// skip non GET routes
if (!in_array('GET', $route->methods(), true)) {
continue;
}
// no name route:
if (null === $route->getName()) {
var_dump($route);
exit;
}
if (!str_contains($route->uri(), '{')) {
$return[$route->getName()] = route($route->getName());
continue;
}
$params = [];
foreach ($route->parameterNames() as $name) {
$params[] = $this->getParameter($name);
}
$return[$route->getName()] = route($route->getName(), $params);
}
$count = 0;
echo '<hr>';
echo '<h1>Routes</h1>';
echo sprintf('<h2>%s</h2>', $count);
foreach ($return as $name => $path) {
echo sprintf('<a href="%1$s">%2$s</a><br>', $path, $name).PHP_EOL;
++$count;
if (0 === $count % 10) {
echo '<hr>';
echo sprintf('<h2>%s</h2>', $count);
}
}
exit;
var_dump($return);
}
/**
* Show all possible errors.
*
@@ -344,4 +404,130 @@ class DebugController extends Controller
return redirect(route('home'));
}
private function getParameter(string $name): string
{
switch ($name) {
default:
throw new FireflyException(sprintf('Unknown parameter "%s"', $name));
case 'cliToken':
case 'token':
case 'code':
case 'oldAddressHash':
return 'fake-token';
case 'objectType':
return 'asset';
case 'account':
return '1';
case 'start_date':
return '20241201';
case 'end_date':
return '20241231';
case 'attachment':
return '1';
case 'bill':
return '1';
case 'budget':
return '1';
case 'budgetLimit':
return '1';
case 'category':
return '1';
case 'currency':
return '1';
case 'fromCurrencyCode':
return 'EUR';
case 'toCurrencyCode':
return 'USD';
case 'accountList':
return '1,6';
case 'budgetList':
return '1,2';
case 'categoryList':
return '1,2';
case 'doubleList':
return '1,2';
case 'tagList':
return '1,2';
case 'tag':
return '1';
case 'piggyBank':
return '1';
case 'objectGroup':
return '1';
case 'route':
return 'accounts';
case 'specificPage':
return 'show';
case 'recurrence':
return '1';
case 'tj':
return '1';
case 'reportType':
return 'default';
case 'ruleGroup':
return '1';
case 'rule':
return '1';
case 'tagOrId':
return '1';
case 'transactionGroup':
return '1';
case 'journalList':
return '1,2';
case 'transactionType':
return 'withdrawal';
case 'journalLink':
return '1';
case 'webhook':
return '1';
case 'user':
return '1';
case 'linkType':
return '1';
case 'userGroup':
return '1';
case 'date':
return '20241201';
}
}
}

View File

@@ -120,7 +120,6 @@ class HomeController extends Controller
*/
public function index(AccountRepositoryInterface $repository): mixed
{
$types = config('firefly.accountTypesByIdentifier.asset');
$count = $repository->count($types);
Log::channel('audit')->info('User visits homepage.');

View File

@@ -24,18 +24,17 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Json;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse;
@@ -47,108 +46,13 @@ class BoxController extends Controller
use DateCalculation;
/**
* This box has three types of info to display:
* 0) If the user has available amount this period and has overspent: overspent box.
* 1) If the user has available amount this period and has NOT overspent: left to spend box.
* 2) if the user has no available amount set this period: spent per day
* Deprecated method, no longer in use.
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @deprecated
*/
public function available(): JsonResponse
{
app('log')->debug('Now in available()');
/** @var OperationsRepositoryInterface $opsRepository */
$opsRepository = app(OperationsRepositoryInterface::class);
/** @var AvailableBudgetRepositoryInterface $abRepository */
$abRepository = app(AvailableBudgetRepositoryInterface::class);
$abRepository->cleanup();
/** @var Carbon $start */
$start = session('start', today(config('app.timezone'))->startOfMonth());
/** @var Carbon $end */
$end = session('end', today(config('app.timezone'))->endOfMonth());
$today = today(config('app.timezone'));
$display = 2; // see method docs.
$boxTitle = (string) trans('firefly.spent');
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($today);
$cache->addProperty('box-available');
if ($cache->has()) {
return response()->json($cache->get());
}
$leftPerDayAmount = '0';
$leftToSpendAmount = '0';
$currency = app('amount')->getDefaultCurrency();
app('log')->debug(sprintf('Default currency is %s', $currency->code));
$availableBudgets = $abRepository->getAvailableBudgetsByExactDate($start, $end);
app('log')->debug(sprintf('Found %d available budget(s)', $availableBudgets->count()));
$availableBudgets = $availableBudgets->filter(
static function (AvailableBudget $availableBudget) use ($currency) { // @phpstan-ignore-line
if ($availableBudget->transaction_currency_id === $currency->id) {
app('log')->debug(sprintf(
'Will include AB #%d: from %s-%s amount %s',
$availableBudget->id,
$availableBudget->start_date->format('Y-m-d'),
$availableBudget->end_date->format('Y-m-d'),
$availableBudget->amount
));
return $availableBudget;
}
return null;
}
);
app('log')->debug(sprintf('Filtered back to %d available budgets', $availableBudgets->count()));
// spent in this period, in budgets, for default currency.
// also calculate spent per day.
$spent = $opsRepository->sumExpenses($start, $end, null, null, $currency);
$spentAmount = $spent[$currency->id]['sum'] ?? '0';
app('log')->debug(sprintf('Spent for default currency for all budgets in this period: %s', $spentAmount));
$days = (int) ($today->between($start, $end) ? $today->diffInDays($start, true) + 1 : $end->diffInDays($start, true) + 1);
app('log')->debug(sprintf('Number of days left: %d', $days));
$spentPerDay = bcdiv($spentAmount, (string) $days);
app('log')->debug(sprintf('Available to spend per day: %s', $spentPerDay));
if ($availableBudgets->count() > 0) {
$display = 0; // assume user overspent
$boxTitle = (string) trans('firefly.overspent');
$totalAvailableSum = (string) $availableBudgets->sum('amount');
app('log')->debug(sprintf('Total available sum is %s', $totalAvailableSum));
// calculate with available budget.
$leftToSpendAmount = bcadd($totalAvailableSum, $spentAmount);
app('log')->debug(sprintf('So left to spend is %s', $leftToSpendAmount));
if (bccomp($leftToSpendAmount, '0') >= 0) {
app('log')->debug('Left to spend is positive or zero!');
$boxTitle = (string) trans('firefly.left_to_spend');
$activeDaysLeft = $this->activeDaysLeft($start, $end); // see method description.
$display = 1; // not overspent
$leftPerDayAmount = 0 === $activeDaysLeft ? $leftToSpendAmount : bcdiv($leftToSpendAmount, (string) $activeDaysLeft);
app('log')->debug(sprintf('Left to spend per day is %s', $leftPerDayAmount));
}
}
$return = [
'display' => $display,
'spent_total' => app('amount')->formatAnything($currency, $spentAmount, false),
'spent_per_day' => app('amount')->formatAnything($currency, $spentPerDay, false),
'left_to_spend' => app('amount')->formatAnything($currency, $leftToSpendAmount, false),
'left_per_day' => app('amount')->formatAnything($currency, $leftPerDayAmount, false),
'title' => $boxTitle,
];
app('log')->debug('Final output', $return);
$cache->store($return);
app('log')->debug('Now done with available()');
return response()->json($return);
return response()->json([]);
}
/**
@@ -165,6 +69,7 @@ class BoxController extends Controller
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($this->convertToNative);
$cache->addProperty('box-balance');
if ($cache->has()) {
return response()->json($cache->get());
@@ -175,6 +80,7 @@ class BoxController extends Controller
$sums = [];
$currency = app('amount')->getDefaultCurrency();
// collect income of user:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
@@ -185,8 +91,8 @@ class BoxController extends Controller
/** @var array $journal */
foreach ($set as $journal) {
$currencyId = (int) $journal['currency_id'];
$amount = $journal['amount'] ?? '0';
$currencyId = $this->convertToNative ? $currency->id : (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
$incomes[$currencyId] ??= '0';
$incomes[$currencyId] = bcadd($incomes[$currencyId], app('steam')->positive($amount));
$sums[$currencyId] ??= '0';
@@ -197,17 +103,18 @@ class BoxController extends Controller
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)
->setTypes([TransactionType::WITHDRAWAL])
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
;
$set = $collector->getExtractedJournals();
/** @var array $journal */
foreach ($set as $journal) {
$currencyId = (int) $journal['currency_id'];
$currencyId = $this->convertToNative ? $currency->id : (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
$expenses[$currencyId] ??= '0';
$expenses[$currencyId] = bcadd($expenses[$currencyId], $journal['amount'] ?? '0');
$expenses[$currencyId] = bcadd($expenses[$currencyId], $amount);
$sums[$currencyId] ??= '0';
$sums[$currencyId] = bcadd($sums[$currencyId], $journal['amount']);
$sums[$currencyId] = bcadd($sums[$currencyId], $amount);
}
// format amounts:

View File

@@ -32,6 +32,7 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
@@ -193,8 +194,8 @@ class ReconcileController extends Controller
$end->endOfDay();
$currency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$startBalance = app('steam')->bcround(app('steam')->balance($account, $startDate), $currency->decimal_places);
$endBalance = app('steam')->bcround(app('steam')->balance($account, $end), $currency->decimal_places);
$startBalance = Steam::finalAccountBalance($account, $startDate)['balance'];
$endBalance = Steam::finalAccountBalance($account, $end)['balance'];
// get the transactions
$selectionStart = clone $start;

View File

@@ -103,6 +103,7 @@ class PreferencesController extends Controller
$darkMode = app('preferences')->get('darkMode', 'browser')->data;
$customFiscalYear = app('preferences')->get('customFiscalYear', 0)->data;
$fiscalYearStartStr = app('preferences')->get('fiscalYearStart', '01-01')->data;
$convertToNative = $this->convertToNative;
if (is_array($fiscalYearStartStr)) {
$fiscalYearStartStr = '01-01';
}
@@ -188,6 +189,7 @@ class PreferencesController extends Controller
'darkMode',
'availableDarkModes',
'notifications',
'convertToNative',
'slackUrl',
'locales',
'locale',
@@ -254,6 +256,10 @@ class PreferencesController extends Controller
app('preferences')->set('ntfy_auth', $all['ntfy_auth'] ?? false);
}
// convert native
$convertToNative = 1 === (int) $request->get('convertToNative');
app('preferences')->set('convert_to_native', $convertToNative);
// custom fiscal year
$customFiscalYear = 1 === (int) $request->get('customFiscalYear');
$string = strtotime((string) $request->get('fiscalYearStart'));

View File

@@ -34,6 +34,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Services\Internal\Update\JournalUpdateService;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\ModelInformation;
use FireflyIII\Transformers\TransactionGroupTransformer;
use FireflyIII\Validation\AccountValidator;
@@ -222,7 +223,7 @@ class ConvertController extends Controller
// group accounts:
/** @var Account $account */
foreach ($accountList as $account) {
$balance = app('steam')->balance($account, today());
$balance = Steam::finalAccountBalance($account, today()->endOfDay())['balance'];
$currency = $this->accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
$role = 'l_'.$account->accountType->type;
$key = (string) trans('firefly.opt_group_'.$role);
@@ -245,7 +246,7 @@ class ConvertController extends Controller
// group accounts:
/** @var Account $account */
foreach ($accountList as $account) {
$balance = app('steam')->balance($account, today());
$balance = Steam::finalAccountBalance($account, today()->endOfDay())['balance'];
$currency = $this->accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
$role = (string) $this->accountRepository->getMetaValue($account, 'account_role');
if ('' === $role) {

View File

@@ -61,7 +61,7 @@ class Account extends Model
'virtual_balance' => 'string',
];
protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban'];
protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban', 'native_virtual_balance'];
protected $hidden = ['encrypted'];
private bool $joinedAccountTypes = false;

View File

@@ -57,7 +57,7 @@ class BudgetLimit extends Model
'deleted' => Deleted::class,
];
protected $fillable = ['budget_id', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'amount', 'transaction_currency_id'];
protected $fillable = ['budget_id', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'amount', 'transaction_currency_id', 'native_amount'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -67,6 +67,8 @@ class Transaction extends Model
'transaction_journal_id',
'description',
'amount',
'native_amount',
'native_foreign_amount',
'identifier',
'transaction_currency_id',
'foreign_currency_id',

View File

@@ -37,25 +37,25 @@ class TransactionType extends Model
use ReturnsIntegerIdTrait;
use SoftDeletes;
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const string DEPOSIT = 'Deposit';
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const string INVALID = 'Invalid';
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const string LIABILITY_CREDIT = 'Liability credit';
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const string OPENING_BALANCE = 'Opening balance';
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const string RECONCILIATION = 'Reconciliation';
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const string TRANSFER = 'Transfer';
#[\Deprecated]
#[\Deprecated] /** @deprecated */
public const string WITHDRAWAL = 'Withdrawal';
protected $casts

View File

@@ -47,8 +47,8 @@ class AccountTasker implements AccountTaskerInterface
{
$yesterday = clone $start;
$yesterday->subDay();
$startSet = app('steam')->balancesByAccounts($accounts, $yesterday);
$endSet = app('steam')->balancesByAccounts($accounts, $end);
$startSet = app('steam')->finalAccountsBalance($accounts, $yesterday);
$endSet = app('steam')->finalAccountsBalance($accounts, $end);
app('log')->debug('Start of accountreport');
/** @var AccountRepositoryInterface $repository */
@@ -86,8 +86,8 @@ class AccountTasker implements AccountTaskerInterface
// get first journal date:
$first = $repository->oldestJournal($account);
$entry['start_balance'] = $startSet[$account->id] ?? '0';
$entry['end_balance'] = $endSet[$account->id] ?? '0';
$entry['start_balance'] = $startSet[$account->id]['balance'] ?? '0';
$entry['end_balance'] = $endSet[$account->id]['balance'] ?? '0';
// first journal exists, and is on start, then this is the actual opening balance:
if (null !== $first && $first->date->isSameDay($start) && TransactionType::OPENING_BALANCE === $first->transactionType->type) {

View File

@@ -37,6 +37,7 @@ use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
use FireflyIII\Services\Internal\Destroy\BillDestroyService;
use FireflyIII\Services\Internal\Update\BillUpdateService;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Query\JoinClause;
@@ -510,16 +511,19 @@ class BillRepository implements BillRepositoryInterface
public function sumPaidInRange(Carbon $start, Carbon $end): array
{
$bills = $this->getActiveBills();
$return = [];
Log::debug(sprintf('sumPaidInRange from %s to %s', $start->toW3cString(), $end->toW3cString()));
$bills = $this->getActiveBills();
$return = [];
$convertToNative = app('preferences')->getForUser($this->user, 'convert_to_native', false)->data;
$default = app('amount')->getDefaultCurrency();
/** @var Bill $bill */
foreach ($bills as $bill) {
/** @var Collection $set */
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
$currency = $bill->transactionCurrency;
$return[$currency->id] ??= [
/** @var Collection $set */
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;
$return[(int) $currency->id] ??= [
'id' => (string) $currency->id,
'name' => $currency->name,
'symbol' => $currency->symbol,
@@ -527,20 +531,15 @@ class BillRepository implements BillRepositoryInterface
'decimal_places' => $currency->decimal_places,
'sum' => '0',
];
$setAmount = '0';
/** @var TransactionJournal $transactionJournal */
foreach ($set as $transactionJournal) {
/** @var null|Transaction $sourceTransaction */
$sourceTransaction = $transactionJournal->transactions()->where('amount', '<', 0)->first();
if (null !== $sourceTransaction) {
$amount = $sourceTransaction->amount;
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) {
// use foreign amount instead!
$amount = (string) $sourceTransaction->foreign_amount;
}
$return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $amount);
}
$setAmount = bcadd($setAmount, Amount::getAmountFromJournalObject($transactionJournal));
}
Log::debug(sprintf('Bill #%d ("%s") with %d transaction(s) and sum %s %s', $bill->id, $bill->name, $set->count(), $currency->code, $setAmount));
$return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $setAmount);
Log::debug(sprintf('Total sum is now %s', $return[$currency->id]['sum']));
}
return $return;
@@ -558,21 +557,28 @@ class BillRepository implements BillRepositoryInterface
public function sumUnpaidInRange(Carbon $start, Carbon $end): array
{
app('log')->debug(sprintf('Now in sumUnpaidInRange("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d')));
$bills = $this->getActiveBills();
$return = [];
$bills = $this->getActiveBills();
$return = [];
$convertToNative = app('preferences')->getForUser($this->user, 'convert_to_native', false)->data;
$default = app('amount')->getDefaultCurrency();
/** @var Bill $bill */
foreach ($bills as $bill) {
// app('log')->debug(sprintf('Processing bill #%d ("%s")', $bill->id, $bill->name));
$dates = $this->getPayDatesInRange($bill, $start, $end);
$count = $bill->transactionJournals()->after($start)->before($end)->count();
$total = $dates->count() - $count;
$dates = $this->getPayDatesInRange($bill, $start, $end);
$count = $bill->transactionJournals()->after($start)->before($end)->count();
$total = $dates->count() - $count;
// app('log')->debug(sprintf('Pay dates: %d, count: %d, left: %d', $dates->count(), $count, $total));
// app('log')->debug('dates', $dates->toArray());
$minField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount_min' : 'amount_min';
$maxField = $convertToNative && $bill->transactionCurrency->id !== $default->id ? 'native_amount_max' : 'amount_max';
// Log::debug(sprintf('min field is %s, max field is %s', $minField, $maxField));
if ($total > 0) {
$currency = $bill->transactionCurrency;
$average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2');
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;
$average = bcdiv(bcadd($bill->{$maxField}, $bill->{$minField}), '2');
Log::debug(sprintf('Amount to pay is %s %s (%d times)', $currency->code, $average, $total));
$return[$currency->id] ??= [
'id' => (string) $currency->id,
'name' => $currency->name,

View File

@@ -133,9 +133,16 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
->where('end_date', $end->format('Y-m-d'))->get()
;
// use native amount if necessary?
$convertToNative = app('preferences')->getForUser($this->user, 'convert_to_native', false)->data;
$default = app('amount')->getDefaultCurrency();
/** @var AvailableBudget $availableBudget */
foreach ($availableBudgets as $availableBudget) {
$return[$availableBudget->transaction_currency_id] = $availableBudget->amount;
$currencyId = $convertToNative && $availableBudget->transaction_currency_id !== $default->id ? $default->id : $availableBudget->transaction_currency_id;
$field = $convertToNative && $availableBudget->transaction_currency_id !== $default->id ? 'native_amount' : 'amount';
$return[$currencyId] ??= '0';
$return[$currencyId] = bcadd($return[$currencyId], $availableBudget->{$field});
}
return $return;

View File

@@ -25,15 +25,18 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Budget;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Budget;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class OperationsRepository
@@ -208,19 +211,21 @@ class OperationsRepository implements OperationsRepositoryInterface
?Collection $budgets = null,
?TransactionCurrency $currency = null
): array {
// app('log')->debug(sprintf('Now in %s', __METHOD__));
$start->startOfDay();
$end->endOfDay();
// this collector excludes all transfers TO
// liabilities (which are also withdrawals)
// because those expenses only become expenses
// once they move from the liability to the friend.
Log::debug('Start of sumExpenses.');
// this collector excludes all transfers TO liabilities (which are also withdrawals)
// because those expenses only become expenses once they move from the liability to the friend.
// TODO this filter must be somewhere in AccountRepositoryInterface because I suspect its needed more often (A113)
$repository = app(AccountRepositoryInterface::class);
// 2024-12-24 disable the exclusion for now.
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($this->user);
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
// default currency information for native stuff.
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$default = app('amount')->getDefaultCurrency();
/** @var Account $account */
foreach ($subset as $account) {
@@ -230,11 +235,11 @@ class OperationsRepository implements OperationsRepositoryInterface
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)
->setRange($start, $end)
->excludeDestinationAccounts($selection)
->setTypes([TransactionType::WITHDRAWAL])
// ->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
;
if (null !== $accounts) {
@@ -244,56 +249,84 @@ class OperationsRepository implements OperationsRepositoryInterface
$budgets = $this->getBudgets();
}
if (null !== $currency) {
$collector->setCurrency($currency);
Log::debug(sprintf('Limit to currency %s', $currency->code));
$collector->setNormalCurrency($currency);
}
$collector->setBudgets($budgets);
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
// same but for foreign currencies:
// same but for transactions in the foreign currency:
if (null !== $currency) {
// app('log')->debug(sprintf('Currency is "%s".', $currency->name));
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])
->setForeignCurrency($currency)->setBudgets($budgets)
;
Log::debug('STOP looking for transactions in the foreign currency.');
if (null !== $accounts) {
$collector->setAccounts($accounts);
}
$result = $collector->getExtractedJournals();
// app('log')->debug(sprintf('Found %d journals with currency %s.', count($result), $currency->code));
// do not use array_merge because you want keys to overwrite (otherwise you get double results):
$journals = $result + $journals;
// Log::debug(sprintf('Look for transactions with foreign currency %s', $currency->code));
// // app('log')->debug(sprintf('Currency is "%s".', $currency->name));
// /** @var GroupCollectorInterface $collector */
// $collector = app(GroupCollectorInterface::class);
// $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value])->setForeignCurrency($currency)->setBudgets($budgets);
//
// if (null !== $accounts) {
// $collector->setAccounts($accounts);
// }
// $result = $collector->getExtractedJournals();
// // app('log')->debug(sprintf('Found %d journals with currency %s.', count($result), $currency->code));
// // do not use array_merge because you want keys to overwrite (otherwise you get double results):
// Log::debug(sprintf('Found %d extra journals in foreign currency.', count($result)));
// $journals = $result + $journals;
}
$array = [];
$array = [];
foreach ($journals as $journal) {
// Log::debug(sprintf('Journal #%d.', $journal['transaction_journal_id']));
// Log::debug(sprintf('Amounts: %1$s %2$s (amount), %3$s %4$s (foreign_amount), %5$s %6$s (native_amount) %5$s %7$s (foreign native amount)',
// $journal['currency_code'], $journal['amount'], $journal['foreign_currency_code'], $journal['foreign_amount'],
// $default->code, $journal['native_amount'], $journal['native_foreign_amount'])
// );
// TODO same as in category::sumexpenses
$amount = '0';
$currencyId = (int) $journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
if ($convertToNative) {
$useNative = $default->id !== (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
if ($useNative) {
Log::debug(sprintf('Journal #%d switches to native amount (original is %s)', $journal['transaction_journal_id'], $journal['currency_code']));
$currencyId = $default->id;
$currencyName = $default->name;
$currencySymbol = $default->symbol;
$currencyCode = $default->code;
$currencyDecimalPlaces = $default->decimal_places;
}
}
if (!$convertToNative) {
$amount = $journal['amount'];
// if the amount is not in $currency (but should be), use the foreign_amount if that one is correct.
// otherwise, ignore the transaction all together.
if (null !== $currency && $currencyId !== $currency->id && $currency->id === (int) $journal['foreign_currency_id']) {
Log::debug(sprintf('Journal #%d switches to foreign amount because it matches native.', $journal['transaction_journal_id']));
$amount = $journal['foreign_amount'];
$currencyId = (int) $journal['foreign_currency_id'];
$currencyName = $journal['foreign_currency_name'];
$currencySymbol = $journal['foreign_currency_symbol'];
$currencyCode = $journal['foreign_currency_code'];
$currencyDecimalPlaces = $journal['foreign_currency_decimal_places'];
}
}
$array[$currencyId] ??= [
'sum' => '0',
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
'currency_name' => $currencyName,
'currency_symbol' => $currencySymbol,
'currency_code' => $currencyCode,
'currency_decimal_places' => $currencyDecimalPlaces,
];
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount']));
// also do foreign amount:
$foreignId = (int) $journal['foreign_currency_id'];
if (0 !== $foreignId) {
$array[$foreignId] ??= [
'sum' => '0',
'currency_id' => $foreignId,
'currency_name' => $journal['foreign_currency_name'],
'currency_symbol' => $journal['foreign_currency_symbol'],
'currency_code' => $journal['foreign_currency_code'],
'currency_decimal_places' => $journal['foreign_currency_decimal_places'],
];
$array[$foreignId]['sum'] = bcadd($array[$foreignId]['sum'], app('steam')->negative($journal['foreign_amount']));
}
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($amount));
Log::debug(sprintf('Journal #%d adds amount %s %s', $journal['transaction_journal_id'], $currencyCode, $amount));
}
Log::debug('End of sumExpenses.', $array);
return $array;
}

View File

@@ -27,9 +27,11 @@ namespace FireflyIII\Repositories\Category;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class NoCategoryRepository
@@ -143,26 +145,54 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->withoutCategory();
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
// default currency information for native stuff.
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$default = app('amount')->getDefaultCurrency();
foreach ($journals as $journal) {
// Almost the same as in \FireflyIII\Repositories\Budget\OperationsRepository::sumExpenses
$amount = '0';
$currencyId = (int) $journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
if ($convertToNative) {
$useNative = $default->id !== (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
if ($useNative) {
$currencyId = $default->id;
$currencyName = $default->name;
$currencySymbol = $default->symbol;
$currencyCode = $default->code;
$currencyDecimalPlaces = $default->decimal_places;
}
Log::debug(sprintf('[a] Add amount %s %s', $currencyCode, $amount));
}
if (!$convertToNative) {
// ignore the amount in foreign currency.
Log::debug(sprintf('[b] Add amount %s %s', $currencyCode, $journal['amount']));
$amount = $journal['amount'];
}
$array[$currencyId] ??= [
'sum' => '0',
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
'currency_id' => (string) $currencyId,
'currency_name' => $currencyName,
'currency_symbol' => $currencySymbol,
'currency_code' => $currencyCode,
'currency_decimal_places' => $currencyDecimalPlaces,
];
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount'] ?? '0'));
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($amount));
}
return $array;

View File

@@ -25,11 +25,14 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Category;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class OperationsRepository
@@ -324,11 +327,12 @@ class OperationsRepository implements OperationsRepositoryInterface
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)
->setTypes([TransactionType::WITHDRAWAL])
;
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
// default currency information for native stuff.
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$default = app('amount')->getDefaultCurrency();
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
@@ -337,20 +341,47 @@ class OperationsRepository implements OperationsRepositoryInterface
}
$collector->setCategories($categories);
$collector->withCategoryInformation();
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
Log::debug(sprintf('Collected %d journals', count($journals)));
foreach ($journals as $journal) {
// Almost the same as in \FireflyIII\Repositories\Budget\OperationsRepository::sumExpenses
$amount = '0';
$currencyId = (int) $journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
if ($convertToNative) {
$useNative = $default->id !== (int) $journal['currency_id'];
$amount = Amount::getAmountFromJournal($journal);
if ($useNative) {
$currencyId = $default->id;
$currencyName = $default->name;
$currencySymbol = $default->symbol;
$currencyCode = $default->code;
$currencyDecimalPlaces = $default->decimal_places;
}
Log::debug(sprintf('[a] Add amount %s %s', $currencyCode, $amount));
}
if (!$convertToNative) {
// ignore the amount in foreign currency.
Log::debug(sprintf('[b] Add amount %s %s', $currencyCode, $journal['amount']));
$amount = $journal['amount'];
}
$array[$currencyId] ??= [
'sum' => '0',
'currency_id' => (string) $currencyId,
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => (int) $journal['currency_decimal_places'],
'currency_name' => $currencyName,
'currency_symbol' => $currencySymbol,
'currency_code' => $currencyCode,
'currency_decimal_places' => $currencyDecimalPlaces,
];
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount']));
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($amount));
}
return $array;

View File

@@ -35,6 +35,7 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
@@ -334,7 +335,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
public function leftOnAccount(PiggyBank $piggyBank, Account $account, Carbon $date): string
{
Log::debug(sprintf('leftOnAccount("%s","%s","%s")', $piggyBank->name, $account->name, $date->format('Y-m-d H:i:s')));
$balance = app('steam')->balanceConvertedIgnoreVirtual($account, $date, $piggyBank->transactionCurrency);
$balance = Steam::finalAccountBalance($account, $date)['balance'];
Log::debug(sprintf('Balance is: %s', $balance));
/** @var Collection $piggies */

View File

@@ -24,10 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Support;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class Amount.
@@ -45,6 +48,50 @@ class Amount
return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
}
/**
* Experimental function to see if we can quickly and quietly get the amount from a journal.
* This depends on the user's default currency and the wish to have it converted.
*/
public function getAmountFromJournal(array $journal): string
{
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$currency = app('amount')->getDefaultCurrency();
$field = $convertToNative && $currency->id !== $journal['currency_id'] ? 'native_amount' : 'amount';
$amount = $journal[$field] ?? '0';
// Log::debug(sprintf('Field is %s, amount is %s', $field, $amount));
// fallback, the transaction has a foreign amount in $currency.
if ($convertToNative && null !== $journal['foreign_amount'] && $currency->id === (int)$journal['foreign_currency_id']) {
$amount = $journal['foreign_amount'];
// Log::debug(sprintf('Overruled, amount is now %s', $amount));
}
return $amount;
}
/**
* Experimental function to see if we can quickly and quietly get the amount from a journal.
* This depends on the user's default currency and the wish to have it converted.
*/
public function getAmountFromJournalObject(TransactionJournal $journal): string
{
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$currency = app('amount')->getDefaultCurrency();
$field = $convertToNative && $currency->id !== $journal->transaction_currency_id ? 'native_amount' : 'amount';
/** @var null|Transaction $sourceTransaction */
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
if (null === $sourceTransaction) {
return '0';
}
$amount = $sourceTransaction->{$field};
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) {
// use foreign amount instead!
$amount = (string) $sourceTransaction->foreign_amount; // hard coded to be foreign amount.
}
return $amount;
}
/**
* This method will properly format the given number, in color or "black and white",
* as a currency, given two things: the currency required and the current locale.

View File

@@ -26,11 +26,13 @@ namespace FireflyIII\Support\Chart\Budget;
use Carbon\Carbon;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class FrontpageChartGenerator
@@ -43,6 +45,8 @@ class FrontpageChartGenerator
private Carbon $end;
private string $monthAndDayFormat;
private Carbon $start;
public bool $convertToNative = false;
public TransactionCurrency $default;
/**
* FrontpageChartGenerator constructor.
@@ -62,6 +66,7 @@ class FrontpageChartGenerator
*/
public function generate(): array
{
Log::debug('Now in generate for budget chart.');
$budgets = $this->budgetRepository->getActiveBudgets();
$data = [
['label' => (string) trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'],
@@ -74,6 +79,7 @@ class FrontpageChartGenerator
foreach ($budgets as $budget) {
$data = $this->processBudget($data, $budget);
}
Log::debug('DONE with generate budget chart.');
return $data;
}
@@ -85,15 +91,21 @@ class FrontpageChartGenerator
*/
private function processBudget(array $data, Budget $budget): array
{
Log::debug(sprintf('Now processing budget #%d ("%s")', $budget->id, $budget->name));
// get all limits:
$limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end);
Log::debug(sprintf('Found %d limit(s) for budget #%d.', $limits->count(), $budget->id));
// if no limits
if (0 === $limits->count()) {
return $this->noBudgetLimits($data, $budget);
}
$result = $this->noBudgetLimits($data, $budget);
Log::debug(sprintf('Now DONE processing budget #%d ("%s")', $budget->id, $budget->name));
return $this->budgetLimits($data, $budget, $limits);
return $result;
}
$result = $this->budgetLimits($data, $budget, $limits);
Log::debug(sprintf('Now DONE processing budget #%d ("%s")', $budget->id, $budget->name));
return $result;
}
/**
@@ -120,10 +132,13 @@ class FrontpageChartGenerator
*/
private function budgetLimits(array $data, Budget $budget, Collection $limits): array
{
Log::debug('Start processing budget limits.');
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
$data = $this->processLimit($data, $budget, $limit);
}
Log::debug('Done processing budget limits.');
return $data;
}
@@ -134,14 +149,29 @@ class FrontpageChartGenerator
*/
private function processLimit(array $data, Budget $budget, BudgetLimit $limit): array
{
$spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $limit->transactionCurrency);
$useNative = $this->convertToNative && $this->default->id !== $limit->transaction_currency_id;
$currency = $limit->transactionCurrency;
if ($useNative) {
Log::debug(sprintf('Processing limit #%d with (native) %s %s', $limit->id, $this->default->code, $limit->native_amount));
}
if (!$useNative) {
Log::debug(sprintf('Processing limit #%d with %s %s', $limit->id, $limit->transactionCurrency->code, $limit->amount));
}
$spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $currency);
Log::debug(sprintf('Spent array has %d entries.', count($spent)));
/** @var array $entry */
foreach ($spent as $entry) {
// only spent the entry where the entry's currency matches the budget limit's currency
if ($entry['currency_id'] === $limit->transaction_currency_id) {
// or when useNative is true.
if ($entry['currency_id'] === $limit->transaction_currency_id || $useNative) {
Log::debug(sprintf('Process spent row (%s)', $entry['currency_code']));
$data = $this->processRow($data, $budget, $limit, $entry);
}
if (!($entry['currency_id'] === $limit->transaction_currency_id || $useNative)) {
Log::debug(sprintf('Skipping spent row (%s).', $entry['currency_code']));
}
}
return $data;
@@ -156,6 +186,7 @@ class FrontpageChartGenerator
private function processRow(array $data, Budget $budget, BudgetLimit $limit, array $entry): array
{
$title = sprintf('%s (%s)', $budget->name, $entry['currency_name']);
Log::debug(sprintf('Title is "%s"', $title));
if ($limit->start_date->startOfDay()->ne($this->start->startOfDay()) || $limit->end_date->startOfDay()->ne($this->end->startOfDay())) {
$title = sprintf(
'%s (%s) (%s - %s)',
@@ -165,11 +196,26 @@ class FrontpageChartGenerator
$limit->end_date->isoFormat($this->monthAndDayFormat)
);
}
$sumSpent = bcmul($entry['sum'], '-1'); // spent
$useNative = $this->convertToNative && $this->default->id !== $limit->transaction_currency_id;
$amount = $limit->amount;
if ($useNative) {
$amount = $limit->native_amount;
}
$data[0]['entries'][$title] = 1 === bccomp($sumSpent, $limit->amount) ? $limit->amount : $sumSpent; // spent
$data[1]['entries'][$title] = 1 === bccomp($limit->amount, $sumSpent) ? bcadd($entry['sum'], $limit->amount) : '0'; // left to spent
$data[2]['entries'][$title] = 1 === bccomp($limit->amount, $sumSpent) ? '0' : bcmul(bcadd($entry['sum'], $limit->amount), '-1'); // overspent
$sumSpent = bcmul($entry['sum'], '-1'); // spent
$data[0]['entries'][$title] ??= '0';
$data[1]['entries'][$title] ??= '0';
$data[2]['entries'][$title] ??= '0';
$data[0]['entries'][$title] = bcadd($data[0]['entries'][$title], 1 === bccomp($sumSpent, $amount) ? $amount : $sumSpent); // spent
$data[1]['entries'][$title] = bcadd($data[1]['entries'][$title], 1 === bccomp($amount, $sumSpent) ? bcadd($entry['sum'], $amount) : '0'); // left to spent
$data[2]['entries'][$title] = bcadd($data[2]['entries'][$title], 1 === bccomp($amount, $sumSpent) ? '0' : bcmul(bcadd($entry['sum'], $amount), '-1')); // overspent
Log::debug(sprintf('Amount [spent] is now %s.', $data[0]['entries'][$title]));
Log::debug(sprintf('Amount [left] is now %s.', $data[1]['entries'][$title]));
Log::debug(sprintf('Amount [overspent] is now %s.', $data[2]['entries'][$title]));
return $data;
}

View File

@@ -25,7 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\Chart\Category;
use Carbon\Carbon;
use FireflyIII\Models\AccountType;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
@@ -33,6 +33,7 @@ use FireflyIII\Repositories\Category\NoCategoryRepositoryInterface;
use FireflyIII\Repositories\Category\OperationsRepositoryInterface;
use FireflyIII\Support\Http\Controllers\AugumentData;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class FrontpageChartGenerator
@@ -65,10 +66,9 @@ class FrontpageChartGenerator
public function generate(): array
{
Log::debug('Now in generate()');
$categories = $this->repository->getCategories();
$accounts = $this->accountRepos->getAccountsByType(
[AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::ASSET, AccountType::DEFAULT]
);
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]);
// get expenses + income per category:
$collection = [];
@@ -95,9 +95,11 @@ class FrontpageChartGenerator
private function collectExpenses(Category $category, Collection $accounts): array
{
Log::debug(sprintf('Collect expenses for category #%d ("%s")', $category->id, $category->name));
$spent = $this->opsRepos->sumExpenses($this->start, $this->end, $accounts, new Collection([$category]));
$tempData = [];
foreach ($spent as $currency) {
Log::debug(sprintf('Spent %s %s', $currency['currency_code'], $currency['sum']));
$this->addCurrency($currency);
$tempData[] = [
'name' => $category->name,

View File

@@ -54,28 +54,6 @@ trait FormSupport
return $html;
}
/**
* @param mixed $selected
*/
public function select(string $name, ?array $list = null, $selected = null, ?array $options = null): string
{
$list ??= [];
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$selected = $this->fillFieldValue($name, $selected);
unset($options['autocomplete'], $options['placeholder']);
try {
$html = view('form.select', compact('classes', 'name', 'label', 'selected', 'options', 'list'))->render();
} catch (\Throwable $e) {
app('log')->debug(sprintf('Could not render select(): %s', $e->getMessage()));
$html = 'Could not render select.';
}
return $html;
}
protected function label(string $name, ?array $options = null): string
{
$options ??= [];
@@ -139,6 +117,28 @@ trait FormSupport
return $value;
}
/**
* @param mixed $selected
*/
public function select(string $name, ?array $list = null, $selected = null, ?array $options = null): string
{
$list ??= [];
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$selected = $this->fillFieldValue($name, $selected);
unset($options['autocomplete'], $options['placeholder']);
try {
$html = view('form.select', compact('classes', 'name', 'label', 'selected', 'options', 'list'))->render();
} catch (\Throwable $e) {
app('log')->debug(sprintf('Could not render select(): %s', $e->getMessage()));
$html = 'Could not render select.';
}
return $html;
}
protected function getAccountRepository(): AccountRepositoryInterface
{
return app(AccountRepositoryInterface::class);

View File

@@ -28,6 +28,7 @@ use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
@@ -45,6 +46,20 @@ class ExchangeRateConverter
private array $prepared = [];
private int $queryCount = 0;
private UserGroup $userGroup;
public function __construct()
{
if (auth()->check()) {
$this->userGroup = auth()->user()->userGroup;
}
}
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
/**
* @throws FireflyException
*/
@@ -109,7 +124,7 @@ class ExchangeRateConverter
if (null !== $rate) {
$rate = bcdiv('1', $rate);
Cache::forever($key, $rate);
Log::debug(sprintf('ExchangeRateConverter: Return DB rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d')));
Log::debug(sprintf('ExchangeRateConverter: Return inverse DB rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d')));
return $rate;
}
@@ -166,8 +181,7 @@ class ExchangeRateConverter
}
/** @var null|CurrencyExchangeRate $result */
$result = auth()->user()
?->currencyExchangeRates()
$result = $this->userGroup->currencyExchangeRates()
->where('from_currency_id', $from)
->where('to_currency_id', $to)
->where('date', '<=', $date)
@@ -272,7 +286,7 @@ class ExchangeRateConverter
$start->startOfDay();
$end->endOfDay();
Log::debug(sprintf('Preparing for %s to %s between %s and %s', $from->code, $to->code, $start->format('Y-m-d'), $end->format('Y-m-d')));
$set = auth()->user()
$set = $this->userGroup
->currencyExchangeRates()
->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)

View File

@@ -38,7 +38,9 @@ trait BasicDataSupport
*/
protected function isInArray(array $array, int $entryId)
{
return $array[$entryId] ?? '0';
$key = $this->convertToNative ? 'native_balance' : 'balance';
return $array[$entryId][$key] ?? '0';
}
/**

View File

@@ -30,6 +30,7 @@ use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Support\Collection;
/**
@@ -45,33 +46,34 @@ trait ChartGeneration
protected function accountBalanceChart(Collection $accounts, Carbon $start, Carbon $end): array // chart helper method.
{
// chart properties for cache:
$cache = new CacheProperties();
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('chart.account.account-balance-chart');
$cache->addProperty($accounts);
$cache->addProperty($convertToNative);
if ($cache->has()) {
return $cache->get();
// return $cache->get();
}
app('log')->debug('Regenerate chart.account.account-balance-chart from scratch.');
$locale = app('steam')->getLocale();
$locale = app('steam')->getLocale();
/** @var GeneratorInterface $generator */
$generator = app(GeneratorInterface::class);
$generator = app(GeneratorInterface::class);
/** @var AccountRepositoryInterface $accountRepos */
$accountRepos = app(AccountRepositoryInterface::class);
$accountRepos = app(AccountRepositoryInterface::class);
$default = app('amount')->getDefaultCurrency();
$chartData = [];
$default = app('amount')->getDefaultCurrency();
$chartData = [];
/** @var Account $account */
foreach ($accounts as $account) {
// TODO we can use getAccountCurrency instead.
$currency = $accountRepos->getAccountCurrency($account);
if (null === $currency) {
$currency = $default;
}
$currency = $accountRepos->getAccountCurrency($account) ?? $default;
$useNative = $convertToNative && $default->id !== $currency->id;
$field = $useNative ? 'native_balance' : 'balance';
$currency = $useNative ? $default : $currency;
$currentSet = [
'label' => $account->name,
'currency_symbol' => $currency->symbol,
@@ -79,7 +81,7 @@ trait ChartGeneration
];
$currentStart = clone $start;
$range = app('steam')->balanceInRange($account, $start, clone $end);
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end);
$previous = array_values($range)[0];
while ($currentStart <= $end) {
$format = $currentStart->format('Y-m-d');
@@ -87,11 +89,11 @@ trait ChartGeneration
$balance = $range[$format] ?? $previous;
$previous = $balance;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance;
$currentSet['entries'][$label] = $balance[$field];
}
$chartData[] = $currentSet;
}
$data = $generator->multiSet($chartData);
$data = $generator->multiSet($chartData);
$cache->store($data);
return $data;

View File

@@ -44,16 +44,16 @@ use Illuminate\Support\Facades\Log;
*/
class AccountEnrichment implements EnrichmentInterface
{
private array $balances;
private Collection $collection;
private array $currencies;
private array $balances;
private Collection $collection;
private array $currencies;
private CurrencyRepositoryInterface $currencyRepository;
private TransactionCurrency $default;
private ?Carbon $end;
private array $grouped;
private array $objectGroups;
private TransactionCurrency $default;
private ?Carbon $end;
private array $grouped;
private array $objectGroups;
private AccountRepositoryInterface $repository;
private ?Carbon $start;
private ?Carbon $start;
public function __construct()
{

View File

@@ -126,7 +126,7 @@ class Preferences
{
$fullName = sprintf('preference%s%s', $user->id, $name);
$groupId = $this->getUserGroupId($user, $name);
$groupId = 0 === (int)$groupId ? null : (int) $groupId;
$groupId = 0 === (int) $groupId ? null : (int) $groupId;
Cache::forget($fullName);
@@ -277,19 +277,6 @@ class Preferences
Session::forget('first');
}
public function setEncrypted(string $name, mixed $value): Preference
{
try {
$encrypted = encrypt($value);
} catch (EncryptException $e) {
Log::error(sprintf('Could not encrypt preference "%s": %s', $name, $e->getMessage()));
throw new FireflyException(sprintf('Could not encrypt preference "%s". Cowardly refuse to continue.', $name));
}
return $this->set($name, $encrypted);
}
public function set(string $name, null|array|bool|int|string $value): Preference
{
/** @var null|User $user */
@@ -305,4 +292,17 @@ class Preferences
return $this->setForUser($user, $name, $value);
}
public function setEncrypted(string $name, mixed $value): Preference
{
try {
$encrypted = encrypt($value);
} catch (EncryptException $e) {
Log::error(sprintf('Could not encrypt preference "%s": %s', $name, $e->getMessage()));
throw new FireflyException(sprintf('Could not encrypt preference "%s". Cowardly refuse to continue.', $name));
}
return $this->set($name, $encrypted);
}
}

View File

@@ -96,24 +96,6 @@ trait ConvertsDataTypes
return Steam::filterSpaces($string);
}
public function convertIban(string $field): string
{
return Steam::filterSpaces($this->convertString($field));
}
/**
* Return string value.
*/
public function convertString(string $field, string $default = ''): string
{
$entry = $this->get($field);
if (!is_scalar($entry)) {
return $default;
}
return (string) $this->clearString((string) $entry);
}
public function clearString(?string $string): ?string
{
$string = $this->clearStringKeepNewlines($string);
@@ -147,12 +129,22 @@ trait ConvertsDataTypes
return trim((string) $string);
}
/**
* Return integer value.
*/
public function convertInteger(string $field): int
public function convertIban(string $field): string
{
return (int) $this->get($field);
return Steam::filterSpaces($this->convertString($field));
}
/**
* Return string value.
*/
public function convertString(string $field, string $default = ''): string
{
$entry = $this->get($field);
if (!is_scalar($entry)) {
return $default;
}
return (string) $this->clearString((string) $entry);
}
/**
@@ -161,6 +153,14 @@ trait ConvertsDataTypes
*/
abstract public function get(string $key, mixed $default = null): mixed;
/**
* Return integer value.
*/
public function convertInteger(string $field): int
{
return (int) $this->get($field);
}
/**
* TODO duplicate, see SelectTransactionsRequest
*

View File

@@ -1822,6 +1822,74 @@ class OperatorQuerySearch implements SearchInterface
case 'sepa_ct_is':
$this->collector->setSepaCT($value);
break;
case 'source_balance_gte':
case '-source_balance_lt':
$this->collector->accountBalanceIs('source', '>=', $value);
break;
case '-source_balance_gte':
case 'source_balance_lt':
$this->collector->accountBalanceIs('source', '<', $value);
break;
case 'source_balance_gt':
case '-source_balance_lte':
$this->collector->accountBalanceIs('source', '>', $value);
break;
case '-source_balance_gt':
case 'source_balance_lte':
$this->collector->accountBalanceIs('source', '<=', $value);
break;
case 'source_balance_is':
$this->collector->accountBalanceIs('source', '==', $value);
break;
case '-source_balance_is':
$this->collector->accountBalanceIs('source', '!=', $value);
break;
case 'destination_balance_gte':
case '-destination_balance_lt':
$this->collector->accountBalanceIs('destination', '>=', $value);
break;
case '-destination_balance_gte':
case 'destination_balance_lt':
$this->collector->accountBalanceIs('destination', '<', $value);
break;
case 'destination_balance_gt':
case '-destination_balance_lte':
$this->collector->accountBalanceIs('destination', '>', $value);
break;
case '-destination_balance_gt':
case 'destination_balance_lte':
$this->collector->accountBalanceIs('destination', '<=', $value);
break;
case 'destination_balance_is':
$this->collector->accountBalanceIs('destination', '==', $value);
break;
case '-destination_balance_is':
$this->collector->accountBalanceIs('destination', '!=', $value);
break;
}

View File

@@ -24,13 +24,10 @@ declare(strict_types=1);
namespace FireflyIII\Support;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -39,311 +36,24 @@ use Illuminate\Support\Facades\Log;
*/
class Steam
{
public function balanceByTransactions(Account $account, Carbon $date, ?TransactionCurrency $currency): array
public function getAccountCurrency(Account $account): ?TransactionCurrency
{
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('balance-by-transactions');
$cache->addProperty($date);
$cache->addProperty(null !== $currency ? $currency->id : 0);
if ($cache->has()) {
return $cache->get();
$type = $account->accountType->type;
$list = config('firefly.valid_currency_account_types');
// return null if not in this list.
if (!in_array($type, $list, true)) {
return null;
}
$result = $account->accountMeta->where('name', 'currency_id')->first();
if (null === $result) {
return null;
}
$query = $account->transactions()
->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->orderBy('transaction_journals.date', 'desc')
->orderBy('transaction_journals.order', 'asc')
->orderBy('transaction_journals.description', 'desc')
->orderBy('transactions.amount', 'desc')
;
if (null !== $currency) {
$query->where('transactions.transaction_currency_id', $currency->id);
$query->limit(1);
$result = $query->get(['transactions.transaction_currency_id', 'transactions.balance_after'])->first();
$key = (int) $result->transaction_currency_id;
$return = [$key => $result->balance_after];
$cache->store($return);
return $return;
}
$return = [];
$result = $query->get(['transactions.transaction_currency_id', 'transactions.balance_after']);
foreach ($result as $entry) {
$key = (int) $entry->transaction_currency_id;
if (array_key_exists($key, $return)) {
continue;
}
$return[$key] = $entry->balance_after;
}
return $return;
return TransactionCurrency::find((int) $result->data);
}
public function balanceConvertedIgnoreVirtual(Account $account, Carbon $date, TransactionCurrency $currency): string
{
$balance = $this->balanceConverted($account, $date, $currency);
$virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance;
// currency of account
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user);
$accountCurrency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
if ($accountCurrency->id !== $currency->id && 0 !== bccomp($virtual, '0')) {
// convert amount to given currency.
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
$converter = new ExchangeRateConverter();
$virtual = $converter->convert($accountCurrency, $currency, $date, $virtual);
}
return bcsub($balance, $virtual);
}
/**
* selection of transactions
* 1: all normal transactions. No foreign currency info. In $currency. Need conversion.
* 2: all normal transactions. No foreign currency info. In $native. Need NO conversion.
* 3: all normal transactions. No foreign currency info. In neither currency. Need conversion.
* Then, select everything with foreign currency info:
* 4. All transactions with foreign currency info in $native. Normal currency value is ignored. Do not need
* conversion.
* 5. All transactions with foreign currency info NOT in $native, but currency info in $currency. Need conversion.
* 6. All transactions with foreign currency info NOT in $native, and currency info NOT in $currency. Need
* conversion.
*
* Gets balance at the end of current month by default. Returns the balance converted
* to the indicated currency ($native).
*
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function balanceConverted(Account $account, Carbon $date, TransactionCurrency $native): string
{
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
Log::debug(sprintf('Now in balanceConverted (%s) for account #%d, converting to %s', $date->format('Y-m-d'), $account->id, $native->code));
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('balance');
$cache->addProperty($date);
$cache->addProperty($native->id);
if ($cache->has()) {
Log::debug('Cached!');
return $cache->get();
}
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$currency = $repository->getAccountCurrency($account);
$currency = null === $currency ? app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup) : $currency;
if ($native->id === $currency->id) {
Log::debug('No conversion necessary!');
return $this->balance($account, $date);
}
$new = [];
$existing = [];
$new[] = $account->transactions() // 1
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currency->id)
->whereNull('transactions.foreign_currency_id')
->get(['transaction_journals.date', 'transactions.amount'])->toArray()
;
Log::debug(sprintf('%d transaction(s) in set #1', count($new[0])));
$existing[] = $account->transactions() // 2
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $native->id)
->whereNull('transactions.foreign_currency_id')
->get(['transactions.amount'])->toArray()
;
Log::debug(sprintf('%d transaction(s) in set #2', count($existing[0])));
$new[] = $account->transactions() // 3
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', '!=', $currency->id)
->where('transactions.transaction_currency_id', '!=', $native->id)
->whereNull('transactions.foreign_currency_id')
->get(['transaction_journals.date', 'transactions.amount'])->toArray()
;
Log::debug(sprintf('%d transactions in set #3', count($new[1])));
$existing[] = $account->transactions() // 4
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $native->id)
->whereNotNull('transactions.foreign_amount')
->get(['transactions.foreign_amount'])->toArray()
;
Log::debug(sprintf('%d transactions in set #4', count($existing[1])));
$new[] = $account->transactions()// 5
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currency->id)
->where('transactions.foreign_currency_id', '!=', $native->id)
->whereNotNull('transactions.foreign_amount')
->get(['transaction_journals.date', 'transactions.amount'])->toArray()
;
Log::debug(sprintf('%d transactions in set #5', count($new[2])));
$new[] = $account->transactions()// 6
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', '!=', $currency->id)
->where('transactions.foreign_currency_id', '!=', $native->id)
->whereNotNull('transactions.foreign_amount')
->get(['transaction_journals.date', 'transactions.amount'])->toArray()
;
Log::debug(sprintf('%d transactions in set #6', count($new[3])));
// process both sets of transactions. Of course, no need to convert set "existing".
$balance = $this->sumTransactions($existing[0], 'amount');
$balance = bcadd($balance, $this->sumTransactions($existing[1], 'foreign_amount'));
Log::debug(sprintf('Balance from set #2 and #4 is %f', $balance));
// need to convert the others. All sets use the "amount" value as their base (that's easy)
// but we need to convert each transaction separately because the date difference may
// incur huge currency changes.
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
$start = clone $date;
$end = clone $date;
$converter = new ExchangeRateConverter();
foreach ($new as $set) {
foreach ($set as $transaction) {
$currentDate = false;
try {
$currentDate = Carbon::parse($transaction['date'], config('app.timezone'));
} catch (InvalidFormatException $e) {
Log::error(sprintf('Could not parse date "%s" in %s', $transaction['date'], __METHOD__));
}
if (false === $currentDate) {
$currentDate = today(config('app.timezone'));
}
if ($currentDate->lte($start)) {
$start = clone $currentDate;
}
}
}
unset($currentDate);
$converter->prepare($currency, $native, $start, $end);
foreach ($new as $set) {
foreach ($set as $transaction) {
$currentDate = false;
try {
$currentDate = Carbon::parse($transaction['date'], config('app.timezone'));
} catch (InvalidFormatException $e) {
Log::error(sprintf('Could not parse date "%s" in %s', $transaction['date'], __METHOD__));
}
if (false === $currentDate) {
$currentDate = today(config('app.timezone'));
}
$rate = $converter->getCurrencyRate($currency, $native, $currentDate);
$convertedAmount = bcmul($transaction['amount'], $rate);
$balance = bcadd($balance, $convertedAmount);
}
}
// add virtual balance (also needs conversion)
$virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance;
$virtual = $converter->convert($currency, $native, $account->created_at, $virtual);
$balance = bcadd($balance, $virtual);
$converter->summarize();
$cache->store($balance);
$converter->summarize();
return $balance;
}
/**
* Gets balance at the end of current month by default
*
* @throws FireflyException
*/
public function balance(Account $account, Carbon $date, ?TransactionCurrency $currency = null): string
{
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
// abuse chart properties:
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('balance');
$cache->addProperty($date);
$cache->addProperty(null !== $currency ? $currency->id : 0);
if ($cache->has()) {
return $cache->get();
}
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
if (null === $currency) {
$currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
}
// first part: get all balances in own currency:
$transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currency->id)
->get(['transactions.amount'])->toArray()
;
$nativeBalance = $this->sumTransactions($transactions, 'amount');
// get all balances in foreign currency:
$transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $currency->id)
->where('transactions.transaction_currency_id', '!=', $currency->id)
->get(['transactions.foreign_amount'])->toArray()
;
$foreignBalance = $this->sumTransactions($transactions, 'foreign_amount');
$balance = bcadd($nativeBalance, $foreignBalance);
$virtual = null === $account->virtual_balance ? '0' : $account->virtual_balance;
$balance = bcadd($balance, $virtual);
$cache->store($balance);
return $balance;
}
/**
* @deprecated
*/
public function balanceIgnoreVirtual(Account $account, Carbon $date): string
{
throw new FireflyException('Deprecated method balanceIgnoreVirtual.');
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user);
$currencyId = (int) $repository->getMetaValue($account, 'currency_id');
$transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currencyId)
->get(['transactions.amount'])->toArray()
;
$nativeBalance = $this->sumTransactions($transactions, 'amount');
// get all balances in foreign currency:
$transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $currencyId)
->where('transactions.transaction_currency_id', '!=', $currencyId)
->get(['transactions.foreign_amount'])->toArray()
;
$foreignBalance = $this->sumTransactions($transactions, 'foreign_amount');
return bcadd($nativeBalance, $foreignBalance);
}
public function sumTransactions(array $transactions, string $key): string
private function sumTransactions(array $transactions, string $key): string
{
$sum = '0';
@@ -352,52 +62,48 @@ class Steam
$value = (string) ($transaction[$key] ?? '0');
$value = '' === $value ? '0' : $value;
$sum = bcadd($sum, $value);
// Log::debug(sprintf('Add value from "%s": %s', $key, $value));
}
Log::debug(sprintf('Sum of "%s"-fields is %s', $key, $sum));
return $sum;
}
/**
* Gets the balance for the given account during the whole range, using this format:.
*
* [yyyy-mm-dd] => 123,2
*
* @throws FireflyException
*/
public function balanceInRange(Account $account, Carbon $start, Carbon $end, ?TransactionCurrency $currency = null): array
public function finalAccountBalanceInRange(Account $account, Carbon $start, Carbon $end): array
{
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
// expand period.
$start->subDay()->startOfDay();
$end->addDay()->endOfDay();
// set up cache
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('balance-in-range');
$cache->addProperty(null !== $currency ? $currency->id : 0);
$cache->addProperty('final-balance-in-range');
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
return $cache->get();
// return $cache->get();
}
$start->subDay();
$end->addDay();
$balances = [];
$formatted = $start->format('Y-m-d');
$startBalance = $this->balance($account, $start, $currency);
$startBalance = $this->finalAccountBalance($account, $start);
$defaultCurrency = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
$currency = $this->getAccountCurrency($account) ?? $defaultCurrency;
$currencies = [
$currency->id => $currency,
$defaultCurrency->id => $defaultCurrency,
];
$startBalance[$defaultCurrency->code] ??= '0';
$startBalance[$currency->code] ??= '0';
$balances[$formatted] = $startBalance;
if (null === $currency) {
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($account->user);
$currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
}
$currencyId = $currency->id;
$start->addDay();
// query!
// sums up the balance changes per day, for foreign, native and normal amounts.
$set = $account->transactions()
->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59'))
->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
->groupBy('transaction_journals.date')
->groupBy('transactions.transaction_currency_id')
->groupBy('transactions.foreign_currency_id')
@@ -410,6 +116,7 @@ class Steam
\DB::raw('SUM(transactions.amount) AS modified'),
'transactions.foreign_currency_id',
\DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'),
\DB::raw('SUM(transactions.native_amount) AS modified_native'),
]
)
;
@@ -418,266 +125,47 @@ class Steam
/** @var Transaction $entry */
foreach ($set as $entry) {
// normal amount and foreign amount
$modified = (string) (null === $entry->modified ? '0' : $entry->modified);
$foreignModified = (string) (null === $entry->modified_foreign ? '0' : $entry->modified_foreign);
$amount = '0';
if ($currencyId === (int) $entry->transaction_currency_id || 0 === $currencyId) {
// use normal amount:
$amount = $modified;
}
if ($currencyId === (int) $entry->foreign_currency_id) {
// use foreign amount:
$amount = $foreignModified;
}
// Log::debug(sprintf('Trying to add %s and %s.', var_export($currentBalance, true), var_export($amount, true)));
$currentBalance = bcadd($currentBalance, $amount);
$carbon = new Carbon($entry->date, config('app.timezone'));
$date = $carbon->format('Y-m-d');
$balances[$date] = $currentBalance;
}
// normal, native and foreign amount
$carbon = new Carbon($entry->date, $entry->date_tz);
$modified = (string) (null === $entry->modified ? '0' : $entry->modified);
$foreignModified = (string) (null === $entry->modified_foreign ? '0' : $entry->modified_foreign);
$nativeModified = (string) (null === $entry->modified_native ? '0' : $entry->modified_native);
// add "modified" to amount if the currency id matches the account currency id.
if ($entry->transaction_currency_id === $currency->id) {
$currentBalance['balance'] = bcadd($currentBalance['balance'], $modified);
$currentBalance[$currency->code] = bcadd($currentBalance[$currency->code], $modified);
}
// always add the native balance, even if it ends up at zero.
$currentBalance['native_balance'] = bcadd($currentBalance['native_balance'], $nativeModified);
// add modified foreign to the array
if (null !== $entry->foreign_currency_id) {
$foreignId = $entry->foreign_currency_id;
$currencies[$foreignId] ??= TransactionCurrency::find($foreignId);
$foreignCurrency = $currencies[$foreignId];
$currentBalance[$foreignCurrency->code] ??= '0';
$currentBalance[$foreignCurrency->code] = bcadd($currentBalance[$foreignCurrency->code], $foreignModified);
}
$balances[$carbon->format('Y-m-d')] = $currentBalance;
}
$cache->store($balances);
return $balances;
}
/**
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function balanceInRangeConverted(Account $account, Carbon $start, Carbon $end, TransactionCurrency $native): array
public function finalAccountsBalance(Collection $accounts, Carbon $date): array
{
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('balance-in-range-converted');
$cache->addProperty($native->id);
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
return $cache->get();
$balances = [];
foreach ($accounts as $account) {
$balances[$account->id] = $this->finalAccountBalance($account, $date);
}
Log::debug(sprintf('balanceInRangeConverted for account #%d to %s', $account->id, $native->code));
$start->subDay();
$end->addDay();
$balances = [];
$formatted = $start->format('Y-m-d');
$currencies = [];
$startBalance = $this->balanceConverted($account, $start, $native); // already converted to native amount
$balances[$formatted] = $startBalance;
Log::debug(sprintf('Start balance on %s is %s', $formatted, $startBalance));
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
$converter = new ExchangeRateConverter();
// not sure why this is happening:
$start->addDay();
// grab all transactions between start and end:
$set = $account->transactions()
->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59'))
->orderBy('transaction_journals.date', 'ASC')
->whereNull('transaction_journals.deleted_at')
->get(
[
'transaction_journals.date',
'transactions.transaction_currency_id',
'transactions.amount',
'transactions.foreign_currency_id',
'transactions.foreign_amount',
]
)->toArray()
;
// loop the set and convert if necessary:
$currentBalance = $startBalance;
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$day = false;
try {
$day = Carbon::parse($transaction['date'], config('app.timezone'));
} catch (InvalidFormatException $e) {
Log::error(sprintf('Could not parse date "%s" in %s: %s', $transaction['date'], __METHOD__, $e->getMessage()));
}
if (false === $day) {
$day = today(config('app.timezone'));
}
$format = $day->format('Y-m-d');
// if the transaction is in the expected currency, change nothing.
if ((int) $transaction['transaction_currency_id'] === $native->id) {
// change the current balance, set it to today, continue the loop.
$currentBalance = bcadd($currentBalance, $transaction['amount']);
$balances[$format] = $currentBalance;
Log::debug(sprintf('%s: transaction in %s, new balance is %s.', $format, $native->code, $currentBalance));
continue;
}
// if foreign currency is in the expected currency, do nothing:
if ((int) $transaction['foreign_currency_id'] === $native->id) {
$currentBalance = bcadd($currentBalance, $transaction['foreign_amount']);
$balances[$format] = $currentBalance;
Log::debug(sprintf('%s: transaction in %s (foreign), new balance is %s.', $format, $native->code, $currentBalance));
continue;
}
// otherwise, convert 'amount' to the necessary currency:
$currencyId = (int) $transaction['transaction_currency_id'];
$currency = $currencies[$currencyId] ?? TransactionCurrency::find($currencyId);
$currencies[$currencyId] = $currency;
$rate = $converter->getCurrencyRate($currency, $native, $day);
$convertedAmount = bcmul($transaction['amount'], $rate);
$currentBalance = bcadd($currentBalance, $convertedAmount);
$balances[$format] = $currentBalance;
Log::debug(sprintf(
'%s: transaction in %s(!). Conversion rate is %s. %s %s = %s %s',
$format,
$currency->code,
$rate,
$currency->code,
$transaction['amount'],
$native->code,
$convertedAmount
));
}
$cache->store($balances);
$converter->summarize();
return $balances;
}
/**
* This method always ignores the virtual balance.
*
* @throws FireflyException
*/
public function balancesByAccounts(Collection $accounts, Carbon $date): array
{
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
$ids = $accounts->pluck('id')->toArray();
// cache this property.
$cache = new CacheProperties();
$cache->addProperty($ids);
$cache->addProperty('balances');
$cache->addProperty($date);
if ($cache->has()) {
return $cache->get();
}
// need to do this per account.
$result = [];
/** @var Account $account */
foreach ($accounts as $account) {
$result[$account->id] = $this->balance($account, $date);
}
$cache->store($result);
return $result;
}
/**
* This method always ignores the virtual balance.
*
* @throws FireflyException
*/
public function balancesByAccountsConverted(Collection $accounts, Carbon $date): array
{
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
$ids = $accounts->pluck('id')->toArray();
// cache this property.
$cache = new CacheProperties();
$cache->addProperty($ids);
$cache->addProperty('balances-converted');
$cache->addProperty($date);
if ($cache->has()) {
return $cache->get();
}
// need to do this per account.
$result = [];
/** @var Account $account */
foreach ($accounts as $account) {
$default = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
$result[$account->id]
= [
'balance' => $this->balance($account, $date),
'native_balance' => $this->balanceConverted($account, $date, $default),
];
}
$cache->store($result);
return $result;
}
/**
* Same as above, but also groups per currency.
*/
public function balancesPerCurrencyByAccounts(Collection $accounts, Carbon $date): array
{
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
$ids = $accounts->pluck('id')->toArray();
// cache this property.
$cache = new CacheProperties();
$cache->addProperty($ids);
$cache->addProperty('balances-per-currency');
$cache->addProperty($date);
if ($cache->has()) {
return $cache->get();
}
// need to do this per account.
$result = [];
/** @var Account $account */
foreach ($accounts as $account) {
$result[$account->id] = $this->balancePerCurrency($account, $date);
}
$cache->store($result);
return $result;
}
public function balancePerCurrency(Account $account, Carbon $date): array
{
// Log::warning(sprintf('Deprecated method %s, do not use.', __METHOD__));
// abuse chart properties:
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('balance-per-currency');
$cache->addProperty($date);
if ($cache->has()) {
return $cache->get();
}
$query = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->groupBy('transactions.transaction_currency_id')
;
$balances = $query->get(['transactions.transaction_currency_id', \DB::raw('SUM(transactions.amount) as sum_for_currency')]); // @phpstan-ignore-line
$return = [];
/** @var \stdClass $entry */
foreach ($balances as $entry) {
$return[(int) $entry->transaction_currency_id] = (string) $entry->sum_for_currency;
}
$cache->store($return);
return $return;
}
/**
* https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers
*/
@@ -765,6 +253,120 @@ class Steam
return str_replace($search, '', $string);
}
/**
* Returns the balance of an account at exact moment given. Array with at least one value.
*
* "balance" the balance in whatever currency the account has, so the sum of all transaction that happen to have
* THAT currency.
* "native_balance" the balance according to the "native_amount" + "native_foreign_amount" fields.
* "ABC" the balance in this particular currency code (may repeat for each found currency).
*
* Het maakt niet uit of de native currency wel of niet gelijk is aan de account currency.
* Optelsom zou hetzelfde moeten zijn. Als het EUR is en de rekening ook is native_amount 0.
* Zo niet is amount 0 en native_amount het bedrag.
*
* Eerst een som van alle transacties in de native currency. Alle EUR bij elkaar opgeteld.
* Om te weten wat er nog meer op de rekening gebeurt, pak alles waar currency niet EUR is, en de foreign ook niet,
* en tel native_amount erbij op.
* Daarna pak je alle transacties waar currency niet EUR is, en de foreign wel, en tel foreign_amount erbij op.
*
* Wil je niks weten van native currencies, pak je:
*
* Eerst een som van alle transacties gegroepeerd op currency. Einde.
*/
public function finalAccountBalance(Account $account, Carbon $date): array
{
Log::debug(sprintf('Now in finalAccountBalance(#%d, "%s", "%s")', $account->id, $account->name, $date->format('Y-m-d H:i:s')));
$native = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
$convertToNative = app('preferences')->getForUser($account->user, 'convert_to_native', false)->data;
$accountCurrency = $this->getAccountCurrency($account);
$hasCurrency = null !== $accountCurrency;
$currency = $hasCurrency ? $accountCurrency : $native;
$return = [];
// first, the "balance", as described earlier.
if ($convertToNative) {
// normal balance
$return['balance'] = (string) $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->where('transactions.transaction_currency_id', $native->id)
->sum('transactions.amount')
;
// plus virtual balance, if the account has a virtual_balance in the native currency
if ($native->id === $accountCurrency?->id) {
$return['balance'] = bcadd('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance, $return['balance']);
}
Log::debug(sprintf('balance is (%s only) %s (with virtual balance)', $native->code, $return['balance']));
// native balance
$return['native_balance'] = (string) $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->whereNot('transactions.transaction_currency_id', $native->id)
->sum('transactions.native_amount')
;
// plus native virtual balance.
$return['native_balance'] = bcadd('' === (string) $account->native_virtual_balance ? '0' : $account->native_virtual_balance, $return['native_balance']);
Log::debug(sprintf('native_balance is (all transactions to %s) %s (with virtual balance)', $native->code, $return['native_balance']));
// plus foreign transactions in THIS currency.
$sum = (string) $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->whereNot('transactions.transaction_currency_id', $native->id)
->where('transactions.foreign_currency_id', $native->id)
->sum('transactions.foreign_amount')
;
$return['native_balance'] = bcadd($return['native_balance'], $sum);
Log::debug(sprintf('Foreign amount transactions add (%s only) %s, total native_balance is now %s', $native->code, $sum, $return['native_balance']));
}
// balance(s) in other (all) currencies.
$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'))
->get(['transaction_currencies.code', 'transactions.amount'])->toArray()
;
$others = $this->groupAndSumTransactions($array, 'code', 'amount');
Log::debug('All balances are (joined)', $others);
// if the account has no own currency preference, drop balance in favor of native balance
if ($hasCurrency && !$convertToNative) {
$return['balance'] = $others[$currency->code] ?? '0';
$return['native_balance'] = $others[$currency->code] ?? '0';
Log::debug(sprintf('Set balance + native_balance to %s', $return['balance']));
}
// if the currency is the same as the native currency, set the native_balance to the balance for consistency.
// if($currency->id === $native->id) {
// $return['native_balance'] = $return['balance'];
// }
if (!$hasCurrency && array_key_exists('balance', $return) && array_key_exists('native_balance', $return)) {
Log::debug('Account has no currency preference, dropping balance in favor of native balance.');
$sum = bcadd($return['balance'], $return['native_balance']);
Log::debug(sprintf('%s + %s = %s', $return['balance'], $return['native_balance'], $sum));
$return['native_balance'] = $sum;
unset($return['balance']);
}
return array_merge($return, $others);
}
private function groupAndSumTransactions(array $array, string $group, string $field): array
{
$return = [];
foreach ($array as $item) {
$groupKey = $item[$group] ?? 'unknown';
$return[$groupKey] = bcadd($return[$groupKey] ?? '0', $item[$field]);
}
return $return;
}
/**
* @throws FireflyException
*/
@@ -798,9 +400,9 @@ class Steam
/** @var Transaction $entry */
foreach ($set as $entry) {
$date = new Carbon($entry->max_date, config('app.timezone'));
$date = new Carbon($entry->max_date, config('app.timezone'));
$date->setTimezone(config('app.timezone'));
$list[$entry->account_id] = $date;
$list[(int) $entry->account_id] = $date;
}
return $list;

View File

@@ -75,6 +75,7 @@ class AmountFormat extends AbstractExtension
$this->formatAmountByAccount(),
$this->formatAmountBySymbol(),
$this->formatAmountByCurrency(),
$this->formatAmountByCode(),
];
}
@@ -100,6 +101,26 @@ class AmountFormat extends AbstractExtension
);
}
/**
* Use the code to format a currency.
*/
protected function formatAmountByCode(): TwigFunction
{
// formatAmountByCode
return new TwigFunction(
'formatAmountByCode',
static function (string $amount, string $code, ?bool $coloured = null): string {
$coloured ??= true;
/** @var TransactionCurrency $currency */
$currency = TransactionCurrency::whereCode($code)->first();
return app('amount')->formatAnything($currency, $amount, $coloured);
},
['is_safe' => ['html']]
);
}
/**
* Will format the amount by the currency related to the given account.
*/

View File

@@ -25,8 +25,11 @@ namespace FireflyIII\Support\Twig;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Search\OperatorQuerySearch;
use League\CommonMark\GithubFlavoredMarkdownConverter;
use Route;
@@ -63,28 +66,33 @@ class General extends AbstractExtension
}
/** @var Carbon $date */
$date = session('end', today(config('app.timezone'))->endOfMonth());
$runningBalance = config('firefly.feature_flags.running_balance_column');
$info = [];
if (true === $runningBalance) {
$info = app('steam')->balanceByTransactions($account, $date, null);
}
if (false === $runningBalance) {
$info[] = app('steam')->balance($account, $date);
}
$date = session('end', today(config('app.timezone'))->endOfMonth());
$info = Steam::finalAccountBalance($account, $date);
$currency = Steam::getAccountCurrency($account);
$default = Amount::getDefaultCurrency();
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$useNative = $convertToNative && $default->id !== $currency->id;
$strings = [];
foreach ($info as $key => $balance) {
if ('balance' === $key) {
// balance in account currency.
if (!$useNative) {
$strings[] = app('amount')->formatAnything($currency, $balance, false);
}
$strings = [];
foreach ($info as $currencyId => $balance) {
$balance = (string) $balance;
if (0 === $currencyId) {
// not good code but OK
/** @var AccountRepositoryInterface $accountRepos */
$accountRepos = app(AccountRepositoryInterface::class);
$currency = $accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$strings[] = app('amount')->formatAnything($currency, $balance, false);
continue;
}
if (0 !== $currencyId) {
$strings[] = app('amount')->formatByCurrencyId($currencyId, $balance, false);
if ('native_balance' === $key) {
// balance in native currency.
if ($useNative) {
$strings[] = app('amount')->formatAnything($default, $balance, false);
}
continue;
}
// for multi currency accounts.
if ($useNative && $key !== $default->code) {
$strings[] = app('amount')->formatAnything(TransactionCurrency::where('code', $key)->first(), $balance, false);
}
}

View File

@@ -28,6 +28,7 @@ use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
@@ -103,7 +104,7 @@ class AccountTransformer extends AbstractTransformer
'currency_code' => $currencyCode,
'currency_symbol' => $currencySymbol,
'currency_decimal_places' => $decimalPlaces,
'current_balance' => app('steam')->bcround(app('steam')->balance($account, $date), $decimalPlaces),
'current_balance' => app('steam')->bcround(Steam::finalAccountBalance($account, $date)['balance'], $decimalPlaces),
'current_balance_date' => $date->toAtomString(),
'notes' => $this->repository->getNoteText($account),
'monthly_payment_date' => $monthlyPaymentDate,

View File

@@ -115,7 +115,7 @@ class AccountTransformer extends AbstractTransformer
private function getMetaBalances(Collection $accounts): void
{
try {
$this->convertedBalances = app('steam')->balancesByAccountsConverted($accounts, $this->getDate());
$this->convertedBalances = app('steam')->finalAccountsBalance($accounts, $this->getDate());
} catch (FireflyException $e) {
Log::error($e->getMessage());
}
@@ -172,14 +172,15 @@ class AccountTransformer extends AbstractTransformer
private function getBalanceDifference(Collection $accounts, Carbon $start, Carbon $end): void
{
throw new FireflyException('Used deprecated method, rethink this.');
// collect balances, start and end for both native and converted.
// yes the b is usually used for boolean by idiots but here it's for balance.
$bStart = [];
$bEnd = [];
try {
$bStart = app('steam')->balancesByAccountsConverted($accounts, $start);
$bEnd = app('steam')->balancesByAccountsConverted($accounts, $end);
$bStart = app('steam')->finalAccountsBalance($accounts, $start);
$bEnd = app('steam')->finalAccountsBalance($accounts, $end);
} catch (FireflyException $e) {
Log::error($e->getMessage());
}

12
composer.lock generated
View File

@@ -10845,16 +10845,16 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.14.9",
"version": "v3.14.10",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "2e805a6bd4e1aa83774316bb062703c65d0691ef"
"reference": "56b9bd235e3fe62e250124804009ce5bab97cc63"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/2e805a6bd4e1aa83774316bb062703c65d0691ef",
"reference": "2e805a6bd4e1aa83774316bb062703c65d0691ef",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/56b9bd235e3fe62e250124804009ce5bab97cc63",
"reference": "56b9bd235e3fe62e250124804009ce5bab97cc63",
"shasum": ""
},
"require": {
@@ -10913,7 +10913,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.9"
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.10"
},
"funding": [
{
@@ -10925,7 +10925,7 @@
"type": "github"
}
],
"time": "2024-11-25T14:51:20+00:00"
"time": "2024-12-23T10:10:42+00:00"
},
{
"name": "barryvdh/laravel-ide-helper",

View File

@@ -24,221 +24,233 @@ declare(strict_types=1);
return [
'operators' => [
'user_action' => ['alias' => false, 'needs_context' => true],
'account_id' => ['alias' => false, 'needs_context' => true],
'reconciled' => ['alias' => false, 'needs_context' => false],
'source_account_id' => ['alias' => false, 'needs_context' => true],
'destination_account_id' => ['alias' => false, 'needs_context' => true],
'transaction_type' => ['alias' => false, 'needs_context' => true],
'type' => ['alias' => true, 'alias_for' => 'transaction_type', 'needs_context' => true],
'tag_is' => ['alias' => false, 'needs_context' => true],
'tag_is_not' => ['alias' => false, 'needs_context' => true],
'tag' => ['alias' => true, 'alias_for' => 'tag_is', 'needs_context' => true],
'tag_contains' => ['alias' => false, 'needs_context' => true],
'tag_ends' => ['alias' => false, 'needs_context' => true],
'tag_starts' => ['alias' => false, 'needs_context' => true],
'description_is' => ['alias' => false, 'needs_context' => true],
'description' => ['alias' => true, 'alias_for' => 'description_is', 'needs_context' => true],
'description_contains' => ['alias' => false, 'needs_context' => true],
'description_ends' => ['alias' => false, 'needs_context' => true],
'description_starts' => ['alias' => false, 'needs_context' => true],
'notes_is' => ['alias' => false, 'needs_context' => true],
'notes_are' => ['alias' => true, 'alias_for' => 'notes_is', 'needs_context' => true],
'notes_contains' => ['alias' => false, 'needs_context' => true],
'notes_contain' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes_ends' => ['alias' => false, 'needs_context' => true],
'notes_end' => ['alias' => true, 'alias_for' => 'notes_ends', 'needs_context' => true],
'notes_starts' => ['alias' => false, 'needs_context' => true],
'notes_start' => ['alias' => true, 'alias_for' => 'notes_starts', 'needs_context' => true],
'source_account_is' => ['alias' => false, 'needs_context' => true],
'from_account_is' => ['alias' => true, 'alias_for' => 'source_account_is', 'needs_context' => true],
'source_account_contains' => ['alias' => false, 'needs_context' => true],
'source' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from_account_contains' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'source_account_ends' => ['alias' => false, 'needs_context' => true],
'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true],
'source_account_starts' => ['alias' => false, 'needs_context' => true],
'from_account_starts' => ['alias' => true, 'alias_for' => 'source_account_starts', 'needs_context' => true],
'source_account_nr_is' => ['alias' => false, 'needs_context' => true],
'from_account_nr_is' => ['alias' => true, 'alias_for' => 'source_account_nr_is', 'needs_context' => true],
'source_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'from_account_nr_contains' => ['alias' => true, 'alias_for' => 'source_account_nr_contains', 'needs_context' => true],
'source_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'from_account_nr_ends' => ['alias' => true, 'alias_for' => 'source_account_nr_ends', 'needs_context' => true],
'source_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'from_account_nr_starts' => ['alias' => true, 'alias_for' => 'source_account_nr_starts', 'needs_context' => true],
'destination_account_is' => ['alias' => false, 'needs_context' => true],
'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', 'needs_context' => true],
'destination_account_contains' => ['alias' => false, 'needs_context' => true],
'destination' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to_account_contains' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'destination_account_ends' => ['alias' => false, 'needs_context' => true],
'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true],
'destination_account_starts' => ['alias' => false, 'needs_context' => true],
'to_account_starts' => ['alias' => true, 'alias_for' => 'destination_account_starts', 'needs_context' => true],
'destination_account_nr_is' => ['alias' => false, 'needs_context' => true],
'to_account_nr_is' => ['alias' => true, 'alias_for' => 'destination_account_nr_is', 'needs_context' => true],
'destination_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'to_account_nr_contains' => ['alias' => true, 'alias_for' => 'destination_account_nr_contains', 'needs_context' => true],
'destination_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'to_account_nr_ends' => ['alias' => true, 'alias_for' => 'destination_account_nr_ends', 'needs_context' => true],
'destination_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'to_account_nr_starts' => ['alias' => true, 'alias_for' => 'destination_account_nr_starts', 'needs_context' => true],
'account_is' => ['alias' => false, 'needs_context' => true],
'account_contains' => ['alias' => false, 'needs_context' => true],
'account_ends' => ['alias' => false, 'needs_context' => true],
'account_starts' => ['alias' => false, 'needs_context' => true],
'account_nr_is' => ['alias' => false, 'needs_context' => true],
'account_nr_contains' => ['alias' => false, 'needs_context' => true],
'account_nr_ends' => ['alias' => false, 'needs_context' => true],
'account_nr_starts' => ['alias' => false, 'needs_context' => true],
'category_is' => ['alias' => false, 'needs_context' => true],
'category_contains' => ['alias' => false, 'needs_context' => true],
'category' => ['alias' => true, 'alias_for' => 'category_contains', 'needs_context' => true],
'category_ends' => ['alias' => false, 'needs_context' => true],
'category_starts' => ['alias' => false, 'needs_context' => true],
'budget_is' => ['alias' => false, 'needs_context' => true],
'budget_contains' => ['alias' => false, 'needs_context' => true],
'budget' => ['alias' => true, 'alias_for' => 'budget_contains', 'needs_context' => true],
'budget_ends' => ['alias' => false, 'needs_context' => true],
'budget_starts' => ['alias' => false, 'needs_context' => true],
'bill_is' => ['alias' => false, 'needs_context' => true],
'bill_contains' => ['alias' => false, 'needs_context' => true],
'bill' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'bill_ends' => ['alias' => false, 'needs_context' => true],
'bill_starts' => ['alias' => false, 'needs_context' => true],
'external_id_is' => ['alias' => false, 'needs_context' => true],
'external_id_contains' => ['alias' => false, 'needs_context' => true],
'external_id' => ['alias' => true, 'alias_for' => 'external_id_contains', 'needs_context' => true],
'external_id_ends' => ['alias' => false, 'needs_context' => true],
'external_id_starts' => ['alias' => false, 'needs_context' => true],
'internal_reference_is' => ['alias' => false, 'needs_context' => true],
'internal_reference_contains' => ['alias' => false, 'needs_context' => true],
'internal_reference' => ['alias' => true, 'alias_for' => 'internal_reference_contains', 'needs_context' => true],
'internal_reference_ends' => ['alias' => false, 'needs_context' => true],
'internal_reference_starts' => ['alias' => false, 'needs_context' => true],
'external_url_is' => ['alias' => false, 'needs_context' => true],
'external_url_contains' => ['alias' => false, 'needs_context' => true],
'external_url' => ['alias' => true, 'alias_for' => 'external_url_contains', 'needs_context' => true],
'external_url_ends' => ['alias' => false, 'needs_context' => true],
'external_url_starts' => ['alias' => false, 'needs_context' => true],
'has_attachments' => ['alias' => false, 'needs_context' => false],
'has_any_category' => ['alias' => false, 'needs_context' => false],
'has_any_budget' => ['alias' => false, 'needs_context' => false],
'has_any_bill' => ['alias' => false, 'needs_context' => false],
'has_any_tag' => ['alias' => false, 'needs_context' => false],
'any_notes' => ['alias' => false, 'needs_context' => false],
'has_any_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'has_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'any_external_url' => ['alias' => false, 'needs_context' => false],
'has_any_external_url' => ['alias' => true, 'alias_for' => 'any_external_url', 'needs_context' => false],
'has_no_attachments' => ['alias' => false, 'needs_context' => false],
'has_no_category' => ['alias' => false, 'needs_context' => false],
'has_no_budget' => ['alias' => false, 'needs_context' => false],
'has_no_bill' => ['alias' => false, 'needs_context' => false],
'has_no_tag' => ['alias' => false, 'needs_context' => false],
'no_notes' => ['alias' => false, 'needs_context' => false],
'no_external_url' => ['alias' => false, 'needs_context' => false],
'source_is_cash' => ['alias' => false, 'needs_context' => false],
'destination_is_cash' => ['alias' => false, 'needs_context' => false],
'account_is_cash' => ['alias' => false, 'needs_context' => false],
'currency_is' => ['alias' => false, 'needs_context' => true],
'foreign_currency_is' => ['alias' => false, 'needs_context' => true],
'id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'journal_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'recurrence_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'date_on' => ['alias' => false, 'needs_context' => true],
'date' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_is' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'on' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_before' => ['alias' => false, 'needs_context' => true],
'before' => ['alias' => true, 'alias_for' => 'date_before', 'needs_context' => true],
'date_after' => ['alias' => false, 'needs_context' => true],
'after' => ['alias' => true, 'alias_for' => 'date_after', 'needs_context' => true],
'interest_date_on' => ['alias' => false, 'needs_context' => true],
'interest_date' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_is' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_before' => ['alias' => false, 'needs_context' => true],
'interest_date_after' => ['alias' => false, 'needs_context' => true],
'book_date_on' => ['alias' => false, 'needs_context' => true],
'book_date' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_is' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_before' => ['alias' => false, 'needs_context' => true],
'book_date_after' => ['alias' => false, 'needs_context' => true],
'process_date_on' => ['alias' => false, 'needs_context' => true],
'process_date' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_is' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_before' => ['alias' => false, 'needs_context' => true],
'process_date_after' => ['alias' => false, 'needs_context' => true],
'due_date_on' => ['alias' => false, 'needs_context' => true],
'due_date' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_is' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_before' => ['alias' => false, 'needs_context' => true],
'due_date_after' => ['alias' => false, 'needs_context' => true],
'payment_date_on' => ['alias' => false, 'needs_context' => true],
'payment_date' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_is' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_before' => ['alias' => false, 'needs_context' => true],
'payment_date_after' => ['alias' => false, 'needs_context' => true],
'invoice_date_on' => ['alias' => false, 'needs_context' => true],
'invoice_date' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_is' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_before' => ['alias' => false, 'needs_context' => true],
'invoice_date_after' => ['alias' => false, 'needs_context' => true],
'created_at_on' => ['alias' => false, 'needs_context' => true],
'created_at' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_is' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_before' => ['alias' => false, 'needs_context' => true],
'created_at_after' => ['alias' => false, 'needs_context' => true],
'updated_at_on' => ['alias' => false, 'needs_context' => true],
'updated_at' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_is' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_before' => ['alias' => false, 'needs_context' => true],
'updated_at_after' => ['alias' => false, 'needs_context' => true],
'created_on_on' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_on' => ['alias' => true, 'alias_for' => 'created_at', 'needs_context' => true],
'created_on_before' => ['alias' => true, 'alias_for' => 'created_at_before', 'needs_context' => true],
'created_on_after' => ['alias' => true, 'alias_for' => 'created_at_after', 'needs_context' => true],
'updated_on_on' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_on' => ['alias' => true, 'alias_for' => 'updated_at', 'needs_context' => true],
'updated_on_before' => ['alias' => true, 'alias_for' => 'updated_at_before', 'needs_context' => true],
'updated_on_after' => ['alias' => true, 'alias_for' => 'updated_at_after', 'needs_context' => true],
'amount_is' => ['alias' => false, 'needs_context' => true],
'amount' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_exactly' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_less' => ['alias' => false, 'needs_context' => true],
'amount_max' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'less' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'amount_more' => ['alias' => false, 'needs_context' => true],
'amount_min' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'more' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'foreign_amount_is' => ['alias' => false, 'needs_context' => true],
'foreign_amount' => ['alias' => true, 'alias_for' => 'foreign_amount_is', 'needs_context' => true],
'foreign_amount_less' => ['alias' => false, 'needs_context' => true],
'foreign_amount_max' => ['alias' => true, 'alias_for' => 'foreign_amount_less', 'needs_context' => true],
'foreign_amount_more' => ['alias' => false, 'needs_context' => true],
'foreign_amount_min' => ['alias' => true, 'alias_for' => 'foreign_amount_more', 'needs_context' => true],
'attachment_name_is' => ['alias' => false, 'needs_context' => true],
'attachment' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_is' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name_contains' => ['alias' => false, 'needs_context' => true],
'attachment_name_starts' => ['alias' => false, 'needs_context' => true],
'attachment_name_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes' => ['alias' => true, 'alias_for' => 'attachment_notes_are', 'needs_context' => true],
'attachment_notes_are' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contains' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contain' => ['alias' => true, 'alias_for' => 'attachment_notes_contains', 'needs_context' => true],
'attachment_notes_starts' => ['alias' => false, 'needs_context' => true],
'attachment_notes_start' => ['alias' => true, 'alias_for' => 'attachment_notes_starts', 'needs_context' => true],
'attachment_notes_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes_end' => ['alias' => true, 'alias_for' => 'attachment_notes_ends', 'needs_context' => true],
'exists' => ['alias' => false, 'needs_context' => false],
'sepa_ct_is' => ['alias' => false, 'needs_context' => true],
'no_external_id' => ['alias' => false, 'needs_context' => false],
'any_external_id' => ['alias' => false, 'needs_context' => false],
'user_action' => ['alias' => false, 'needs_context' => true],
'account_id' => ['alias' => false, 'needs_context' => true],
'reconciled' => ['alias' => false, 'needs_context' => false],
'source_account_id' => ['alias' => false, 'needs_context' => true],
'destination_account_id' => ['alias' => false, 'needs_context' => true],
'transaction_type' => ['alias' => false, 'needs_context' => true],
'type' => ['alias' => true, 'alias_for' => 'transaction_type', 'needs_context' => true],
'tag_is' => ['alias' => false, 'needs_context' => true],
'tag_is_not' => ['alias' => false, 'needs_context' => true],
'tag' => ['alias' => true, 'alias_for' => 'tag_is', 'needs_context' => true],
'tag_contains' => ['alias' => false, 'needs_context' => true],
'tag_ends' => ['alias' => false, 'needs_context' => true],
'tag_starts' => ['alias' => false, 'needs_context' => true],
'description_is' => ['alias' => false, 'needs_context' => true],
'description' => ['alias' => true, 'alias_for' => 'description_is', 'needs_context' => true],
'description_contains' => ['alias' => false, 'needs_context' => true],
'description_ends' => ['alias' => false, 'needs_context' => true],
'description_starts' => ['alias' => false, 'needs_context' => true],
'notes_is' => ['alias' => false, 'needs_context' => true],
'notes_are' => ['alias' => true, 'alias_for' => 'notes_is', 'needs_context' => true],
'notes_contains' => ['alias' => false, 'needs_context' => true],
'notes_contain' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes_ends' => ['alias' => false, 'needs_context' => true],
'notes_end' => ['alias' => true, 'alias_for' => 'notes_ends', 'needs_context' => true],
'notes_starts' => ['alias' => false, 'needs_context' => true],
'notes_start' => ['alias' => true, 'alias_for' => 'notes_starts', 'needs_context' => true],
'source_account_is' => ['alias' => false, 'needs_context' => true],
'from_account_is' => ['alias' => true, 'alias_for' => 'source_account_is', 'needs_context' => true],
'source_account_contains' => ['alias' => false, 'needs_context' => true],
'source' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from_account_contains' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'source_account_ends' => ['alias' => false, 'needs_context' => true],
'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true],
'source_account_starts' => ['alias' => false, 'needs_context' => true],
'from_account_starts' => ['alias' => true, 'alias_for' => 'source_account_starts', 'needs_context' => true],
'source_account_nr_is' => ['alias' => false, 'needs_context' => true],
'from_account_nr_is' => ['alias' => true, 'alias_for' => 'source_account_nr_is', 'needs_context' => true],
'source_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'from_account_nr_contains' => ['alias' => true, 'alias_for' => 'source_account_nr_contains', 'needs_context' => true],
'source_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'from_account_nr_ends' => ['alias' => true, 'alias_for' => 'source_account_nr_ends', 'needs_context' => true],
'source_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'from_account_nr_starts' => ['alias' => true, 'alias_for' => 'source_account_nr_starts', 'needs_context' => true],
'destination_account_is' => ['alias' => false, 'needs_context' => true],
'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', 'needs_context' => true],
'destination_account_contains' => ['alias' => false, 'needs_context' => true],
'destination' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to_account_contains' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'destination_account_ends' => ['alias' => false, 'needs_context' => true],
'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true],
'destination_account_starts' => ['alias' => false, 'needs_context' => true],
'to_account_starts' => ['alias' => true, 'alias_for' => 'destination_account_starts', 'needs_context' => true],
'destination_account_nr_is' => ['alias' => false, 'needs_context' => true],
'to_account_nr_is' => ['alias' => true, 'alias_for' => 'destination_account_nr_is', 'needs_context' => true],
'destination_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'to_account_nr_contains' => ['alias' => true, 'alias_for' => 'destination_account_nr_contains', 'needs_context' => true],
'destination_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'to_account_nr_ends' => ['alias' => true, 'alias_for' => 'destination_account_nr_ends', 'needs_context' => true],
'destination_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'to_account_nr_starts' => ['alias' => true, 'alias_for' => 'destination_account_nr_starts', 'needs_context' => true],
'account_is' => ['alias' => false, 'needs_context' => true],
'account_contains' => ['alias' => false, 'needs_context' => true],
'account_ends' => ['alias' => false, 'needs_context' => true],
'account_starts' => ['alias' => false, 'needs_context' => true],
'account_nr_is' => ['alias' => false, 'needs_context' => true],
'account_nr_contains' => ['alias' => false, 'needs_context' => true],
'account_nr_ends' => ['alias' => false, 'needs_context' => true],
'account_nr_starts' => ['alias' => false, 'needs_context' => true],
'category_is' => ['alias' => false, 'needs_context' => true],
'category_contains' => ['alias' => false, 'needs_context' => true],
'category' => ['alias' => true, 'alias_for' => 'category_contains', 'needs_context' => true],
'category_ends' => ['alias' => false, 'needs_context' => true],
'category_starts' => ['alias' => false, 'needs_context' => true],
'budget_is' => ['alias' => false, 'needs_context' => true],
'budget_contains' => ['alias' => false, 'needs_context' => true],
'budget' => ['alias' => true, 'alias_for' => 'budget_contains', 'needs_context' => true],
'budget_ends' => ['alias' => false, 'needs_context' => true],
'budget_starts' => ['alias' => false, 'needs_context' => true],
'bill_is' => ['alias' => false, 'needs_context' => true],
'bill_contains' => ['alias' => false, 'needs_context' => true],
'bill' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'bill_ends' => ['alias' => false, 'needs_context' => true],
'bill_starts' => ['alias' => false, 'needs_context' => true],
'external_id_is' => ['alias' => false, 'needs_context' => true],
'external_id_contains' => ['alias' => false, 'needs_context' => true],
'external_id' => ['alias' => true, 'alias_for' => 'external_id_contains', 'needs_context' => true],
'external_id_ends' => ['alias' => false, 'needs_context' => true],
'external_id_starts' => ['alias' => false, 'needs_context' => true],
'internal_reference_is' => ['alias' => false, 'needs_context' => true],
'internal_reference_contains' => ['alias' => false, 'needs_context' => true],
'internal_reference' => ['alias' => true, 'alias_for' => 'internal_reference_contains', 'needs_context' => true],
'internal_reference_ends' => ['alias' => false, 'needs_context' => true],
'internal_reference_starts' => ['alias' => false, 'needs_context' => true],
'external_url_is' => ['alias' => false, 'needs_context' => true],
'external_url_contains' => ['alias' => false, 'needs_context' => true],
'external_url' => ['alias' => true, 'alias_for' => 'external_url_contains', 'needs_context' => true],
'external_url_ends' => ['alias' => false, 'needs_context' => true],
'external_url_starts' => ['alias' => false, 'needs_context' => true],
'has_attachments' => ['alias' => false, 'needs_context' => false],
'has_any_category' => ['alias' => false, 'needs_context' => false],
'has_any_budget' => ['alias' => false, 'needs_context' => false],
'has_any_bill' => ['alias' => false, 'needs_context' => false],
'has_any_tag' => ['alias' => false, 'needs_context' => false],
'any_notes' => ['alias' => false, 'needs_context' => false],
'has_any_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'has_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'any_external_url' => ['alias' => false, 'needs_context' => false],
'has_any_external_url' => ['alias' => true, 'alias_for' => 'any_external_url', 'needs_context' => false],
'has_no_attachments' => ['alias' => false, 'needs_context' => false],
'has_no_category' => ['alias' => false, 'needs_context' => false],
'has_no_budget' => ['alias' => false, 'needs_context' => false],
'has_no_bill' => ['alias' => false, 'needs_context' => false],
'has_no_tag' => ['alias' => false, 'needs_context' => false],
'no_notes' => ['alias' => false, 'needs_context' => false],
'no_external_url' => ['alias' => false, 'needs_context' => false],
'source_is_cash' => ['alias' => false, 'needs_context' => false],
'destination_is_cash' => ['alias' => false, 'needs_context' => false],
'account_is_cash' => ['alias' => false, 'needs_context' => false],
'currency_is' => ['alias' => false, 'needs_context' => true],
'foreign_currency_is' => ['alias' => false, 'needs_context' => true],
'id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'journal_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'recurrence_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'date_on' => ['alias' => false, 'needs_context' => true],
'date' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_is' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'on' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_before' => ['alias' => false, 'needs_context' => true],
'before' => ['alias' => true, 'alias_for' => 'date_before', 'needs_context' => true],
'date_after' => ['alias' => false, 'needs_context' => true],
'after' => ['alias' => true, 'alias_for' => 'date_after', 'needs_context' => true],
'interest_date_on' => ['alias' => false, 'needs_context' => true],
'interest_date' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_is' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_before' => ['alias' => false, 'needs_context' => true],
'interest_date_after' => ['alias' => false, 'needs_context' => true],
'book_date_on' => ['alias' => false, 'needs_context' => true],
'book_date' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_is' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_before' => ['alias' => false, 'needs_context' => true],
'book_date_after' => ['alias' => false, 'needs_context' => true],
'process_date_on' => ['alias' => false, 'needs_context' => true],
'process_date' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_is' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_before' => ['alias' => false, 'needs_context' => true],
'process_date_after' => ['alias' => false, 'needs_context' => true],
'due_date_on' => ['alias' => false, 'needs_context' => true],
'due_date' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_is' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_before' => ['alias' => false, 'needs_context' => true],
'due_date_after' => ['alias' => false, 'needs_context' => true],
'payment_date_on' => ['alias' => false, 'needs_context' => true],
'payment_date' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_is' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_before' => ['alias' => false, 'needs_context' => true],
'payment_date_after' => ['alias' => false, 'needs_context' => true],
'invoice_date_on' => ['alias' => false, 'needs_context' => true],
'invoice_date' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_is' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_before' => ['alias' => false, 'needs_context' => true],
'invoice_date_after' => ['alias' => false, 'needs_context' => true],
'created_at_on' => ['alias' => false, 'needs_context' => true],
'created_at' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_is' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_before' => ['alias' => false, 'needs_context' => true],
'created_at_after' => ['alias' => false, 'needs_context' => true],
'updated_at_on' => ['alias' => false, 'needs_context' => true],
'updated_at' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_is' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_before' => ['alias' => false, 'needs_context' => true],
'updated_at_after' => ['alias' => false, 'needs_context' => true],
'created_on_on' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_on' => ['alias' => true, 'alias_for' => 'created_at', 'needs_context' => true],
'created_on_before' => ['alias' => true, 'alias_for' => 'created_at_before', 'needs_context' => true],
'created_on_after' => ['alias' => true, 'alias_for' => 'created_at_after', 'needs_context' => true],
'updated_on_on' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_on' => ['alias' => true, 'alias_for' => 'updated_at', 'needs_context' => true],
'updated_on_before' => ['alias' => true, 'alias_for' => 'updated_at_before', 'needs_context' => true],
'updated_on_after' => ['alias' => true, 'alias_for' => 'updated_at_after', 'needs_context' => true],
'amount_is' => ['alias' => false, 'needs_context' => true],
'amount' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_exactly' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_less' => ['alias' => false, 'needs_context' => true],
'amount_max' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'less' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'amount_more' => ['alias' => false, 'needs_context' => true],
'amount_min' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'more' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'foreign_amount_is' => ['alias' => false, 'needs_context' => true],
'foreign_amount' => ['alias' => true, 'alias_for' => 'foreign_amount_is', 'needs_context' => true],
'foreign_amount_less' => ['alias' => false, 'needs_context' => true],
'foreign_amount_max' => ['alias' => true, 'alias_for' => 'foreign_amount_less', 'needs_context' => true],
'foreign_amount_more' => ['alias' => false, 'needs_context' => true],
'foreign_amount_min' => ['alias' => true, 'alias_for' => 'foreign_amount_more', 'needs_context' => true],
'attachment_name_is' => ['alias' => false, 'needs_context' => true],
'attachment' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_is' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name_contains' => ['alias' => false, 'needs_context' => true],
'attachment_name_starts' => ['alias' => false, 'needs_context' => true],
'attachment_name_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes' => ['alias' => true, 'alias_for' => 'attachment_notes_are', 'needs_context' => true],
'attachment_notes_are' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contains' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contain' => ['alias' => true, 'alias_for' => 'attachment_notes_contains', 'needs_context' => true],
'attachment_notes_starts' => ['alias' => false, 'needs_context' => true],
'attachment_notes_start' => ['alias' => true, 'alias_for' => 'attachment_notes_starts', 'needs_context' => true],
'attachment_notes_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes_end' => ['alias' => true, 'alias_for' => 'attachment_notes_ends', 'needs_context' => true],
'exists' => ['alias' => false, 'needs_context' => false],
'sepa_ct_is' => ['alias' => false, 'needs_context' => true],
'no_external_id' => ['alias' => false, 'needs_context' => false],
'any_external_id' => ['alias' => false, 'needs_context' => false],
// based on source or destination balance. Very heavy search.
'source_balance_gte' => ['alias' => false, 'needs_context' => true],
'source_balance_gt' => ['alias' => false, 'needs_context' => true],
'source_balance_lte' => ['alias' => false, 'needs_context' => true],
'source_balance_lt' => ['alias' => false, 'needs_context' => true],
'source_balance_is' => ['alias' => false, 'needs_context' => true],
'destination_balance_gte' => ['alias' => false, 'needs_context' => true],
'destination_balance_gt' => ['alias' => false, 'needs_context' => true],
'destination_balance_lte' => ['alias' => false, 'needs_context' => true],
'destination_balance_lt' => ['alias' => false, 'needs_context' => true],
'destination_balance_is' => ['alias' => false, 'needs_context' => true],
],
];

24
package-lock.json generated
View File

@@ -3621,9 +3621,9 @@
}
},
"node_modules/admin-lte": {
"version": "4.0.0-beta2",
"resolved": "https://registry.npmjs.org/admin-lte/-/admin-lte-4.0.0-beta2.tgz",
"integrity": "sha512-Ofav0BKnCnz+IeeXrHQZ6JWnHouwv+fDYyfagRpjfFaMBmYCljA2Qo1+fCGkJuJn/SfNPhFpJhbUt+l2tH0LwA==",
"version": "4.0.0-beta3",
"resolved": "https://registry.npmjs.org/admin-lte/-/admin-lte-4.0.0-beta3.tgz",
"integrity": "sha512-q2VoAOu1DtZ7z41M2gQ05VMNYkFCAMxFU+j/HUMwCOlr/e3VhO+qww2SGJw4OxBw5nZQ7YV78+wK2RiB7ConzQ==",
"license": "MIT"
},
"node_modules/ag-charts-types": {
@@ -3702,9 +3702,9 @@
}
},
"node_modules/alpinejs": {
"version": "3.14.7",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.7.tgz",
"integrity": "sha512-ScnbydNBcWVnCiVupD3wWUvoMPm8244xkvDNMxVCspgmap9m4QuJ7pjc+77UtByU+1+Ejg0wzYkP4mQaOMcvng==",
"version": "3.14.8",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.8.tgz",
"integrity": "sha512-wT2fuP2DXpGk/jKaglwy7S/IJpm1FD+b7U6zUrhwErjoq5h27S4dxkJEXVvhbdwyPv9U+3OkUuNLkZT4h2Kfrg==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "~3.1.1"
@@ -5646,9 +5646,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.75",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz",
"integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==",
"version": "1.5.76",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz",
"integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==",
"dev": true,
"license": "ISC"
},
@@ -6104,9 +6104,9 @@
}
},
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
"integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
"dev": true,
"license": "ISC",
"dependencies": {

View File

@@ -26,6 +26,7 @@ $(function () {
});
function drawChart() {
"use strict";
lineChart(accountFrontpageUrl, 'accounts-chart');
@@ -37,13 +38,77 @@ function drawChart() {
columnChart('chart/category/frontpage', 'categories-chart');
columnChart(accountExpenseUrl, 'expense-accounts-chart');
columnChart(accountRevenueUrl, 'revenue-accounts-chart');
// get balance box:
getBalanceBox();
getBillsBox();
getAvailableBox();
getNetWorthBox();
getPiggyBanks();
getAllBoxes();
function getAllBoxes() {
// get summary.
$.getJSON('api/v1/summary/basic?start=' + sessionStart + '&end=' + sessionEnd).done(function (data) {
var key;
// balance.
var balance_top = [];
var balance_bottom = [];
// bills
var unpaid = [];
var paid = [];
// left to spend.
var left_to_spend_top = [];
var left_to_spend_bottom = [];
// net worth
var net_worth = [];
for (key in data) {
// balance
if (key.substring(0, 11) === 'balance-in-') {
balance_top.push(data[key].value_parsed);
balance_bottom.push(data[key].sub_title);
}
// bills
if (key.substring(0, 16) === 'bills-unpaid-in-') {
unpaid.push(data[key].value_parsed);
}
if (key.substring(0, 14) === 'bills-paid-in-') {
paid.push(data[key].value_parsed);
}
// left to spend
if (key.substring(0, 17) === 'left-to-spend-in-') {
left_to_spend_top.push(data[key].value_parsed);
left_to_spend_bottom.push(data[key].sub_title);
if(parseFloat(data[key].monetary_value) < 0) {
$('#box-left-to-spend-box').removeClass('bg-green-gradient').addClass('bg-red-gradient');
}
}
// net worth
if (key.substring(0, 13) === 'net-worth-in-') {
net_worth.push(data[key].value_parsed);
}
}
// balance
$('#box-balance-sums').html(balance_top.join(', '));
$('#box-balance-list').html(balance_bottom.join(', '));
// bills
$('#box-bills-unpaid').html(unpaid.join(', '));
$('#box-bills-paid').html(paid.join(', '));
// left to spend
$('#box-left-to-spend').html(left_to_spend_top.join(', '));
$('#box-left-per-day').html(left_to_spend_bottom.join(', '));
// net worth
$('#box-net-worth').html(net_worth.join(', '));
});
}
//getBoxAmounts();
}
@@ -58,121 +123,3 @@ function getPiggyBanks() {
}
});
}
function getNetWorthBox() {
// box-net-worth
$.getJSON('json/box/net-worth').done(function (data) {
$('#box-net-worth').html(data.net_worths.join(', '));
});
}
/**
*
*/
function getAvailableBox() {
// box-left-to-spend
// box-left-per-day
// * 0) If the user has available amount this period and has overspent: overspent box.
// * 1) If the user has available amount this period and has NOT overspent: left to spend box.
// * 2) if the user has no available amount set this period: spent per day
$.getJSON('json/box/available').done(function (data) {
$('#box-left-to-spend-text').text(data.title);
if (0 === data.display) {
$('#box-left-to-spend-box').removeClass('bg-green-gradient').addClass('bg-red-gradient');
$('#box-left-to-spend').html(data.left_to_spend);
$('#box-left-per-day').html(data.left_per_day);
}
if (1 === data.display) {
$('#box-left-to-spend').html(data.left_to_spend);
$('#box-left-per-day').html(data.left_per_day);
}
if (2 === data.display) {
$('#box-left-to-spend').html(data.spent_total);
$('#box-left-per-day').html(data.spent_per_day);
}
});
}
/**
*
*/
function getBillsBox() {
// box-bills-unpaid
// box-bills-paid
// get summary.
$.getJSON('api/v1/summary/basic?start=' + sessionStart + '&end=' + sessionEnd).done(function (data) {
var key;
var unpaid = [];
var paid = [];
for (key in data) {
//console.log(key);
if (key.substr(0, 16) === 'bills-unpaid-in-') {
// only when less than 3.
if (unpaid.length < 3) {
unpaid.push(data[key].value_parsed);
}
}
if (key.substr(0, 14) === 'bills-paid-in-') {
// only when less than 5.
if (paid.length < 3) {
paid.push(data[key].value_parsed);
}
}
}
$('#box-bills-unpaid').html(unpaid.join(', '));
$('#box-bills-paid').html(paid.join(', '));
});
}
/**
*
*/
function getBalanceBox() {
// box-balance-sums
// box-balance-list
$.getJSON('json/box/balance').done(function (data) {
if (data.size === 1) {
// show balance in "sums", show single entry in list.
for (var x in data.sums) {
$('#box-balance-sums').html(data.sums[x]);
$('#box-balance-list').html(data.incomes[x] + ' + ' + data.expenses[x]);
}
return;
}
// do not use "sums", only use list.
$('#box-balance-progress').remove();
var expense, string, sum, income, current;
// first loop, echo only "preferred".
for (x in data.sums) {
current = $('#box-balance-list').html();
sum = data.sums[x];
expense = data.expenses[x];
income = data.incomes[x];
string = income + ' + ' + expense + ': ' + sum;
if (data.preferred == x) {
$('#box-balance-list').html(current + '<span title="' + string + '">' + string + '</span>' + '<br>');
}
}
// then list the others (only 1 space)
var count = 0;
for (x in data.sums) {
if (count > 2) {
return;
}
current = $('#box-balance-list').html();
sum = data.sums[x];
expense = data.expenses[x];
income = data.incomes[x];
string = income + ' + ' + expense + ': ' + sum;
if (data.preferred != x) {
$('#box-balance-list').html(current + '<span title="' + string + '">' + string + '</span>' + '<br>');
}
count++;
}
});
}

View File

@@ -27,9 +27,7 @@
<h3 class="box-title">{{ $t('firefly.header_exchange_rates') }}</h3>
</div>
<div class="box-body">
<p>
{{ $t('firefly.exchange_rates_intro') }}
</p>
<p v-html="$t('firefly.exchange_rates_intro')"></p>
</div>
</div>
</div>

View File

@@ -130,12 +130,12 @@
"response": "Antwort",
"visit_webhook_url": "Webhook-URL besuchen",
"reset_webhook_secret": "Webhook Secret zur\u00fccksetzen",
"header_exchange_rates": "Wechselkurse",
"exchange_rates_intro": "Firefly III unterst\u00fctzt das Herunterladen und Verwenden von Wechselkursen. Lesen Sie mehr dar\u00fcber in <a href=\u201ehttps:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\u201c>der Dokumentation<\/a>.",
"exchange_rates_from_to": "Zwischen {from} und {to} (und umgekehrt)",
"header_exchange_rates": "Exchange rates",
"exchange_rates_intro": "Firefly III supports downloading and using exchange rates. Read more about this in <a href=\"https:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\">the documentation<\/a>.",
"exchange_rates_from_to": "Between {from} and {to} (and the other way around)",
"exchange_rates_intro_rates": "Firefly III bla bla bla exchange rates. Inverse is automatically calculated if not provided. Will go back to last found rate.",
"header_exchange_rates_rates": "Wechselkurse",
"header_exchange_rates_table": "Tabelle mit Wechselkursen",
"header_exchange_rates_rates": "Exchange rates",
"header_exchange_rates_table": "Table with exchange rates",
"help_rate_form": "On this day, how many {to} will you get for one {from}?",
"add_new_rate": "Add a new exchange rate",
"save_new_rate": "Save new rate"

View File

@@ -130,12 +130,12 @@
"response": "R\u00e9ponse",
"visit_webhook_url": "Visiter l'URL du webhook",
"reset_webhook_secret": "R\u00e9initialiser le secret du webhook",
"header_exchange_rates": "Taux de change",
"exchange_rates_intro": "Firefly III prend en charge le chargement et l'utilisation des taux de change. En savoir plus \u00e0 ce sujet dans <a href=\"https:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\">la documentation<\/a>.",
"exchange_rates_from_to": "Entre {from} et {to} (et l'inverse)",
"header_exchange_rates": "Exchange rates",
"exchange_rates_intro": "Firefly III supports downloading and using exchange rates. Read more about this in <a href=\"https:\/\/docs.firefly-iii.org\/LOL_NOT_FINISHED_YET_TODO\">the documentation<\/a>.",
"exchange_rates_from_to": "Between {from} and {to} (and the other way around)",
"exchange_rates_intro_rates": "Firefly III bla bla bla exchange rates. Inverse is automatically calculated if not provided. Will go back to last found rate.",
"header_exchange_rates_rates": "Taux de change",
"header_exchange_rates_table": "Table avec taux de change",
"header_exchange_rates_rates": "Exchange rates",
"header_exchange_rates_table": "Table with exchange rates",
"help_rate_form": "On this day, how many {to} will you get for one {from}?",
"add_new_rate": "Add a new exchange rate",
"save_new_rate": "Save new rate"

File diff suppressed because it is too large Load Diff

View File

@@ -66,14 +66,24 @@
{% if objectType != 'liabilities' %}
<td style="text-align: right;">
<span style="margin-right:5px;">
{{ formatAmountByAccount(account, account.endBalance) }}
{% for key, balance in account.endBalances %}
<span title="{{ key }}">
{% if 'balance' == key or 'native_balance' == key %}
{{ formatAmountBySymbol(balance, defaultCurrency.symbol, defaultCurrency.currency.decimal_places) }}
{% else %}
({{ formatAmountByCode(balance, key) }})
{% endif %}
</span>
{% endfor %}
</span>
</td>
{% endif %}
{% if objectType == 'liabilities' %}
<td style="text-align: right;">
{% if '-' != account.current_debt %}
<span class="text-info money-transfer">{{ formatAmountByAccount(account, account.current_debt, false) }}</span>
<span class="text-info money-transfer">
{{ formatAmountBySymbol(account.current_debt, account.currency.symbol, account.currency.decimal_places, false) }}
</span>
{% endif %}
</td>
{% endif %}
@@ -99,7 +109,13 @@
{% endif %}
<td class="hidden-sm hidden-xs hidden-md" style="text-align: right;">
<span style="margin-right:5px;">
{{ formatAmountByAccount(account, account.difference) }}
{% for key, balance in account.differences %}
{% if 'balance' == key or 'native_balance' == key %}
{{ formatAmountBySymbol(balance, defaultCurrency.symbol, defaultCurrency.currency.decimal_places) }}
{% else %}
({{ formatAmountByCode(balance, key) }})
{% endif %}
{% endfor %}
</span>
</td>
<td class="hidden-sm hidden-xs">

View File

@@ -12,6 +12,10 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and null != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount*-1, defaultCurrency.symbol, foreign_currency_.decimal_places) }})
{% endif %}
{% elseif transaction.transaction_type_type == 'Transfer' %}
<span class="text-info money-transfer">
{# transfer away: #}
@@ -20,13 +24,18 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places, false) }})
{% endif %}
{% if convertToNative and null != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount*-1, defaultCurrency.symbol, foreign_currency_.decimal_places) }})
{% endif %}
{% else %}
{{ formatAmountBySymbol(transaction.amount*-1, transaction.currency_symbol, transaction.currency_decimal_places, false) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places, false) }})
{% endif %}
{% if convertToNative and null != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount*-1, defaultCurrency.symbol, foreign_currency_.decimal_places) }})
{% endif %}
{% endif %}
{# transfer to #}
</span>
{% elseif transaction.transaction_type_type == 'Opening balance' %}
@@ -35,11 +44,17 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and null != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount*-1, defaultCurrency.symbol, foreign_currency_.decimal_places) }})
{% endif %}
{% else %}
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and null != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount, defaultCurrency.symbol, foreign_currency_.decimal_places) }})
{% endif %}
{% endif %}
{% elseif transaction.transaction_type_type == 'Reconciliation' %}
{% if transaction.source_account_type == 'Reconciliation account' %}
@@ -47,17 +62,26 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and null != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount*-1, defaultCurrency.symbol, foreign_currency_.decimal_places) }})
{% endif %}
{% else %}
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and null != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount, defaultCurrency.symbol, foreign_currency_.decimal_places) }})
{% endif %}
{% endif %}
{% else %}
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and null != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount, defaultCurrency.symbol, foreign_currency_.decimal_places) }})
{% endif %}
{% endif %}
</span>
</a>

View File

@@ -59,13 +59,14 @@
<td colspan="1" style="text-align:right;border-top:1px #aaa solid;">
{% for sum in group.sums %}
{% if group.transaction_type == 'Deposit' %}
{{ formatAmountBySymbol(sum.amount*-1, sum.currency_symbol, sum.currency_decimal_places) }}{% if loop.index != group.sums|length %},{% endif %}
{{ formatAmountBySymbol(sum.amount*-1, sum.currency_symbol, sum.currency_decimal_places) }}{# (TODO NATIVE1) #}{% if loop.index != group.sums|length %},{% endif %}
{% elseif group.transaction_type == 'Transfer' %}
<span class="text-info money-transfer">
{{ formatAmountBySymbol(sum.amount*-1, sum.currency_symbol, sum.currency_decimal_places, false) }}{% if loop.index != group.sums|length %},{% endif %}
{{ formatAmountBySymbol(sum.amount*-1, sum.currency_symbol, sum.currency_decimal_places, false) }}{# (TODO NATIVE2) #}{% if loop.index != group.sums|length %},{% endif %}
</span>
{% else %}
{{ formatAmountBySymbol(sum.amount, sum.currency_symbol, sum.currency_decimal_places) }}{% if loop.index != group.sums|length %},{% endif %}
{{ formatAmountBySymbol(sum.amount, sum.currency_symbol, sum.currency_decimal_places) }}{# (TODO NATIVE3) #}{% if loop.index != group.sums|length %},{% endif %}
{% endif %}
{% endfor %}
</td>
@@ -153,6 +154,7 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{# (TODO NATIVE1) #}
{# transfer #}
{% elseif transaction.transaction_type_type == 'Transfer' %}
<span class="text-info money-transfer">
@@ -160,6 +162,7 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places, false) }})
{% endif %}
{# (TODO NATIVE2) #}
</span>
{# opening balance #}
{% elseif transaction.transaction_type_type == 'Opening balance' %}
@@ -168,11 +171,13 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{# (TODO NATIVE3) #}
{% else %}
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{# (TODO NATIVE4) #}
{% endif %}
{# reconciliation #}
{% elseif transaction.transaction_type_type == 'Reconciliation' %}
@@ -181,11 +186,13 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{# (TODO NATIVE5) #}
{% else %}
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{# (TODO NATIVE6) #}
{% endif %}
{# liability credit #}
{% elseif transaction.transaction_type_type == 'Liability credit' %}
@@ -194,11 +201,13 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{# (TODO NATIVE7) #}
{% else %}
{{ formatAmountBySymbol(transaction.amount*-1, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{# (TODO NATIVE8) #}
{% endif %}
@@ -208,6 +217,7 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{# (TODO NATIVE9) #}
{% endif %}
</td>
<td style=" {{ style|raw }}">

View File

@@ -99,6 +99,15 @@
{{ ExpandedForm.checkbox('customFiscalYear','1',isCustomFiscalYear,{ 'label' : 'pref_custom_fiscal_year_label'|_ }) }}
{{ ExpandedForm.date('fiscalYearStart',fiscalYearStart,{ 'label' : 'pref_fiscal_year_start_label'|_ }) }}
</div>
{# conversion back to natiev #}
<div class="preferences-box">
<h3>{{ 'pref_convert_to_native'|_ }}</h3>
<p class="text-info">
{{ 'pref_convert_to_native_help'|_ }}
</p>
{{ ExpandedForm.checkbox('convertToNative','1',convertToNative,{ 'label' : 'pref_convert_native_help'|_ }) }}
</div>
</div>
{# general settings column B #}

View File

@@ -58,7 +58,7 @@
account_name: account.name|escape,
url: url,
end: auditData[account.id].dayBefore,
balance: formatAmountByAccount(account, auditData[account.id].dayBeforeBalance)
balance: formatAmountByAccount(account, auditData[account.id].dayBeforeBalance.balance)
})|raw }}
</p>
{% include 'reports.partials.journals-audit' with {'journals': auditData[account.id].journals,'account':account} %}

View File

@@ -22,6 +22,8 @@
<th class="hide-create_date">{{ trans('list.create_date') }}</th>
<th class="hide-update_date">{{ trans('list.update_date') }}</th>
<th class="hide-notes">{{ trans('list.notes') }}</th>
{# even more optional fields #}
<th class="hide-interest_date">{{ trans('list.interest_date') }}</th>
<th class="hide-book_date">{{ trans('list.book_date') }}</th>
@@ -144,6 +146,9 @@
<td class="hide-update_date">
{{ journal.updated_at.isoFormat(dateTimeFormat) }}
</td>
<td class="hide-notes">
{{ journal.notes|default('')|markdown }}
</td>
<!-- more new dates -->
<td class="hide-interest_date">

View File

@@ -80,7 +80,7 @@ Route::group(
Route::group(
['middleware' => 'binders-only', 'namespace' => 'FireflyIII\Http\Controllers\System'],
static function (): void {
Route::get('offline', static fn () => view('errors.offline'));
// Route::get('offline', static fn () => view('errors.offline'));
Route::get('health', ['uses' => 'HealthcheckController@check', 'as' => 'healthcheck']);
}
);
@@ -117,7 +117,7 @@ Route::group(
Route::get('error', ['uses' => 'DebugController@displayError', 'as' => 'error']);
Route::post('logout', ['uses' => 'Auth\LoginController@logout', 'as' => 'logout']);
Route::get('flush', ['uses' => 'DebugController@flush', 'as' => 'flush']);
// Route::get('routes', ['uses' => 'DebugController@routes', 'as' => 'routes']);
Route::get('routes', ['uses' => 'DebugController@routes', 'as' => 'routes']);
Route::get('debug', 'DebugController@index')->name('debug');
}
);