Compare commits

..

18 Commits

Author SHA1 Message Date
github-actions
f4868126c1 Auto commit for release 'branch-v6.2' on 2024-12-26 2024-12-26 05:25:46 +01:00
James Cole
00147e98dd Fix home chart. 2024-12-26 05:21:28 +01:00
James Cole
6d22663ca2 Fix charts and balances. 2024-12-26 05:11:32 +01:00
James Cole
756bb9cf5e Fix overview, skip chart for now. 2024-12-25 11:59:15 +01:00
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
47 changed files with 1443 additions and 729 deletions

View File

@@ -116,7 +116,7 @@ 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')->finalAccountBalanceInRange($account, $start, clone $end);
$range = app('steam')->finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
// 2022-10-11 this method no longer converts to float.
$previous = array_values($range)[0];
while ($currentStart <= $end) {

View File

@@ -50,11 +50,12 @@ abstract class Controller extends BaseController
use DispatchesJobs;
use ValidatesRequests;
protected const string CONTENT_TYPE = 'application/vnd.api+json';
protected const string CONTENT_TYPE = 'application/vnd.api+json';
/** @var array<int, string> */
protected array $allowedSort;
protected ParameterBag $parameters;
protected bool $convertToNative = false;
/**
* Controller constructor.
@@ -67,8 +68,10 @@ abstract class Controller extends BaseController
function ($request, $next) {
$this->parameters = $this->getParameters();
if (auth()->check()) {
$language = app('steam')->getLanguage();
$language = app('steam')->getLanguage();
$this->convertToNative = app('preferences')->get('convert_to_native', false)->data;
app()->setLocale($language);
}
return $next($request);

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

@@ -118,7 +118,7 @@ class AccountController extends Controller
'native_entries' => [],
];
$currentStart = clone $params['start'];
$range = app('steam')->finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $currency);
$range = app('steam')->finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative);
$previous = array_values($range)[0]['balance'];
$previousNative = array_values($range)[0]['native_balance'];

View File

@@ -54,9 +54,10 @@ class Controller extends BaseController
{
use ValidatesUserGroupTrait;
protected const string CONTENT_TYPE = 'application/vnd.api+json';
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
protected const string CONTENT_TYPE = 'application/vnd.api+json';
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
protected ParameterBag $parameters;
protected bool $convertToNative = false;
public function __construct()
{

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

@@ -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

@@ -746,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)));
@@ -874,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.
*/

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')->finalAccountsBalance($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,7 +159,7 @@ class NetWorth implements NetWorthInterface
*/
$accounts = $this->getAccounts();
$return = [];
$balances = app('steam')->finalAccountsBalance($accounts, $date);
$balances = Steam::finalAccountsBalance($accounts, $date);
foreach ($accounts as $account) {
$currency = $this->getRepository()->getAccountCurrency($account);
$balance = $balances[$account->id]['balance'] ?? '0';

View File

@@ -28,6 +28,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\BasicDataSupport;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
@@ -94,13 +95,13 @@ class IndexController extends Controller
$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 = Steam::filterAccountBalance($startBalances[$account->id] ?? [], $account, $this->convertToNative, $currency);
$account->endBalances = Steam::filterAccountBalance($endBalances[$account->id] ?? [], $account, $this->convertToNative, $currency);
$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));
@@ -154,16 +155,18 @@ class IndexController extends Controller
$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 = Steam::filterAccountBalance($startBalances[$account->id] ?? [], $account, $this->convertToNative, $currency);
$account->endBalances = Steam::filterAccountBalance($endBalances[$account->id] ?? [], $account, $this->convertToNative, $currency);
$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'))
@@ -172,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 ?? $this->defaultCurrency;
$account->iban = implode(' ', str_split((string) $account->iban, 4));
}
);
// make paginator:
@@ -187,4 +193,14 @@ class IndexController extends Controller
return view('accounts.index', compact('objectType', 'inactiveCount', 'subTitleIcon', 'subTitle', 'page', 'accounts'));
}
private function subtract(array $startBalances, array $endBalances)
{
$result = [];
foreach ($endBalances as $key => $value) {
$result[$key] = bcsub($value, $startBalances[$key] ?? '0');
}
return $result;
}
}

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\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\PeriodOverview;
use Illuminate\Contracts\View\Factory;
@@ -90,7 +91,7 @@ class ShowController extends Controller
// @var Carbon $end
$end ??= session('end');
if ($end < $start) {
if ($end->lt($start)) {
[$start, $end] = [$end, $start];
}
$location = $this->repository->getLocation($account);
@@ -99,7 +100,8 @@ class ShowController extends Controller
$subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type));
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$accountCurrency = $this->repository->getAccountCurrency($account);
$currency = $accountCurrency ?? Amount::getDefaultCurrency();
$fStart = $start->isoFormat($this->monthAndDayFormat);
$fEnd = $end->isoFormat($this->monthAndDayFormat);
$subTitle = (string) trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
@@ -129,7 +131,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 = Steam::finalAccountBalance($account, $end)['balance'];
$balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $end), $account, $this->convertToNative, $accountCurrency);
return view(
'accounts.show',
@@ -148,7 +150,7 @@ class ShowController extends Controller
'end',
'chartUrl',
'location',
'balance'
'balances'
)
);
}
@@ -175,7 +177,7 @@ class ShowController extends Controller
$subTitleIcon = config('firefly.subIconsByIdentifier.'.$account->accountType->type);
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$currency = $this->repository->getAccountCurrency($account) ?? Amount::getDefaultCurrency();
$subTitle = (string) trans('firefly.all_journals_for_account', ['name' => $account->name]);
$periods = new Collection();

View File

@@ -30,12 +30,12 @@ use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
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;
@@ -82,6 +82,8 @@ class AccountController extends Controller
*/
public function expenseAccounts(): JsonResponse
{
Log::debug('RevenueAccounts');
/** @var Carbon $start */
$start = clone session('start', today(config('app.timezone'))->startOfMonth());
@@ -90,6 +92,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());
@@ -100,6 +103,7 @@ class AccountController extends Controller
$currencies = [];
$chartData = [];
$tempData = [];
$default = Amount::getDefaultCurrency();
// grab all accounts and names
$accounts = $this->accountRepository->getAccountsByType([AccountTypeEnum::EXPENSE->value]);
@@ -109,26 +113,47 @@ class AccountController extends Controller
$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 $currencyCode => $endAmount) {
if (3 !== strlen($currencyCode)) {
// 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][$currencyCode] ?? '0');
$diff = bcsub((string) $endAmount, $startAmount);
$currencies[$currencyCode] ??= $this->currencyRepository->findByCode($currencyCode);
$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' => $currencies[$currencyCode]->id,
'currency_id' => $currencies[$searchCode]->id,
];
}
}
@@ -140,8 +165,6 @@ class AccountController extends Controller
}
$currencies = $newCurrencies;
// sort temp array by amount.
$amounts = array_column($tempData, 'diff_float');
array_multisort($amounts, SORT_DESC, $tempData);
@@ -394,28 +417,137 @@ class AccountController extends Controller
*/
public function period(Account $account, Carbon $start, Carbon $end): JsonResponse
{
$chartData = [];
$cache = new CacheProperties();
Log::debug('Now in period()');
$chartData = [];
$cache = new CacheProperties();
$cache->addProperty('chart.account.period');
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($this->convertToNative);
$cache->addProperty($account->id);
if ($cache->has()) {
return response()->json($cache->get());
// return response()->json($cache->get());
}
$currencies = $this->accountRepository->getUsedCurrencies($account);
// if the account is not expense or revenue, just use the account's default currency.
if (!in_array($account->accountType->type, [AccountType::REVENUE, AccountType::EXPENSE], true)) {
$currencies = [$this->accountRepository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency()];
// collect and filter balances for the entire period.
$step = $this->calculateStep($start, $end);
Log::debug(sprintf('Step is %s', $step));
$locale = app('steam')->getLocale();
$return = [];
// fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
// have to make sure this chart is always based on the balance at the END of the period.
// This period depends on the size of the chart
$current = clone $start;
$current = app('navigation')->endOfX($current, $step, null);
$format = (string) trans('config.month_and_day_js', [], $locale);
$accountCurrency = $this->accountRepository->getAccountCurrency($account);
Log::debug('One');
$range = Steam::finalAccountBalanceInRange($account, $start, $end, $this->convertToNative);
Log::debug('Two');
$range = Steam::filterAccountBalances($range, $account, $this->convertToNative, $accountCurrency);
Log::debug('Three');
$previous = array_values($range)[0];
$accountCurrency ??= $this->defaultCurrency; // do this AFTER getting the balances.
while ($end >= $current) {
$theDate = $current->format('Y-m-d');
// each day contains multiple balances, and this may even be different over time.
$momentBalance = $range[$theDate] ?? $previous;
$return = $this->updateChartKeys($return, $momentBalance);
// process each balance thing.
foreach ($momentBalance as $key => $amount) {
$label = $current->isoFormat($format);
$return[$key]['entries'][$label] = $amount;
}
$current = app('navigation')->addPeriod($current, $step, 0);
// here too, to fix #8041, the data is corrected to the end of the period.
$current = app('navigation')->endOfX($current, $step, null);
$previous = $momentBalance;
}
// second loop (yes) to create nice array with info! Yay!
$chartData = [];
foreach ($return as $key => $info) {
if (3 === strlen($key)) {
// assume it's a currency:
$setCurrency = $this->currencyRepository->findByCode($key);
$info['currency_symbol'] = $setCurrency->symbol;
$info['currency_code'] = $setCurrency->code;
$info['label'] = sprintf('%s (%s)', $account->name, $setCurrency->symbol);
}
if ('balance' === $key) {
$info['currency_symbol'] = $accountCurrency->symbol;
$info['currency_code'] = $accountCurrency->code;
$info['label'] = sprintf('%s (%s)', $account->name, $accountCurrency->symbol);
}
if ('native_balance' === $key) {
$info['currency_symbol'] = $this->defaultCurrency->symbol;
$info['currency_code'] = $this->defaultCurrency->code;
$info['label'] = sprintf('%s (%s) (%s)', $account->name, (string)trans('firefly.sum'), $this->defaultCurrency->symbol);
}
$chartData[] = $info;
}
$data = $this->generator->multiSet($chartData);
$cache->store($data);
return response()->json($data);
var_dump($chartData);
exit;
$result = [
'label' => sprintf('%s (%s)', $account->name, $currency->symbol),
'currency_symbol' => $currency->symbol,
'currency_code' => $currency->code,
];
$entries = [];
$current = clone $start;
Log::debug(sprintf('$current date is %s', $current->format('Y-m-d')));
if ('1D' === $step) {
// per day the entire period, balance for every day.
$format = (string) trans('config.month_and_day_js', [], $locale);
$range = app('steam')->finalAccountBalanceInRange($account, $start, $end, $this->convertToNative);
$previous = array_values($range)[0];
while ($end >= $current) {
$theDate = $current->format('Y-m-d');
$balance = $range[$theDate]['balance'] ?? $previous;
$label = $current->isoFormat($format);
$entries[$label] = (float) $balance;
$previous = $balance;
$current->addDay();
}
}
if ('1W' === $step || '1M' === $step || '1Y' === $step) {
while ($end >= $current) {
Log::debug(sprintf('Current is: %s', $current->format('Y-m-d')));
$balance = Steam::finalAccountBalance($account, $current)[$currency->code] ?? '0';
$label = app('navigation')->periodShow($current, $step);
$entries[$label] = $balance;
$current = app('navigation')->addPeriod($current, $step, 0);
// here too, to fix #8041, the data is corrected to the end of the period.
$current = app('navigation')->endOfX($current, $step, null);
}
}
$result['entries'] = $entries;
return $result;
/** @var TransactionCurrency $currency */
foreach ($currencies as $currency) {
$chartData[] = $this->periodByCurrency($start, $end, $account, $currency);
}
$data = $this->generator->multiSet($chartData);
$data = $this->generator->multiSet($chartData);
$cache->store($data);
return response()->json($data);
@@ -446,7 +578,7 @@ 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')->finalAccountBalanceInRange($account, $start, $end);
$range = app('steam')->finalAccountBalanceInRange($account, $start, $end, $this->convertToNative);
$previous = array_values($range)[0];
while ($end >= $current) {
$theDate = $current->format('Y-m-d');
@@ -500,6 +632,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());
@@ -510,9 +643,10 @@ 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
@@ -520,33 +654,53 @@ class AccountController extends Controller
$endBalances = app('steam')->finalAccountsBalance($accounts, $end);
// 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));
// 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 $currencyCode => $endAmount) {
if (3 !== strlen($currencyCode)) {
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][$currencyCode] ?? '0');
$diff = bcsub((string) $endAmount, $startAmount);
$currencies[$currencyCode] ??= $this->currencyRepository->findByCode($currencyCode);
$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' => $currencies[$currencyCode]->id,
'currency_id' => $currencies[$searchCode]->id,
];
}
}
}
// recreate currencies, but on ID instead of code.
$newCurrencies = [];
foreach ($currencies as $currency) {
@@ -587,4 +741,15 @@ class AccountController extends Controller
return response()->json($data);
}
private function updateChartKeys(array $array, array $balances): array
{
foreach (array_keys($balances) as $key) {
$array[$key] ??= [
'key' => $key,
];
}
return $array;
}
}

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,6 +23,7 @@ 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;
@@ -52,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 = '/';
@@ -111,17 +114,19 @@ abstract class Controller extends BaseController
$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()) {
$this->defaultCurrency = app('amount')->getDefaultCurrency();
$language = Steam::getLanguage();
$locale = Steam::getLocale();
$darkMode = app('preferences')->get('darkMode', 'browser')->data;
$convertToNative =app('preferences')->get('convert_to_native', false)->data;
$this->convertToNative =app('preferences')->get('convert_to_native', false)->data;
$page = $this->getPageName();
$shownDemo = $this->hasSeenDemo();
View::share('language', $language);
View::share('locale', $locale);
View::share('convertToNative', $convertToNative);
View::share('convertToNative', $this->convertToNative);
View::share('shownDemo', $shownDemo);
View::share('current_route_name', $page);
View::share('original_route_name', Route::currentRouteName());

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

@@ -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

@@ -103,7 +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 = app('preferences')->get('convert_to_native', false)->data;
$convertToNative = $this->convertToNative;
if (is_array($fiscalYearStartStr)) {
$fiscalYearStartStr = '01-01';
}

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

@@ -431,11 +431,10 @@ class AccountRepository implements AccountRepositoryInterface
public function getUsedCurrencies(Account $account): Collection
{
$info = $account->transactions()->get(['transaction_currency_id', 'foreign_currency_id'])->toArray();
$info = $account->transactions()->distinct()->groupBy('transaction_currency_id')->get(['transaction_currency_id'])->toArray();
$currencyIds = [];
foreach ($info as $entry) {
$currencyIds[] = (int) $entry['transaction_currency_id'];
$currencyIds[] = (int) $entry['foreign_currency_id'];
}
$currencyIds = array_unique($currencyIds);

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

@@ -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

@@ -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]['balance'] ?? '0';
$key = $this->convertToNative ? 'native_balance' : 'balance';
return $array[$entryId][$key] ?? '0';
}
/**

View File

@@ -46,12 +46,13 @@ trait ChartGeneration
protected function accountBalanceChart(Collection $accounts, Carbon $start, Carbon $end): array // chart helper method.
{
// chart properties for cache:
$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);
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$cache->addProperty($convertToNative);
if ($cache->has()) {
// return $cache->get();
}
@@ -69,16 +70,10 @@ trait ChartGeneration
/** @var Account $account */
foreach ($accounts as $account) {
// TODO we can use getAccountCurrency instead.
$currency = $accountRepos->getAccountCurrency($account);
if (null === $currency) {
$currency = $default;
}
// if the user prefers the native currency, overrule the currency of the account.
if ($currency->id !== $default->id && $convertToNative) {
$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,
@@ -86,7 +81,7 @@ trait ChartGeneration
];
$currentStart = clone $start;
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end);
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
$previous = array_values($range)[0];
while ($currentStart <= $end) {
$format = $currentStart->format('Y-m-d');
@@ -94,7 +89,7 @@ trait ChartGeneration
$balance = $range[$format] ?? $previous;
$previous = $balance;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance['balance']; // TODO or native_balance
$currentSet['entries'][$label] = $balance[$field];
}
$chartData[] = $currentSet;
}

View File

@@ -62,16 +62,19 @@ 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;
}
public function finalAccountBalanceInRange(Account $account, Carbon $start, Carbon $end): array
public function finalAccountBalanceInRange(Account $account, Carbon $start, Carbon $end, bool $convertToNative): array
{
// expand period.
$start->subDay()->startOfDay();
$end->addDay()->endOfDay();
Log::debug(sprintf('finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
// set up cache
$cache = new CacheProperties();
@@ -87,14 +90,23 @@ class Steam
$formatted = $start->format('Y-m-d');
$startBalance = $this->finalAccountBalance($account, $start);
$defaultCurrency = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
$currency = $this->getAccountCurrency($account) ?? $defaultCurrency;
$accountCurrency = $this->getAccountCurrency($account);
$hasCurrency = null !== $accountCurrency;
$currency = $accountCurrency ?? $defaultCurrency;
Log::debug(sprintf('Currency is %s', $currency->code));
if (!$hasCurrency) {
Log::debug(sprintf('Also set start balance in %s', $defaultCurrency->code));
$startBalance[$defaultCurrency->code] ??= '0';
}
$currencies = [
$currency->id => $currency,
$defaultCurrency->id => $defaultCurrency,
];
$startBalance[$defaultCurrency->code] ??= '0';
$startBalance[$currency->code] ??= '0';
$startBalance[$currency->code] ??= '0';
$balances[$formatted] = $startBalance;
Log::debug('Final start balance: ', $startBalance);
// sums up the balance changes per day, for foreign, native and normal amounts.
@@ -123,31 +135,60 @@ class Steam
/** @var Transaction $entry */
foreach ($set as $entry) {
// 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);
// find currency of this entry.
$currencies[$entry->transaction_currency_id] ??= TransactionCurrency::find($entry->transaction_currency_id);
$entryCurrency = $currencies[$entry->transaction_currency_id];
Log::debug(sprintf('Processing transaction(s) on date %s', $carbon->format('Y-m-d H:i:s')));
// if convert to native, if NOT convert to native.
if ($convertToNative) {
Log::debug(sprintf('Amount is %s %s, foreign amount is %s, native amount is %s', $entryCurrency->code, $modified, $foreignModified, $nativeModified));
// add to native balance.
$currentBalance['native_balance'] = bcadd($currentBalance['native_balance'], $nativeModified);
if ($entry->foreign_currency_id === $defaultCurrency->id) {
$currentBalance['native_balance'] = bcadd($currentBalance['native_balance'], $foreignModified);
}
// add to balance if is the same.
if ($entry->transaction_currency_id === $accountCurrency?->id) {
$currentBalance['balance'] = bcadd($currentBalance['balance'], $modified);
}
// add currency balance
$currentBalance[$entryCurrency->code] = bcadd($currentBalance[$entryCurrency->code], $modified);
}
if (!$convertToNative) {
Log::debug(sprintf('Amount is %s %s, foreign amount is %s, native amount is %s', $entryCurrency->code, $modified, $foreignModified, $nativeModified));
// add to balance, as expected.
$currentBalance['balance'] = bcadd($currentBalance['balance'] ?? '0', $modified);
// add to GBP, as expected.
$currentBalance[$entryCurrency->code] = bcadd($currentBalance[$entryCurrency->code], $modified);
}
// always add the native balance, even if it ends up at zero.
$currentBalance['native_balance'] = bcadd($currentBalance['native_balance'], $nativeModified);
// // 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);
}
// DO NOT 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;
Log::debug('Updated entry', $currentBalance);
}
$cache->store($balances);
@@ -258,52 +299,164 @@ class Steam
* 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
{
$native = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
$currency = $this->getAccountCurrency($account) ?? $native;
$return = [
'native_balance' => '0',
];
Log::debug(sprintf('Now in finalAccountBalance("%s", "%s")', $account->name, $date->format('Y-m-d H:i:s')));
// first, the "balance", as described earlier.
$array = $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', $currency->id)
->get(['transactions.amount'])->toArray()
;
$return['balance'] = $this->sumTransactions($array, 'amount');
// Log::debug(sprintf('balance is %s', $return['balance']));
// add virtual balance:
$return['balance'] = bcadd('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance, $return['balance']);
// Log::debug(sprintf('balance is %s (with virtual balance)', $return['balance']));
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 = [];
// then, native balance (if necessary(
if ($native->id !== $currency->id) {
$array = $account->transactions()
// 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'))
->get(['transactions.native_amount'])->toArray()
->where('transactions.transaction_currency_id', $native->id)
->sum('transactions.amount')
;
$return['native_balance'] = $this->sumTransactions($array, 'native_amount');
// Log::debug(sprintf('native_balance is %s', $return['native_balance']));
$return['native_balance'] = bcadd('' === (string) $account->native_virtual_balance ? '0' : $account->native_virtual_balance, $return['balance']);
// Log::debug(sprintf('native_balance is %s (with virtual balance)', $return['native_balance']));
// 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 currencies.
$array = $account->transactions()
// 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 others are (joined)', $others);
$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']));
}
return array_merge($return, $others);
// 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']);
}
$final = array_merge($return, $others);
Log::debug('Return is', $final);
return $final;
}
public function filterAccountBalances(array $total, Account $account, bool $convertToNative, ?TransactionCurrency $currency = null): array
{
Log::debug(sprintf('filterAccountBalances(#%d)', $account->id));
$return = [];
foreach ($total as $key => $value) {
$return[$key] = $this->filterAccountBalance($value, $account, $convertToNative, $currency);
}
Log::debug(sprintf('end of filterAccountBalances(#%d)', $account->id));
return $return;
}
public function filterAccountBalance(array $set, Account $account, bool $convertToNative, ?TransactionCurrency $currency = null): array
{
Log::debug(sprintf('filterAccountBalance(#%d)', $account->id), $set);
if (0 === count($set)) {
Log::debug(sprintf('Return empty array for account #%d', $account->id));
return [];
}
$defaultCurrency = app('amount')->getDefaultCurrency();
if ($convertToNative) {
if ($defaultCurrency->id === $currency?->id) {
Log::debug(sprintf('Unset "native_balance" and "%s" for account #%d', $defaultCurrency->code, $account->id));
unset($set['native_balance'], $set[$defaultCurrency->code]);
}
if (null !== $currency && $defaultCurrency->id !== $currency->id) {
Log::debug(sprintf('Unset balance for account #%d', $account->id));
unset($set['balance']);
}
if (null === $currency) {
Log::debug(sprintf('TEMP DO NOT Drop defaultCurrency balance for account #%d', $account->id));
// unset($set[$this->defaultCurrency->code]);
}
}
if (!$convertToNative) {
if (null === $currency) {
Log::debug(sprintf('Unset native_balance and make defaultCurrency balance the balance for account #%d', $account->id));
$set['balance'] = $set[$defaultCurrency->code] ?? '0';
unset($set['native_balance'], $set[$defaultCurrency->code]);
}
if (null !== $currency) {
Log::debug(sprintf('Unset native_balance + defaultCurrency + currencyCode balance for account #%d', $account->id));
unset($set['native_balance'], $set[$defaultCurrency->code], $set[$currency->code]);
}
}
// put specific value first in array.
if (array_key_exists('native_balance', $set)) {
$set = ['native_balance' => $set['native_balance']] + $set;
}
if (array_key_exists('balance', $set)) {
$set = ['balance' => $set['balance']] + $set;
}
Log::debug(sprintf('Return #%d', $account->id), $set);
return $set;
}
private function groupAndSumTransactions(array $array, string $group, string $field): array
@@ -351,9 +504,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[(int)$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

@@ -69,13 +69,14 @@ class General extends AbstractExtension
$date = session('end', today(config('app.timezone'))->endOfMonth());
$info = Steam::finalAccountBalance($account, $date);
$currency = Steam::getAccountCurrency($account);
$native = Amount::getDefaultCurrency();
$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 (!$convertToNative || $currency->code === $native->code) {
if (!$useNative) {
$strings[] = app('amount')->formatAnything($currency, $balance, false);
}
@@ -83,13 +84,14 @@ class General extends AbstractExtension
}
if ('native_balance' === $key) {
// balance in native currency.
if ($convertToNative) {
$strings[] = app('amount')->formatAnything($native, $balance, false);
if ($useNative) {
$strings[] = app('amount')->formatAnything($default, $balance, false);
}
continue;
}
if ($key !== $currency->code) {
// for multi currency accounts.
if ($useNative && $key !== $default->code) {
$strings[] = app('amount')->formatAnything(TransactionCurrency::where('code', $key)->first(), $balance, false);
}
}

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",

248
package-lock.json generated
View File

@@ -1709,9 +1709,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz",
"integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
"integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
"cpu": [
"ppc64"
],
@@ -1726,9 +1726,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz",
"integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
"integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
"cpu": [
"arm"
],
@@ -1743,9 +1743,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz",
"integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
"integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
"cpu": [
"arm64"
],
@@ -1760,9 +1760,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz",
"integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
"integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
"cpu": [
"x64"
],
@@ -1777,9 +1777,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
"integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
"integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
"cpu": [
"arm64"
],
@@ -1794,9 +1794,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz",
"integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
"integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
"cpu": [
"x64"
],
@@ -1811,9 +1811,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz",
"integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
"integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
"cpu": [
"arm64"
],
@@ -1828,9 +1828,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz",
"integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
"integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
"cpu": [
"x64"
],
@@ -1845,9 +1845,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz",
"integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
"integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
"cpu": [
"arm"
],
@@ -1862,9 +1862,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz",
"integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
"integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
"cpu": [
"arm64"
],
@@ -1879,9 +1879,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz",
"integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
"integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
"cpu": [
"ia32"
],
@@ -1896,9 +1896,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz",
"integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
"integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
"cpu": [
"loong64"
],
@@ -1913,9 +1913,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz",
"integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
"integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
"cpu": [
"mips64el"
],
@@ -1930,9 +1930,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz",
"integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
"integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
"cpu": [
"ppc64"
],
@@ -1947,9 +1947,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz",
"integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
"integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
"cpu": [
"riscv64"
],
@@ -1964,9 +1964,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz",
"integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
"integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
"cpu": [
"s390x"
],
@@ -1981,9 +1981,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz",
"integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
"integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
"cpu": [
"x64"
],
@@ -1997,10 +1997,27 @@
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
"integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz",
"integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
"integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
"cpu": [
"x64"
],
@@ -2015,9 +2032,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz",
"integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
"integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
"cpu": [
"arm64"
],
@@ -2032,9 +2049,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz",
"integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
"integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
"cpu": [
"x64"
],
@@ -2049,9 +2066,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz",
"integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
"integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
"cpu": [
"x64"
],
@@ -2066,9 +2083,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz",
"integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
"integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
"cpu": [
"arm64"
],
@@ -2083,9 +2100,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz",
"integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
"integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
"cpu": [
"ia32"
],
@@ -2100,9 +2117,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz",
"integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
"integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
"cpu": [
"x64"
],
@@ -3621,9 +3638,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 +3719,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 +5663,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"
},
@@ -5793,9 +5810,9 @@
}
},
"node_modules/esbuild": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz",
"integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==",
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -5806,30 +5823,31 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.24.0",
"@esbuild/android-arm": "0.24.0",
"@esbuild/android-arm64": "0.24.0",
"@esbuild/android-x64": "0.24.0",
"@esbuild/darwin-arm64": "0.24.0",
"@esbuild/darwin-x64": "0.24.0",
"@esbuild/freebsd-arm64": "0.24.0",
"@esbuild/freebsd-x64": "0.24.0",
"@esbuild/linux-arm": "0.24.0",
"@esbuild/linux-arm64": "0.24.0",
"@esbuild/linux-ia32": "0.24.0",
"@esbuild/linux-loong64": "0.24.0",
"@esbuild/linux-mips64el": "0.24.0",
"@esbuild/linux-ppc64": "0.24.0",
"@esbuild/linux-riscv64": "0.24.0",
"@esbuild/linux-s390x": "0.24.0",
"@esbuild/linux-x64": "0.24.0",
"@esbuild/netbsd-x64": "0.24.0",
"@esbuild/openbsd-arm64": "0.24.0",
"@esbuild/openbsd-x64": "0.24.0",
"@esbuild/sunos-x64": "0.24.0",
"@esbuild/win32-arm64": "0.24.0",
"@esbuild/win32-ia32": "0.24.0",
"@esbuild/win32-x64": "0.24.0"
"@esbuild/aix-ppc64": "0.24.2",
"@esbuild/android-arm": "0.24.2",
"@esbuild/android-arm64": "0.24.2",
"@esbuild/android-x64": "0.24.2",
"@esbuild/darwin-arm64": "0.24.2",
"@esbuild/darwin-x64": "0.24.2",
"@esbuild/freebsd-arm64": "0.24.2",
"@esbuild/freebsd-x64": "0.24.2",
"@esbuild/linux-arm": "0.24.2",
"@esbuild/linux-arm64": "0.24.2",
"@esbuild/linux-ia32": "0.24.2",
"@esbuild/linux-loong64": "0.24.2",
"@esbuild/linux-mips64el": "0.24.2",
"@esbuild/linux-ppc64": "0.24.2",
"@esbuild/linux-riscv64": "0.24.2",
"@esbuild/linux-s390x": "0.24.2",
"@esbuild/linux-x64": "0.24.2",
"@esbuild/netbsd-arm64": "0.24.2",
"@esbuild/netbsd-x64": "0.24.2",
"@esbuild/openbsd-arm64": "0.24.2",
"@esbuild/openbsd-x64": "0.24.2",
"@esbuild/sunos-x64": "0.24.2",
"@esbuild/win32-arm64": "0.24.2",
"@esbuild/win32-ia32": "0.24.2",
"@esbuild/win32-x64": "0.24.2"
}
},
"node_modules/escalade": {
@@ -6104,9 +6122,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": {
@@ -11255,13 +11273,13 @@
}
},
"node_modules/vite": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.5.tgz",
"integrity": "sha512-akD5IAH/ID5imgue2DYhzsEwCi0/4VKY31uhMLEYJwPP4TiUp8pL5PIK+Wo7H8qT8JY9i+pVfPydcFPYD1EL7g==",
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.6.tgz",
"integrity": "sha512-NSjmUuckPmDU18bHz7QZ+bTYhRR0iA72cs2QAxCqDpafJ0S6qetco0LB3WW2OxlMHS0JmAv+yZ/R3uPmMyGTjQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "0.24.0",
"esbuild": "^0.24.2",
"postcss": "^8.4.49",
"rollup": "^4.23.0"
},

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

@@ -130,15 +130,15 @@
"response": "Antwort",
"visit_webhook_url": "Webhook-URL besuchen",
"reset_webhook_secret": "Webhook Secret zur\u00fccksetzen",
"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)",
"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)",
"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": "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"
"header_exchange_rates_rates": "Wechselkurse",
"header_exchange_rates_table": "Tabelle mit Wechselkursen",
"help_rate_form": "An diesem Tag, wie viele {to} werden Sie f\u00fcr {from} bekommen?",
"add_new_rate": "Neuen Wechselkurs hinzuf\u00fcgen",
"save_new_rate": "Neuen Kurs speichern"
},
"form": {
"url": "URL",
@@ -158,7 +158,7 @@
"webhook_delivery": "Zustellung",
"from_currency_to_currency": "{from} &rarr; {to}",
"to_currency_from_currency": "{to} &rarr; {from}",
"rate": "Rate"
"rate": "Kurs"
},
"list": {
"active": "Aktiv?",

View File

@@ -11,9 +11,15 @@
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">
{% if balances.balance %}
{{ trans('firefly.chart_account_in_period', {
balance: formatAmountBySymbol(balance, currency.symbol, currency.decimal_places, true),
balance: formatAmountBySymbol(balances.balance, currency.symbol, currency.decimal_places, true),
name: account.name|escape, start: start.isoFormat(monthAndDayFormat), end: end.isoFormat(monthAndDayFormat) })|raw }}
{% elseif balances.native_balance %}
{{ trans('firefly.chart_account_in_period', {
balance: formatAmountBySymbol(balances.native_balance, defaultCurrency.symbol, defaultCurrency.decimal_places, true),
name: account.name|escape, start: start.isoFormat(monthAndDayFormat), end: end.isoFormat(monthAndDayFormat) })|raw }}
{% endif %}
</h3>
<div class="box-tools pull-right">
<div class="btn-group">
@@ -140,7 +146,12 @@
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transactions'|_ }}
({{ formatAmountBySymbol(balance, currency.symbol, currency.decimal_places, true)|raw }})</h3>
{% if balances.balance %}
({{ formatAmountBySymbol(balances.balance, currency.symbol, currency.decimal_places, true)|raw }})
{% elseif balances.native_balance %}
({{ formatAmountBySymbol(balances.native_balance, defaultCurrency.symbol, defaultCurrency.decimal_places, true)|raw }})
{% endif %}
</h3>
</div>
<div class="box-body">
{% if account.accountType.type == 'Asset account' %}

View File

@@ -66,14 +66,26 @@
{% 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 %}
{{ formatAmountBySymbol(balance, account.currency.symbol, account.currency.decimal_places) }}
{% elseif 'native_balance' == key %}
{{ formatAmountBySymbol(balance, defaultCurrency.symbol, defaultCurrency.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 +111,16 @@
{% 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 %}
<span title="{{ key }}">
{% if 'balance' == key or 'native_balance' == key %}
{{ formatAmountBySymbol(balance, account.currency.symbol, account.currency.currency.decimal_places) }}
{% else %}
({{ formatAmountByCode(balance, key) }})
{% endif %}
</span>
{% endfor %}
</span>
</td>
<td class="hidden-sm hidden-xs">

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 NATIVE group 1){% 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 group 2 ){% 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 NATIVE group 3){% if loop.index != group.sums|length %},{% endif %}
{% endif %}
{% endfor %}
</td>
@@ -153,6 +154,9 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and 0 != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount*-1, defaultCurrency.symbol, defaultCurrency.decimal_places) }})
{% endif %}
{# transfer #}
{% elseif transaction.transaction_type_type == 'Transfer' %}
<span class="text-info money-transfer">
@@ -160,6 +164,9 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places, false) }})
{% endif %}
{% if convertToNative and 0 != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount*-1, defaultCurrency.symbol, defaultCurrency.decimal_places) }})
{% endif %}
</span>
{# opening balance #}
{% elseif transaction.transaction_type_type == 'Opening balance' %}
@@ -168,11 +175,17 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and 0 != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount*-1, defaultCurrency.symbol, defaultCurrency.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 0 != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount, defaultCurrency.symbol, defaultCurrency.decimal_places) }})
{% endif %}
{% endif %}
{# reconciliation #}
{% elseif transaction.transaction_type_type == 'Reconciliation' %}
@@ -181,11 +194,17 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and 0 != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount*-1, defaultCurrency.symbol, defaultCurrency.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 0 != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount, defaultCurrency.symbol, defaultCurrency.decimal_places) }})
{% endif %}
{% endif %}
{# liability credit #}
{% elseif transaction.transaction_type_type == 'Liability credit' %}
@@ -194,11 +213,17 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and 0 != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount, defaultCurrency.symbol, defaultCurrency.decimal_places) }})
{% endif %}
{% 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 %}
{% if convertToNative and 0 != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount*-1, defaultCurrency.symbol, defaultCurrency.decimal_places) }})
{% endif %}
{% endif %}
@@ -208,6 +233,9 @@
{% if null != transaction.foreign_amount %}
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToNative and 0 != transaction.native_amount %}
({{ formatAmountBySymbol(transaction.native_amount, defaultCurrency.symbol, defaultCurrency.decimal_places) }})
{% endif %}
{% endif %}
</td>
<td style=" {{ style|raw }}">

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');
}
);