mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 02:36:28 +00:00 
			
		
		
		
	Auto commit for release 'branch-v6.2' on 2024-12-25
This commit is contained in:
		| @@ -32,7 +32,6 @@ 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; | ||||
| @@ -94,24 +93,24 @@ class BasicController extends Controller | ||||
|     public function basic(DateRequest $request): JsonResponse | ||||
|     { | ||||
|         // parameters for boxes:
 | ||||
|         $dates = $request->getAll(); | ||||
|         $start = $dates['start']; | ||||
|         $end   = $dates['end']; | ||||
|         $code  = $request->get('currency_code'); | ||||
|         $dates        = $request->getAll(); | ||||
|         $start        = $dates['start']; | ||||
|         $end          = $dates['end']; | ||||
|         $code         = $request->get('currency_code'); | ||||
| 
 | ||||
|         // balance information:
 | ||||
|         $balanceData  = $this->getBalanceInformation($start, $end); | ||||
|         $billData     = $this->getBillInformation($start, $end); | ||||
|         $spentData    = $this->getLeftToSpendInfo($start, $end); | ||||
|         $netWorthData = $this->getNetWorthInfo($start, $end); | ||||
| //        $balanceData  = [];
 | ||||
| //        $billData     = [];
 | ||||
| //        $spentData    = [];
 | ||||
| //        $netWorthData = [];
 | ||||
|         //        $balanceData  = [];
 | ||||
|         //        $billData     = [];
 | ||||
|         //        $spentData    = [];
 | ||||
|         //        $netWorthData = [];
 | ||||
|         $total        = array_merge($balanceData, $billData, $spentData, $netWorthData); | ||||
| 
 | ||||
|         // give new keys
 | ||||
|         $return = []; | ||||
|         $return       = []; | ||||
|         foreach ($total as $entry) { | ||||
|             if (null === $code || ($code === $entry['currency_code'])) { | ||||
|                 $return[$entry['key']] = $entry; | ||||
| @@ -125,19 +124,19 @@ class BasicController extends Controller | ||||
|     { | ||||
|         // some config settings
 | ||||
|         $convertToNative = app('preferences')->get('convert_to_native', false)->data; | ||||
|         $default = app('amount')->getDefaultCurrency(); | ||||
|         $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 $journal */ | ||||
|         foreach ($set as $journal) { | ||||
| @@ -154,14 +153,14 @@ class BasicController extends Controller | ||||
| 
 | ||||
|         // 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 $journal */ | ||||
|         foreach ($set as $journal) { | ||||
|             $currencyId           = $convertToNative ? $default->id : (int) $journal['currency_id']; | ||||
|             $amount               = Amount::getAmountFromJournal($journal); | ||||
|             $currencyId            = $convertToNative ? $default->id : (int) $journal['currency_id']; | ||||
|             $amount                = Amount::getAmountFromJournal($journal); | ||||
|             $expenses[$currencyId] ??= '0'; | ||||
|             $expenses[$currencyId] = bcadd($expenses[$currencyId], $amount); | ||||
|             $sums[$currencyId]     ??= '0'; | ||||
| @@ -169,7 +168,7 @@ class BasicController extends Controller | ||||
|         } | ||||
| 
 | ||||
|         // format amounts:
 | ||||
|         $keys = array_keys($sums); | ||||
|         $keys            = array_keys($sums); | ||||
|         foreach ($keys as $currencyId) { | ||||
|             $currency = $this->currencyRepos->find($currencyId); | ||||
|             if (null === $currency) { | ||||
| @@ -186,8 +185,8 @@ class BasicController extends Controller | ||||
|                 'currency_decimal_places' => $currency->decimal_places, | ||||
|                 'value_parsed'            => app('amount')->formatAnything($currency, $sums[$currencyId] ?? '0', false), | ||||
|                 'local_icon'              => 'balance-scale', | ||||
|                 'sub_title'               => app('amount')->formatAnything($currency, $expenses[$currencyId] ?? '0', false) . | ||||
|                                              ' + ' . app('amount')->formatAnything($currency, $incomes[$currencyId] ?? '0', false), | ||||
|                 'sub_title'               => app('amount')->formatAnything($currency, $expenses[$currencyId] ?? '0', false). | ||||
|                                              ' + '.app('amount')->formatAnything($currency, $incomes[$currencyId] ?? '0', false), | ||||
|             ]; | ||||
|             $return[] = [ | ||||
|                 'key'                     => sprintf('spent-in-%s', $currency->code), | ||||
| @@ -228,7 +227,7 @@ class BasicController extends Controller | ||||
|         $paidAmount   = $this->billRepository->sumPaidInRange($start, $end); | ||||
|         $unpaidAmount = $this->billRepository->sumUnpaidInRange($start, $end); | ||||
| 
 | ||||
|         $return = []; | ||||
|         $return       = []; | ||||
| 
 | ||||
|         /** | ||||
|          * @var array $info | ||||
| @@ -298,7 +297,7 @@ class BasicController extends Controller | ||||
| 
 | ||||
|             Log::debug(sprintf('Spent %s %s', $row['currency_code'], $row['sum'])); | ||||
| 
 | ||||
|             $return[] = [ | ||||
|             $return[]        = [ | ||||
|                 'key'                     => sprintf('left-to-spend-in-%s', $row['currency_code']), | ||||
|                 'title'                   => trans('firefly.box_left_to_spend_in_currency', ['currency' => $row['currency_symbol']]), | ||||
|                 'monetary_value'          => $leftToSpend, | ||||
| @@ -323,9 +322,10 @@ class BasicController extends Controller | ||||
|     private function getNetWorthInfo(Carbon $start, Carbon $end): array | ||||
|     { | ||||
|         Log::debug('getNetWorthInfo'); | ||||
| 
 | ||||
|         /** @var User $user */ | ||||
|         $user = auth()->user(); | ||||
|         $date = now(config('app.timezone')); | ||||
|         $user           = auth()->user(); | ||||
|         $date           = now(config('app.timezone')); | ||||
|         // start and end in the future? use $end
 | ||||
|         if ($this->notInDateRange($date, $start, $end)) { | ||||
|             /** @var Carbon $date */ | ||||
| @@ -335,10 +335,10 @@ class BasicController extends Controller | ||||
|         /** @var NetWorthInterface $netWorthHelper */ | ||||
|         $netWorthHelper = app(NetWorthInterface::class); | ||||
|         $netWorthHelper->setUser($user); | ||||
|         $allAccounts = $this->accountRepository->getActiveAccountsByType([AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value]); | ||||
|         $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( | ||||
|         $filtered       = $allAccounts->filter( | ||||
|             function (Account $account) { | ||||
|                 $includeNetWorth = $this->accountRepository->getMetaValue($account, 'include_net_worth'); | ||||
| 
 | ||||
| @@ -346,13 +346,13 @@ class BasicController extends Controller | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         $netWorthSet = $netWorthHelper->byAccounts($filtered, $date); | ||||
|         $return      = []; | ||||
|         $netWorthSet    = $netWorthHelper->byAccounts($filtered, $date); | ||||
|         $return         = []; | ||||
|         foreach ($netWorthSet as $key => $data) { | ||||
|             if ('native' === $key) { | ||||
|                 continue; | ||||
|             } | ||||
|             $amount = $data['balance']; | ||||
|             $amount   = $data['balance']; | ||||
|             if (0 === bccomp($amount, '0')) { | ||||
|                 continue; | ||||
|             } | ||||
|   | ||||
| @@ -212,10 +212,10 @@ class RecalculateNativeAmounts extends Command | ||||
|             ->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|             ->where('transaction_journals.user_group_id', $userGroup->id) | ||||
| 
 | ||||
|             ->where(function(DatabaseBuilder $q1) use ($currency) { | ||||
|                 $q1->where(function(DatabaseBuilder $q2) use ($currency) { | ||||
|             ->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) { | ||||
|                 })->orWhere(function (DatabaseBuilder $q3) use ($currency): void { | ||||
|                     $q3->whereNot('transactions.transaction_currency_id', $currency->id)->whereNot('transactions.foreign_currency_id', $currency->id); | ||||
|                 }); | ||||
|             }) | ||||
|   | ||||
| @@ -77,28 +77,28 @@ class NetWorth implements NetWorthInterface | ||||
|             // return $cache->get();
 | ||||
|         } | ||||
|         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); | ||||
|         $default         = Amount::getDefaultCurrency(); | ||||
|         $netWorth        = []; | ||||
|         $balances        = Steam::finalAccountsBalance($accounts, $date); | ||||
| 
 | ||||
|         /** @var Account $account */ | ||||
|         foreach ($accounts as $account) { | ||||
|             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'; | ||||
|             $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'; | ||||
|             } | ||||
|             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; | ||||
|             $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] ??= [ | ||||
| @@ -117,7 +117,7 @@ class NetWorth implements NetWorthInterface | ||||
|         return $netWorth; | ||||
|     } | ||||
| 
 | ||||
|     private function getRepository(): AccountRepositoryInterface | AdminAccountRepositoryInterface | ||||
|     private function getRepository(): AccountRepositoryInterface|AdminAccountRepositoryInterface | ||||
|     { | ||||
|         if (null === $this->userGroup) { | ||||
|             return $this->accountRepository; | ||||
| @@ -126,19 +126,19 @@ class NetWorth implements NetWorthInterface | ||||
|         return $this->adminAccountRepository; | ||||
|     } | ||||
| 
 | ||||
|     public function setUser(null | Authenticatable | User $user): void | ||||
|     public function setUser(null|Authenticatable|User $user): void | ||||
|     { | ||||
|         if (!$user instanceof User) { | ||||
|             return; | ||||
|         } | ||||
|         $this->user      = $user; | ||||
|         $this->userGroup = null; | ||||
|         $this->user              = $user; | ||||
|         $this->userGroup         = null; | ||||
| 
 | ||||
|         // make repository:
 | ||||
|         $this->accountRepository = app(AccountRepositoryInterface::class); | ||||
|         $this->accountRepository->setUser($this->user); | ||||
| 
 | ||||
|         $this->currencyRepos = app(CurrencyRepositoryInterface::class); | ||||
|         $this->currencyRepos     = app(CurrencyRepositoryInterface::class); | ||||
|         $this->currencyRepos->setUser($this->user); | ||||
|     } | ||||
| 
 | ||||
| @@ -161,16 +161,16 @@ class NetWorth implements NetWorthInterface | ||||
|         $return   = []; | ||||
|         $balances = Steam::finalAccountsBalance($accounts, $date); | ||||
|         foreach ($accounts as $account) { | ||||
|             $currency = $this->getRepository()->getAccountCurrency($account); | ||||
|             $balance  = $balances[$account->id]['balance'] ?? '0'; | ||||
|             $currency                     = $this->getRepository()->getAccountCurrency($account); | ||||
|             $balance                      = $balances[$account->id]['balance'] ?? '0'; | ||||
| 
 | ||||
|             // always subtract virtual balance.
 | ||||
|             $virtualBalance = $account->virtual_balance; | ||||
|             $virtualBalance               = $account->virtual_balance; | ||||
|             if ('' !== $virtualBalance) { | ||||
|                 $balance = bcsub($balance, $virtualBalance); | ||||
|             } | ||||
| 
 | ||||
|             $return[$currency->id]        ??= [ | ||||
|             $return[$currency->id] ??= [ | ||||
|                 'id'             => (string) $currency->id, | ||||
|                 'name'           => $currency->name, | ||||
|                 'symbol'         => $currency->symbol, | ||||
|   | ||||
| @@ -71,22 +71,22 @@ class IndexController extends Controller | ||||
|      *                                              */ | ||||
|     public function inactive(Request $request, string $objectType) | ||||
|     { | ||||
|         $inactivePage = true; | ||||
|         $subTitle     = (string) trans(sprintf('firefly.%s_accounts_inactive', $objectType)); | ||||
|         $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); | ||||
|         $types        = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); | ||||
|         $collection   = $this->repository->getInactiveAccountsByType($types); | ||||
|         $total        = $collection->count(); | ||||
|         $page         = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page'); | ||||
|         $pageSize     = (int) app('preferences')->get('listPageSize', 50)->data; | ||||
|         $accounts     = $collection->slice(($page - 1) * $pageSize, $pageSize); | ||||
|         $inactivePage  = true; | ||||
|         $subTitle      = (string) trans(sprintf('firefly.%s_accounts_inactive', $objectType)); | ||||
|         $subTitleIcon  = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); | ||||
|         $types         = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); | ||||
|         $collection    = $this->repository->getInactiveAccountsByType($types); | ||||
|         $total         = $collection->count(); | ||||
|         $page          = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page'); | ||||
|         $pageSize      = (int) app('preferences')->get('listPageSize', 50)->data; | ||||
|         $accounts      = $collection->slice(($page - 1) * $pageSize, $pageSize); | ||||
|         unset($collection); | ||||
| 
 | ||||
|         /** @var Carbon $start */ | ||||
|         $start = clone session('start', today(config('app.timezone'))->startOfMonth()); | ||||
|         $start         = clone session('start', today(config('app.timezone'))->startOfMonth()); | ||||
| 
 | ||||
|         /** @var Carbon $end */ | ||||
|         $end = clone session('end', today(config('app.timezone'))->endOfMonth()); | ||||
|         $end           = clone session('end', today(config('app.timezone'))->endOfMonth()); | ||||
|         $start->subDay(); | ||||
| 
 | ||||
|         $ids           = $accounts->pluck('id')->toArray(); | ||||
| @@ -111,7 +111,7 @@ class IndexController extends Controller | ||||
|         ); | ||||
| 
 | ||||
|         // make paginator:
 | ||||
|         $accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page); | ||||
|         $accounts      = new LengthAwarePaginator($accounts, $total, $pageSize, $page); | ||||
|         $accounts->setPath(route('accounts.inactive.index', [$objectType])); | ||||
| 
 | ||||
|         return view('accounts.index', compact('objectType', 'inactivePage', 'subTitleIcon', 'subTitle', 'page', 'accounts')); | ||||
| @@ -127,9 +127,9 @@ class IndexController extends Controller | ||||
|     public function index(Request $request, string $objectType) | ||||
|     { | ||||
|         app('log')->debug(sprintf('Now at %s', __METHOD__)); | ||||
|         $subTitle     = (string) trans(sprintf('firefly.%s_accounts', $objectType)); | ||||
|         $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); | ||||
|         $types        = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); | ||||
|         $subTitle      = (string) trans(sprintf('firefly.%s_accounts', $objectType)); | ||||
|         $subTitleIcon  = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); | ||||
|         $types         = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); | ||||
| 
 | ||||
|         $this->repository->resetAccountOrder(); | ||||
| 
 | ||||
| @@ -145,10 +145,10 @@ class IndexController extends Controller | ||||
|         unset($collection); | ||||
| 
 | ||||
|         /** @var Carbon $start */ | ||||
|         $start = clone session('start', today(config('app.timezone'))->startOfMonth()); | ||||
|         $start         = clone session('start', today(config('app.timezone'))->startOfMonth()); | ||||
| 
 | ||||
|         /** @var Carbon $end */ | ||||
|         $end = clone session('end', today(config('app.timezone'))->endOfMonth()); | ||||
|         $end           = clone session('end', today(config('app.timezone'))->endOfMonth()); | ||||
|         $start->subDay(); | ||||
| 
 | ||||
|         $ids           = $accounts->pluck('id')->toArray(); | ||||
| @@ -159,9 +159,9 @@ class IndexController extends Controller | ||||
| 
 | ||||
|         $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); | ||||
|                 $interest                     = (string) $this->repository->getMetaValue($account, 'interest'); | ||||
|                 $interest                     = '' === $interest ? '0' : $interest; | ||||
|                 $currency                     = $this->repository->getAccountCurrency($account); | ||||
| 
 | ||||
|                 $account->startBalances       = $this->getBalance($account, $currency, $startBalances); | ||||
|                 $account->endBalances         = $this->getBalance($account, $currency, $endBalances); | ||||
| @@ -175,7 +175,7 @@ class IndexController extends Controller | ||||
|                 $account->location            = $this->repository->getLocation($account); | ||||
|                 $account->liability_direction = $this->repository->getMetaValue($account, 'liability_direction'); | ||||
|                 $account->current_debt        = $this->repository->getMetaValue($account, 'current_debt') ?? '-'; | ||||
|                 $account->currency = $currency; | ||||
|                 $account->currency            = $currency; | ||||
|                 $account->iban                = implode(' ', str_split((string) $account->iban, 4)); | ||||
| 
 | ||||
| 
 | ||||
| @@ -185,7 +185,7 @@ class IndexController extends Controller | ||||
|         app('log')->debug(sprintf('Count of accounts before LAP: %d', $accounts->count())); | ||||
| 
 | ||||
|         /** @var LengthAwarePaginator $accounts */ | ||||
|         $accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page); | ||||
|         $accounts      = new LengthAwarePaginator($accounts, $total, $pageSize, $page); | ||||
|         $accounts->setPath(route('accounts.index', [$objectType])); | ||||
| 
 | ||||
|         app('log')->debug(sprintf('Count of accounts after LAP (1): %d', $accounts->count())); | ||||
| @@ -194,7 +194,7 @@ class IndexController extends Controller | ||||
|         return view('accounts.index', compact('objectType', 'inactiveCount', 'subTitleIcon', 'subTitle', 'page', 'accounts')); | ||||
|     } | ||||
| 
 | ||||
|     private function getBalance(Account $account, ?TransactionCurrency $currency = null, array $balances): array | ||||
|     private function getBalance(Account $account, ?TransactionCurrency $currency, array $balances): array | ||||
|     { | ||||
|         if (!array_key_exists($account->id, $balances)) { | ||||
|             return []; | ||||
| @@ -217,6 +217,7 @@ class IndexController extends Controller | ||||
|         foreach ($endBalances as $key => $value) { | ||||
|             $result[$key] = bcsub($value, $startBalances[$key] ?? '0'); | ||||
|         } | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -84,6 +84,7 @@ class AccountController extends Controller | ||||
|     public function expenseAccounts(): JsonResponse | ||||
|     { | ||||
|         Log::debug('RevenueAccounts'); | ||||
| 
 | ||||
|         /** @var Carbon $start */ | ||||
|         $start         = clone session('start', today(config('app.timezone'))->startOfMonth()); | ||||
| 
 | ||||
| @@ -95,7 +96,7 @@ class AccountController extends Controller | ||||
|         $cache->addProperty($this->convertToNative); | ||||
|         $cache->addProperty('chart.account.expense-accounts'); | ||||
|         if ($cache->has()) { | ||||
|               return response()->json($cache->get()); | ||||
|             return response()->json($cache->get()); | ||||
|         } | ||||
|         $start->subDay(); | ||||
| 
 | ||||
| @@ -114,34 +115,38 @@ class AccountController extends Controller | ||||
|         $endBalances   = app('steam')->finalAccountsBalance($accounts, $end); | ||||
| 
 | ||||
|         // loop the accounts, then check for balance and currency info.
 | ||||
|         foreach($accounts as $account) { | ||||
|         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)); | ||||
|             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) { | ||||
|                 if (!$this->convertToNative && 'native_balance' === $key) { | ||||
|                     Log::debug(sprintf('[a] Will skip expense array "%s"', $key)); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if($this->convertToNative && 'native_balance' !== $key) { | ||||
|                 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; | ||||
|                 $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.
 | ||||
|                 $startBalance = ($startBalances[$account->id][$key] ?? '0'); | ||||
|                 Log::debug(sprintf('Start balance is %s', $startBalance)); | ||||
|                 $diff        = bcsub($endBalance, $startBalance); | ||||
|                 $diff         = bcsub($endBalance, $startBalance); | ||||
|                 $currencies[$searchCode] ??= $this->currencyRepository->findByCode($searchCode); | ||||
|                 if (0 !== bccomp($diff, '0')) { | ||||
|                     // store the values in a temporary array.
 | ||||
| @@ -522,7 +527,7 @@ class AccountController extends Controller | ||||
|         $cache->addProperty($this->convertToNative); | ||||
|         $cache->addProperty('chart.account.revenue-accounts'); | ||||
|         if ($cache->has()) { | ||||
|              return response()->json($cache->get()); | ||||
|             return response()->json($cache->get()); | ||||
|         } | ||||
|         $start->subDay(); | ||||
| 
 | ||||
| @@ -541,35 +546,39 @@ class AccountController extends Controller | ||||
|         $endBalances   = app('steam')->finalAccountsBalance($accounts, $end); | ||||
| 
 | ||||
| 
 | ||||
| // loop the accounts, then check for balance and currency info.
 | ||||
|         foreach($accounts as $account) { | ||||
|         // 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)); | ||||
|             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) { | ||||
|                 if (!$this->convertToNative && 'native_balance' === $key) { | ||||
|                     Log::debug(sprintf('[a] Will skip expense array "%s"', $key)); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if($this->convertToNative && 'native_balance' !== $key) { | ||||
|                 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; | ||||
|                 $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.
 | ||||
|                 $startBalance = ($startBalances[$account->id][$key] ?? '0'); | ||||
|                 Log::debug(sprintf('Start balance is %s', $startBalance)); | ||||
|                 $diff        = bcsub($endBalance, $startBalance); | ||||
|                 $diff         = bcsub($endBalance, $startBalance); | ||||
|                 $currencies[$searchCode] ??= $this->currencyRepository->findByCode($searchCode); | ||||
|                 if (0 !== bccomp($diff, '0')) { | ||||
|                     // store the values in a temporary array.
 | ||||
|   | ||||
| @@ -85,11 +85,11 @@ class BudgetController extends Controller | ||||
|     public function budget(Budget $budget): JsonResponse | ||||
|     { | ||||
|         /** @var Carbon $start */ | ||||
|         $start = $this->repository->firstUseDate($budget) ?? session('start', today(config('app.timezone'))); | ||||
|         $start          = $this->repository->firstUseDate($budget) ?? session('start', today(config('app.timezone'))); | ||||
| 
 | ||||
|         /** @var Carbon $end */ | ||||
|         $end   = session('end', today(config('app.timezone'))); | ||||
|         $cache = new CacheProperties(); | ||||
|         $end            = session('end', today(config('app.timezone'))); | ||||
|         $cache          = new CacheProperties(); | ||||
|         $cache->addProperty($start); | ||||
|         $cache->addProperty($end); | ||||
|         $cache->addProperty('chart.budget.budget'); | ||||
| @@ -107,19 +107,19 @@ class BudgetController extends Controller | ||||
|         $defaultEntries = []; | ||||
|         while ($end >= $loopStart) { | ||||
|             /** @var Carbon $loopEnd */ | ||||
|             $loopEnd = app('navigation')->endOfPeriod($loopStart, $step); | ||||
|             $spent   = $this->opsRepository->sumExpenses($loopStart, $loopEnd, null, $collection); | ||||
|             $label   = trim(app('navigation')->periodShow($loopStart, $step)); | ||||
|             $loopEnd                = app('navigation')->endOfPeriod($loopStart, $step); | ||||
|             $spent                  = $this->opsRepository->sumExpenses($loopStart, $loopEnd, null, $collection); | ||||
|             $label                  = trim(app('navigation')->periodShow($loopStart, $step)); | ||||
| 
 | ||||
|             foreach ($spent as $row) { | ||||
|                 $currencyId              = $row['currency_id']; | ||||
|                 $currencyId                               = $row['currency_id']; | ||||
|                 $currencies[$currencyId] ??= $row; // don't mind the field 'sum'
 | ||||
|                 // also store this day's sum:
 | ||||
|                 $currencies[$currencyId]['spent'][$label] = $row['sum']; | ||||
|             } | ||||
|             $defaultEntries[$label] = 0; | ||||
|             // set loop start to the next period:
 | ||||
|             $loopStart = clone $loopEnd; | ||||
|             $loopStart              = clone $loopEnd; | ||||
|             $loopStart->addSecond(); | ||||
|         } | ||||
|         // loop all currencies:
 | ||||
| @@ -135,7 +135,7 @@ class BudgetController extends Controller | ||||
|                 $chartData[$currencyId]['entries'][$label] = bcmul($spent, '-1'); | ||||
|             } | ||||
|         } | ||||
|         $data = $this->generator->multiSet(array_values($chartData)); | ||||
|         $data           = $this->generator->multiSet(array_values($chartData)); | ||||
|         $cache->store($data); | ||||
| 
 | ||||
|         return response()->json($data); | ||||
| @@ -152,9 +152,9 @@ class BudgetController extends Controller | ||||
|             throw new FireflyException('This budget limit is not part of this budget.'); | ||||
|         } | ||||
| 
 | ||||
|         $start = clone $budgetLimit->start_date; | ||||
|         $end   = clone $budgetLimit->end_date; | ||||
|         $cache = new CacheProperties(); | ||||
|         $start                                  = clone $budgetLimit->start_date; | ||||
|         $end                                    = clone $budgetLimit->end_date; | ||||
|         $cache                                  = new CacheProperties(); | ||||
|         $cache->addProperty($start); | ||||
|         $cache->addProperty($end); | ||||
|         $cache->addProperty('chart.budget.budget.limit'); | ||||
| @@ -164,11 +164,11 @@ class BudgetController extends Controller | ||||
|         if ($cache->has()) { | ||||
|             return response()->json($cache->get()); | ||||
|         } | ||||
|         $locale           = app('steam')->getLocale(); | ||||
|         $entries          = []; | ||||
|         $amount           = $budgetLimit->amount; | ||||
|         $budgetCollection = new Collection([$budget]); | ||||
|         $currency         = $budgetLimit->transactionCurrency; | ||||
|         $locale                                 = app('steam')->getLocale(); | ||||
|         $entries                                = []; | ||||
|         $amount                                 = $budgetLimit->amount; | ||||
|         $budgetCollection                       = new Collection([$budget]); | ||||
|         $currency                               = $budgetLimit->transactionCurrency; | ||||
|         while ($start <= $end) { | ||||
|             $current          = clone $start; | ||||
|             $expenses         = $this->opsRepository->sumExpenses($current, $current, null, $budgetCollection, $currency); | ||||
| @@ -179,7 +179,7 @@ class BudgetController extends Controller | ||||
| 
 | ||||
|             $start->addDay(); | ||||
|         } | ||||
|         $data = $this->generator->singleSet((string) trans('firefly.left'), $entries); | ||||
|         $data                                   = $this->generator->singleSet((string) trans('firefly.left'), $entries); | ||||
|         // add currency symbol from budget limit:
 | ||||
|         $data['datasets'][0]['currency_symbol'] = $budgetLimit->transactionCurrency->symbol; | ||||
|         $data['datasets'][0]['currency_code']   = $budgetLimit->transactionCurrency->code; | ||||
| @@ -200,8 +200,8 @@ class BudgetController extends Controller | ||||
|         $cache->addProperty($budget->id); | ||||
|         $cache->addProperty($budgetLimitId); | ||||
|         $cache->addProperty('chart.budget.expense-asset'); | ||||
|         $start = session('first', today(config('app.timezone'))->startOfYear()); | ||||
|         $end   = today(); | ||||
|         $start         = session('first', today(config('app.timezone'))->startOfYear()); | ||||
|         $end           = today(); | ||||
| 
 | ||||
|         if (null !== $budgetLimit) { | ||||
|             $start = $budgetLimit->start_date; | ||||
| @@ -216,14 +216,14 @@ class BudgetController extends Controller | ||||
|         } | ||||
|         $collector->setRange($start, $end); | ||||
|         $collector->setBudget($budget); | ||||
|         $journals  = $collector->getExtractedJournals(); | ||||
|         $result    = []; | ||||
|         $chartData = []; | ||||
|         $journals      = $collector->getExtractedJournals(); | ||||
|         $result        = []; | ||||
|         $chartData     = []; | ||||
| 
 | ||||
|         // group by asset account ID:
 | ||||
|         foreach ($journals as $journal) { | ||||
|             $key                    = sprintf('%d-%d', (int) $journal['source_account_id'], $journal['currency_id']); | ||||
|             $result[$key]           ??= [ | ||||
|             $result[$key] ??= [ | ||||
|                 'amount'          => '0', | ||||
|                 'currency_symbol' => $journal['currency_symbol'], | ||||
|                 'currency_code'   => $journal['currency_code'], | ||||
| @@ -232,20 +232,20 @@ class BudgetController extends Controller | ||||
|             $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']); | ||||
|         } | ||||
| 
 | ||||
|         $names = $this->getAccountNames(array_keys($result)); | ||||
|         $names         = $this->getAccountNames(array_keys($result)); | ||||
|         foreach ($result as $combinedId => $info) { | ||||
|             $parts   = explode('-', $combinedId); | ||||
|             $assetId = (int) $parts[0]; | ||||
|             $title   = sprintf('%s (%s)', $names[$assetId] ?? '(empty)', $info['currency_name']); | ||||
|             $chartData[$title] | ||||
|                      = [ | ||||
|                 'amount'          => $info['amount'], | ||||
|                 'currency_symbol' => $info['currency_symbol'], | ||||
|                 'currency_code'   => $info['currency_code'], | ||||
|             ]; | ||||
|                          'amount'          => $info['amount'], | ||||
|                          'currency_symbol' => $info['currency_symbol'], | ||||
|                          'currency_code'   => $info['currency_code'], | ||||
|                      ]; | ||||
|         } | ||||
| 
 | ||||
|         $data = $this->generator->multiCurrencyPieChart($chartData); | ||||
|         $data          = $this->generator->multiCurrencyPieChart($chartData); | ||||
|         $cache->store($data); | ||||
| 
 | ||||
|         return response()->json($data); | ||||
| @@ -263,8 +263,8 @@ class BudgetController extends Controller | ||||
|         $cache->addProperty($budget->id); | ||||
|         $cache->addProperty($budgetLimitId); | ||||
|         $cache->addProperty('chart.budget.expense-category'); | ||||
|         $start = session('first', today(config('app.timezone'))->startOfYear()); | ||||
|         $end   = today(); | ||||
|         $start         = session('first', today(config('app.timezone'))->startOfYear()); | ||||
|         $end           = today(); | ||||
|         if (null !== $budgetLimit) { | ||||
|             $start = $budgetLimit->start_date; | ||||
|             $end   = $budgetLimit->end_date; | ||||
| @@ -278,12 +278,12 @@ class BudgetController extends Controller | ||||
|         } | ||||
|         $collector->setRange($start, $end); | ||||
|         $collector->setBudget($budget)->withCategoryInformation(); | ||||
|         $journals  = $collector->getExtractedJournals(); | ||||
|         $result    = []; | ||||
|         $chartData = []; | ||||
|         $journals      = $collector->getExtractedJournals(); | ||||
|         $result        = []; | ||||
|         $chartData     = []; | ||||
|         foreach ($journals as $journal) { | ||||
|             $key                    = sprintf('%d-%d', $journal['category_id'], $journal['currency_id']); | ||||
|             $result[$key]           ??= [ | ||||
|             $result[$key] ??= [ | ||||
|                 'amount'          => '0', | ||||
|                 'currency_symbol' => $journal['currency_symbol'], | ||||
|                 'currency_code'   => $journal['currency_code'], | ||||
| @@ -292,7 +292,7 @@ class BudgetController extends Controller | ||||
|             $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']); | ||||
|         } | ||||
| 
 | ||||
|         $names = $this->getCategoryNames(array_keys($result)); | ||||
|         $names         = $this->getCategoryNames(array_keys($result)); | ||||
|         foreach ($result as $combinedId => $info) { | ||||
|             $parts             = explode('-', $combinedId); | ||||
|             $categoryId        = (int) $parts[0]; | ||||
| @@ -303,7 +303,7 @@ class BudgetController extends Controller | ||||
|                 'currency_code'   => $info['currency_code'], | ||||
|             ]; | ||||
|         } | ||||
|         $data = $this->generator->multiCurrencyPieChart($chartData); | ||||
|         $data          = $this->generator->multiCurrencyPieChart($chartData); | ||||
|         $cache->store($data); | ||||
| 
 | ||||
|         return response()->json($data); | ||||
| @@ -321,8 +321,8 @@ class BudgetController extends Controller | ||||
|         $cache->addProperty($budget->id); | ||||
|         $cache->addProperty($budgetLimitId); | ||||
|         $cache->addProperty('chart.budget.expense-expense'); | ||||
|         $start = session('first', today(config('app.timezone'))->startOfYear()); | ||||
|         $end   = today(); | ||||
|         $start         = session('first', today(config('app.timezone'))->startOfYear()); | ||||
|         $end           = today(); | ||||
|         if (null !== $budgetLimit) { | ||||
|             $start = $budgetLimit->start_date; | ||||
|             $end   = $budgetLimit->end_date; | ||||
| @@ -336,14 +336,14 @@ class BudgetController extends Controller | ||||
|         } | ||||
|         $collector->setRange($start, $end); | ||||
|         $collector->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withAccountInformation(); | ||||
|         $journals  = $collector->getExtractedJournals(); | ||||
|         $result    = []; | ||||
|         $chartData = []; | ||||
|         $journals      = $collector->getExtractedJournals(); | ||||
|         $result        = []; | ||||
|         $chartData     = []; | ||||
| 
 | ||||
|         /** @var array $journal */ | ||||
|         foreach ($journals as $journal) { | ||||
|             $key                    = sprintf('%d-%d', $journal['destination_account_id'], $journal['currency_id']); | ||||
|             $result[$key]           ??= [ | ||||
|             $result[$key] ??= [ | ||||
|                 'amount'          => '0', | ||||
|                 'currency_symbol' => $journal['currency_symbol'], | ||||
|                 'currency_code'   => $journal['currency_code'], | ||||
| @@ -352,7 +352,7 @@ class BudgetController extends Controller | ||||
|             $result[$key]['amount'] = bcadd($journal['amount'], $result[$key]['amount']); | ||||
|         } | ||||
| 
 | ||||
|         $names = $this->getAccountNames(array_keys($result)); | ||||
|         $names         = $this->getAccountNames(array_keys($result)); | ||||
|         foreach ($result as $combinedId => $info) { | ||||
|             $parts             = explode('-', $combinedId); | ||||
|             $opposingId        = (int) $parts[0]; | ||||
| @@ -365,7 +365,7 @@ class BudgetController extends Controller | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|         $data = $this->generator->multiCurrencyPieChart($chartData); | ||||
|         $data          = $this->generator->multiCurrencyPieChart($chartData); | ||||
|         $cache->store($data); | ||||
| 
 | ||||
|         return response()->json($data); | ||||
| @@ -376,10 +376,10 @@ 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); | ||||
| @@ -387,16 +387,16 @@ class BudgetController extends Controller | ||||
|         if ($cache->has()) { | ||||
|             // return response()->json($cache->get());
 | ||||
|         } | ||||
|         Log::debug(sprintf('Regenerate frontpage chart from scratch.')); | ||||
|         $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); | ||||
| @@ -412,7 +412,7 @@ class BudgetController extends Controller | ||||
|     public function period(Budget $budget, TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse | ||||
|     { | ||||
|         // chart properties for cache:
 | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache          = new CacheProperties(); | ||||
|         $cache->addProperty($start); | ||||
|         $cache->addProperty($end); | ||||
|         $cache->addProperty($accounts); | ||||
| @@ -441,11 +441,11 @@ class BudgetController extends Controller | ||||
|             ], | ||||
|         ]; | ||||
| 
 | ||||
|         $currentStart = clone $start; | ||||
|         $currentStart   = clone $start; | ||||
|         while ($currentStart <= $end) { | ||||
|             $currentStart = app('navigation')->startOfPeriod($currentStart, $preferredRange); | ||||
|             $title        = $currentStart->isoFormat($titleFormat); | ||||
|             $currentEnd   = app('navigation')->endOfPeriod($currentStart, $preferredRange); | ||||
|             $currentStart                    = app('navigation')->startOfPeriod($currentStart, $preferredRange); | ||||
|             $title                           = $currentStart->isoFormat($titleFormat); | ||||
|             $currentEnd                      = app('navigation')->endOfPeriod($currentStart, $preferredRange); | ||||
| 
 | ||||
|             // default limit is no limit:
 | ||||
|             $chartData[0]['entries'][$title] = 0; | ||||
| @@ -454,7 +454,7 @@ class BudgetController extends Controller | ||||
|             $chartData[1]['entries'][$title] = 0; | ||||
| 
 | ||||
|             // get budget limit in this period for this currency.
 | ||||
|             $limit = $this->blRepository->find($budget, $currency, $currentStart, $currentEnd); | ||||
|             $limit                           = $this->blRepository->find($budget, $currency, $currentStart, $currentEnd); | ||||
|             if (null !== $limit) { | ||||
|                 $chartData[1]['entries'][$title] = app('steam')->bcround($limit->amount, $currency->decimal_places); | ||||
|             } | ||||
| @@ -464,11 +464,11 @@ class BudgetController extends Controller | ||||
|             $amount                          = app('steam')->positive($sum[$currency->id]['sum'] ?? '0'); | ||||
|             $chartData[0]['entries'][$title] = app('steam')->bcround($amount, $currency->decimal_places); | ||||
| 
 | ||||
|             $currentStart = clone $currentEnd; | ||||
|             $currentStart                    = clone $currentEnd; | ||||
|             $currentStart->addDay()->startOfDay(); | ||||
|         } | ||||
| 
 | ||||
|         $data = $this->generator->multiSet($chartData); | ||||
|         $data           = $this->generator->multiSet($chartData); | ||||
|         $cache->store($data); | ||||
| 
 | ||||
|         return response()->json($data); | ||||
| @@ -480,7 +480,7 @@ class BudgetController extends Controller | ||||
|     public function periodNoBudget(TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse | ||||
|     { | ||||
|         // chart properties for cache:
 | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache          = new CacheProperties(); | ||||
|         $cache->addProperty($start); | ||||
|         $cache->addProperty($end); | ||||
|         $cache->addProperty($accounts); | ||||
| @@ -504,7 +504,7 @@ class BudgetController extends Controller | ||||
|             $currentStart      = app('navigation')->addPeriod($currentStart, $preferredRange, 0); | ||||
|         } | ||||
| 
 | ||||
|         $data = $this->generator->singleSet((string) trans('firefly.spent'), $chartData); | ||||
|         $data           = $this->generator->singleSet((string) trans('firefly.spent'), $chartData); | ||||
|         $cache->store($data); | ||||
| 
 | ||||
|         return response()->json($data); | ||||
|   | ||||
| @@ -70,7 +70,7 @@ class CategoryController extends Controller | ||||
|     public function all(Category $category): JsonResponse | ||||
|     { | ||||
|         // cache results:
 | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache          = new CacheProperties(); | ||||
|         $cache->addProperty('chart.category.all'); | ||||
|         $cache->addProperty($category->id); | ||||
|         if ($cache->has()) { | ||||
| @@ -78,11 +78,11 @@ class CategoryController extends Controller | ||||
|         } | ||||
| 
 | ||||
|         /** @var CategoryRepositoryInterface $repository */ | ||||
|         $repository = app(CategoryRepositoryInterface::class); | ||||
|         $start      = $repository->firstUseDate($category) ?? $this->getDate(); | ||||
|         $range      = app('navigation')->getViewRange(false); | ||||
|         $start      = app('navigation')->startOfPeriod($start, $range); | ||||
|         $end        = $this->getDate(); | ||||
|         $repository     = app(CategoryRepositoryInterface::class); | ||||
|         $start          = $repository->firstUseDate($category) ?? $this->getDate(); | ||||
|         $range          = app('navigation')->getViewRange(false); | ||||
|         $start          = app('navigation')->startOfPeriod($start, $range); | ||||
|         $end            = $this->getDate(); | ||||
| 
 | ||||
|         /** @var WholePeriodChartGenerator $chartGenerator */ | ||||
|         $chartGenerator = app(WholePeriodChartGenerator::class); | ||||
| @@ -104,10 +104,10 @@ class CategoryController 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); | ||||
| @@ -139,7 +139,7 @@ class CategoryController extends Controller | ||||
|         if ($cache->has()) { | ||||
|             return response()->json($cache->get()); | ||||
|         } | ||||
|         $data = $this->reportPeriodChart($accounts, $start, $end, $category); | ||||
|         $data  = $this->reportPeriodChart($accounts, $start, $end, $category); | ||||
| 
 | ||||
|         $cache->store($data); | ||||
| 
 | ||||
| @@ -160,8 +160,8 @@ class CategoryController extends Controller | ||||
|             $noCatRepository = app(NoCategoryRepositoryInterface::class); | ||||
| 
 | ||||
|             // this gives us all currencies
 | ||||
|             $expenses = $noCatRepository->listExpenses($start, $end, $accounts); | ||||
|             $income   = $noCatRepository->listIncome($start, $end, $accounts); | ||||
|             $expenses        = $noCatRepository->listExpenses($start, $end, $accounts); | ||||
|             $income          = $noCatRepository->listIncome($start, $end, $accounts); | ||||
|         } | ||||
| 
 | ||||
|         if (null !== $category) { | ||||
| @@ -169,9 +169,9 @@ class CategoryController extends Controller | ||||
|             $opsRepository = app(OperationsRepositoryInterface::class); | ||||
|             $categoryId    = $category->id; | ||||
|             // this gives us all currencies
 | ||||
|             $collection = new Collection([$category]); | ||||
|             $expenses   = $opsRepository->listExpenses($start, $end, $accounts, $collection); | ||||
|             $income     = $opsRepository->listIncome($start, $end, $accounts, $collection); | ||||
|             $collection    = new Collection([$category]); | ||||
|             $expenses      = $opsRepository->listExpenses($start, $end, $accounts, $collection); | ||||
|             $income        = $opsRepository->listIncome($start, $end, $accounts, $collection); | ||||
|         } | ||||
|         $currencies = array_unique(array_merge(array_keys($income), array_keys($expenses))); | ||||
|         $periods    = app('navigation')->listOfPeriods($start, $end); | ||||
| @@ -185,19 +185,19 @@ class CategoryController extends Controller | ||||
|             $inKey        = sprintf('%d-in', $currencyId); | ||||
|             $chartData[$outKey] | ||||
|                           = [ | ||||
|                 'label'           => sprintf('%s (%s)', (string) trans('firefly.spent'), $currencyInfo['currency_name']), | ||||
|                 'entries'         => [], | ||||
|                 'type'            => 'bar', | ||||
|                 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red
 | ||||
|             ]; | ||||
|                               'label'           => sprintf('%s (%s)', (string) trans('firefly.spent'), $currencyInfo['currency_name']), | ||||
|                               'entries'         => [], | ||||
|                               'type'            => 'bar', | ||||
|                               'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red
 | ||||
|                           ]; | ||||
| 
 | ||||
|             $chartData[$inKey] | ||||
|                 = [ | ||||
|                 'label'           => sprintf('%s (%s)', (string) trans('firefly.earned'), $currencyInfo['currency_name']), | ||||
|                 'entries'         => [], | ||||
|                 'type'            => 'bar', | ||||
|                 'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green
 | ||||
|             ]; | ||||
|                           = [ | ||||
|                     'label'           => sprintf('%s (%s)', (string) trans('firefly.earned'), $currencyInfo['currency_name']), | ||||
|                     'entries'         => [], | ||||
|                     'type'            => 'bar', | ||||
|                     'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green
 | ||||
|                 ]; | ||||
|             // loop empty periods:
 | ||||
|             foreach (array_keys($periods) as $period) { | ||||
|                 $label                                 = $periods[$period]; | ||||
| @@ -205,7 +205,7 @@ class CategoryController extends Controller | ||||
|                 $chartData[$inKey]['entries'][$label]  = '0'; | ||||
|             } | ||||
|             // loop income and expenses for this category.:
 | ||||
|             $outSet = $expenses[$currencyId]['categories'][$categoryId] ?? ['transaction_journals' => []]; | ||||
|             $outSet       = $expenses[$currencyId]['categories'][$categoryId] ?? ['transaction_journals' => []]; | ||||
|             foreach ($outSet['transaction_journals'] as $journal) { | ||||
|                 $amount                               = app('steam')->positive($journal['amount']); | ||||
|                 $date                                 = $journal['date']->isoFormat($format); | ||||
| @@ -214,7 +214,7 @@ class CategoryController extends Controller | ||||
|                 $chartData[$outKey]['entries'][$date] = bcadd($amount, $chartData[$outKey]['entries'][$date]); | ||||
|             } | ||||
| 
 | ||||
|             $inSet = $income[$currencyId]['categories'][$categoryId] ?? ['transaction_journals' => []]; | ||||
|             $inSet        = $income[$currencyId]['categories'][$categoryId] ?? ['transaction_journals' => []]; | ||||
|             foreach ($inSet['transaction_journals'] as $journal) { | ||||
|                 $amount                              = app('steam')->positive($journal['amount']); | ||||
|                 $date                                = $journal['date']->isoFormat($format); | ||||
| @@ -240,7 +240,7 @@ class CategoryController extends Controller | ||||
|         if ($cache->has()) { | ||||
|             return response()->json($cache->get()); | ||||
|         } | ||||
|         $data = $this->reportPeriodChart($accounts, $start, $end, null); | ||||
|         $data  = $this->reportPeriodChart($accounts, $start, $end, null); | ||||
| 
 | ||||
|         $cache->store($data); | ||||
| 
 | ||||
| @@ -255,14 +255,14 @@ class CategoryController extends Controller | ||||
|      */ | ||||
|     public function specificPeriod(Category $category, Carbon $date): JsonResponse | ||||
|     { | ||||
|         $range = app('navigation')->getViewRange(false); | ||||
|         $start = app('navigation')->startOfPeriod($date, $range); | ||||
|         $end   = session()->get('end'); | ||||
|         $range          = app('navigation')->getViewRange(false); | ||||
|         $start          = app('navigation')->startOfPeriod($date, $range); | ||||
|         $end            = session()->get('end'); | ||||
|         if ($end < $start) { | ||||
|             [$end, $start] = [$start, $end]; | ||||
|         } | ||||
| 
 | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache          = new CacheProperties(); | ||||
|         $cache->addProperty($start); | ||||
|         $cache->addProperty($end); | ||||
|         $cache->addProperty($category->id); | ||||
|   | ||||
| @@ -33,7 +33,6 @@ use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; | ||||
| use FireflyIII\Support\Http\Controllers\GetConfigurationData; | ||||
| use FireflyIII\Support\Models\AccountBalanceCalculator; | ||||
| use FireflyIII\User; | ||||
| use Http\Discovery\Exception\NotFoundException; | ||||
| use Illuminate\Contracts\View\Factory; | ||||
| use Illuminate\Http\RedirectResponse; | ||||
| use Illuminate\Http\Request; | ||||
| @@ -63,55 +62,59 @@ class DebugController extends Controller | ||||
| 
 | ||||
|     public function routes(): never | ||||
|     { | ||||
|         if(!auth()->user()->hasRole('owner')) { | ||||
|         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') | ||||
|                 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())) { | ||||
|             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 = []; | ||||
|             $params                    = []; | ||||
|             foreach ($route->parameterNames() as $name) { | ||||
|                 $params[] = $this->getParameter($name); | ||||
|             } | ||||
|             $return[$route->getName()] = route($route->getName(), $params); | ||||
|         } | ||||
|         $count = 0; | ||||
|         $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) { | ||||
|         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); | ||||
|     } | ||||
| @@ -176,12 +179,12 @@ class DebugController extends Controller | ||||
|      */ | ||||
|     public function index() | ||||
|     { | ||||
|         $table = $this->generateTable(); | ||||
|         $table = str_replace(["\n", "\t", '  '], '', $table); | ||||
|         $now   = now(config('app.timezone'))->format('Y-m-d H:i:s'); | ||||
|         $table      = $this->generateTable(); | ||||
|         $table      = str_replace(["\n", "\t", '  '], '', $table); | ||||
|         $now        = now(config('app.timezone'))->format('Y-m-d H:i:s'); | ||||
| 
 | ||||
|         // get latest log file:
 | ||||
|         $logger = Log::driver(); | ||||
|         $logger     = Log::driver(); | ||||
|         // PHPstan doesn't recognize the method because of its polymorphic nature.
 | ||||
|         $handlers   = $logger->getHandlers(); // @phpstan-ignore-line
 | ||||
|         $logContent = ''; | ||||
| @@ -195,7 +198,7 @@ class DebugController extends Controller | ||||
|         } | ||||
|         if ('' !== $logContent) { | ||||
|             // last few lines
 | ||||
|             $logContent = 'Truncated from this point <----|' . substr((string) $logContent, -16384); | ||||
|             $logContent = 'Truncated from this point <----|'.substr((string) $logContent, -16384); | ||||
|         } | ||||
| 
 | ||||
|         return view('debug', compact('table', 'now', 'logContent')); | ||||
| @@ -275,7 +278,7 @@ class DebugController extends Controller | ||||
| 
 | ||||
|     private function getAppInfo(): array | ||||
|     { | ||||
|         $userGuard = config('auth.defaults.guard'); | ||||
|         $userGuard      = config('auth.defaults.guard'); | ||||
| 
 | ||||
|         $config         = app('fireflyconfig')->get('last_rt_job', 0); | ||||
|         $lastTime       = (int) $config->data; | ||||
| @@ -300,24 +303,24 @@ class DebugController extends Controller | ||||
|             // any of the cron jobs will do, they always run at the same time.
 | ||||
|             // but this job is the oldest, so the biggest chance it ran once
 | ||||
| 
 | ||||
|             'last_cronjob'     => $lastCronjob, | ||||
|             'last_cronjob_ago' => $lastCronjobAgo, | ||||
|             'last_cronjob'       => $lastCronjob, | ||||
|             'last_cronjob_ago'   => $lastCronjobAgo, | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     private function getuserInfo(): array | ||||
|     { | ||||
|         $userFlags = $this->getUserFlags(); | ||||
|         $userFlags      = $this->getUserFlags(); | ||||
| 
 | ||||
|         // user info
 | ||||
|         $userAgent = request()->header('user-agent'); | ||||
|         $userAgent      = request()->header('user-agent'); | ||||
| 
 | ||||
|         // set languages, see what happens:
 | ||||
|         $original       = setlocale(LC_ALL, '0'); | ||||
|         $localeAttempts = []; | ||||
|         $parts          = app('steam')->getLocaleArray(app('steam')->getLocale()); | ||||
|         foreach ($parts as $code) { | ||||
|             $code = trim($code); | ||||
|             $code                  = trim($code); | ||||
|             app('log')->debug(sprintf('Trying to set %s', $code)); | ||||
|             $result                = setlocale(LC_ALL, $code); | ||||
|             $localeAttempts[$code] = $result === $code; | ||||
| @@ -338,10 +341,10 @@ class DebugController extends Controller | ||||
| 
 | ||||
|     private function getUserFlags(): string | ||||
|     { | ||||
|         $flags = []; | ||||
|         $flags      = []; | ||||
| 
 | ||||
|         /** @var User $user */ | ||||
|         $user = auth()->user(); | ||||
|         $user       = auth()->user(); | ||||
| 
 | ||||
|         // has liabilities
 | ||||
|         if ($user->accounts()->accountTypeIn([AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE])->count() > 0) { | ||||
| @@ -357,7 +360,7 @@ class DebugController extends Controller | ||||
|         } | ||||
| 
 | ||||
|         // has stored reconciliations
 | ||||
|         $type = TransactionType::whereType(TransactionType::RECONCILIATION)->first(); | ||||
|         $type       = TransactionType::whereType(TransactionType::RECONCILIATION)->first(); | ||||
|         if ($user->transactionJournals()->where('transaction_type_id', $type->id)->count() > 0) { | ||||
|             $flags[] = '<span title="Has reconciled">:ledger:</span>'; | ||||
|         } | ||||
| @@ -407,81 +410,118 @@ class DebugController extends Controller | ||||
|         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'; | ||||
| 
 | ||||
|   | ||||
| @@ -62,31 +62,32 @@ class BoxController extends Controller | ||||
|     { | ||||
|         // Cache result, return cache if present.
 | ||||
|         /** @var Carbon $start */ | ||||
|         $start = session('start', today(config('app.timezone'))->startOfMonth()); | ||||
|         $start     = session('start', today(config('app.timezone'))->startOfMonth()); | ||||
| 
 | ||||
|         /** @var Carbon $end */ | ||||
|         $end             = session('end', today(config('app.timezone'))->endOfMonth()); | ||||
|         $cache           = new CacheProperties(); | ||||
|         $end       = session('end', today(config('app.timezone'))->endOfMonth()); | ||||
|         $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()); | ||||
|             return response()->json($cache->get()); | ||||
|         } | ||||
|         // prep some arrays:
 | ||||
|         $incomes  = []; | ||||
|         $expenses = []; | ||||
|         $sums     = []; | ||||
|         $currency = app('amount')->getDefaultCurrency(); | ||||
|         $incomes   = []; | ||||
|         $expenses  = []; | ||||
|         $sums      = []; | ||||
|         $currency  = app('amount')->getDefaultCurrency(); | ||||
| 
 | ||||
| 
 | ||||
|         // collect income of user:
 | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector = app(GroupCollectorInterface::class); | ||||
|         $collector->setRange($start, $end) | ||||
|                   ->setTypes([TransactionType::DEPOSIT]); | ||||
|         $set = $collector->getExtractedJournals(); | ||||
|             ->setTypes([TransactionType::DEPOSIT]) | ||||
|         ; | ||||
|         $set       = $collector->getExtractedJournals(); | ||||
| 
 | ||||
|         /** @var array $journal */ | ||||
|         foreach ($set as $journal) { | ||||
| @@ -102,8 +103,9 @@ class BoxController extends Controller | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector = app(GroupCollectorInterface::class); | ||||
|         $collector->setRange($start, $end) | ||||
|                   ->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); | ||||
|         $set = $collector->getExtractedJournals(); | ||||
|             ->setTypes([TransactionTypeEnum::WITHDRAWAL->value]) | ||||
|         ; | ||||
|         $set       = $collector->getExtractedJournals(); | ||||
| 
 | ||||
|         /** @var array $journal */ | ||||
|         foreach ($set as $journal) { | ||||
| @@ -116,7 +118,7 @@ class BoxController extends Controller | ||||
|         } | ||||
| 
 | ||||
|         // format amounts:
 | ||||
|         $keys = array_keys($sums); | ||||
|         $keys      = array_keys($sums); | ||||
|         foreach ($keys as $currencyId) { | ||||
|             $currency              = $repository->find($currencyId); | ||||
|             $sums[$currencyId]     = app('amount')->formatAnything($currency, $sums[$currencyId], false); | ||||
| @@ -130,7 +132,7 @@ class BoxController extends Controller | ||||
|             $expenses[$currency->id] = app('amount')->formatAnything($currency, '0', false); | ||||
|         } | ||||
| 
 | ||||
|         $response = [ | ||||
|         $response  = [ | ||||
|             'incomes'   => $incomes, | ||||
|             'expenses'  => $expenses, | ||||
|             'sums'      => $sums, | ||||
| @@ -147,7 +149,7 @@ class BoxController extends Controller | ||||
|      */ | ||||
|     public function netWorth(): JsonResponse | ||||
|     { | ||||
|         $date = today(config('app.timezone'))->endOfDay(); | ||||
|         $date              = today(config('app.timezone'))->endOfDay(); | ||||
| 
 | ||||
|         // start and end in the future? use $end
 | ||||
|         if ($this->notInSessionRange($date)) { | ||||
| @@ -156,7 +158,7 @@ class BoxController extends Controller | ||||
|         } | ||||
| 
 | ||||
|         /** @var NetWorthInterface $netWorthHelper */ | ||||
|         $netWorthHelper = app(NetWorthInterface::class); | ||||
|         $netWorthHelper    = app(NetWorthInterface::class); | ||||
|         $netWorthHelper->setUser(auth()->user()); | ||||
| 
 | ||||
|         /** @var AccountRepositoryInterface $accountRepository */ | ||||
| @@ -167,7 +169,7 @@ class BoxController extends Controller | ||||
|         app('log')->debug(sprintf('Found %d accounts.', $allAccounts->count())); | ||||
| 
 | ||||
|         // filter list on preference of being included.
 | ||||
|         $filtered = $allAccounts->filter( | ||||
|         $filtered          = $allAccounts->filter( | ||||
|             static function (Account $account) use ($accountRepository) { | ||||
|                 $includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth'); | ||||
|                 $result          = null === $includeNetWorth ? true : '1' === $includeNetWorth; | ||||
| @@ -179,15 +181,15 @@ class BoxController extends Controller | ||||
|             } | ||||
|         ); | ||||
| 
 | ||||
|         $netWorthSet = $netWorthHelper->byAccounts($filtered, $date); | ||||
|         $return      = []; | ||||
|         $netWorthSet       = $netWorthHelper->byAccounts($filtered, $date); | ||||
|         $return            = []; | ||||
|         foreach ($netWorthSet as $key => $data) { | ||||
|             if ('native' === $key) { | ||||
|                 continue; | ||||
|             } | ||||
|             $return[$data['currency_id']] = app('amount')->formatFlat($data['currency_symbol'], $data['currency_decimal_places'], $data['balance'], false); | ||||
|         } | ||||
|         $return = [ | ||||
|         $return            = [ | ||||
|             'net_worths' => array_values($return), | ||||
|         ]; | ||||
| 
 | ||||
|   | ||||
| @@ -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','native_virtual_balance']; | ||||
|     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; | ||||
|   | ||||
| @@ -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','native_amount']; | ||||
|     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). | ||||
|   | ||||
| @@ -61,7 +61,8 @@ class BillRepository implements BillRepositoryInterface | ||||
|             $search->whereLike('name', sprintf('%%%s', $query)); | ||||
|         } | ||||
|         $search->orderBy('name', 'ASC') | ||||
|                ->where('active', true); | ||||
|             ->where('active', true) | ||||
|         ; | ||||
| 
 | ||||
|         return $search->take($limit)->get(); | ||||
|     } | ||||
| @@ -73,7 +74,8 @@ class BillRepository implements BillRepositoryInterface | ||||
|             $search->whereLike('name', sprintf('%s%%', $query)); | ||||
|         } | ||||
|         $search->orderBy('name', 'ASC') | ||||
|                ->where('active', true); | ||||
|             ->where('active', true) | ||||
|         ; | ||||
| 
 | ||||
|         return $search->take($limit)->get(); | ||||
|     } | ||||
| @@ -156,7 +158,7 @@ class BillRepository implements BillRepositoryInterface | ||||
|      */ | ||||
|     public function getAttachments(Bill $bill): Collection | ||||
|     { | ||||
|         $set = $bill->attachments()->get(); | ||||
|         $set  = $bill->attachments()->get(); | ||||
| 
 | ||||
|         /** @var \Storage $disk */ | ||||
|         $disk = \Storage::disk('upload'); | ||||
| @@ -175,9 +177,10 @@ class BillRepository implements BillRepositoryInterface | ||||
|     public function getBills(): Collection | ||||
|     { | ||||
|         return $this->user->bills() | ||||
|                           ->orderBy('order', 'ASC') | ||||
|                           ->orderBy('active', 'DESC') | ||||
|                           ->orderBy('name', 'ASC')->get(); | ||||
|             ->orderBy('order', 'ASC') | ||||
|             ->orderBy('active', 'DESC') | ||||
|             ->orderBy('name', 'ASC')->get() | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     public function getBillsForAccounts(Collection $accounts): Collection | ||||
| @@ -201,24 +204,25 @@ class BillRepository implements BillRepositoryInterface | ||||
|         $ids    = $accounts->pluck('id')->toArray(); | ||||
| 
 | ||||
|         return $this->user->bills() | ||||
|                           ->leftJoin( | ||||
|                               'transaction_journals', | ||||
|                               static function (JoinClause $join): void { | ||||
|                                   $join->on('transaction_journals.bill_id', '=', 'bills.id')->whereNull('transaction_journals.deleted_at'); | ||||
|                               } | ||||
|                           ) | ||||
|                           ->leftJoin( | ||||
|                               'transactions', | ||||
|                               static function (JoinClause $join): void { | ||||
|                                   $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); | ||||
|                               } | ||||
|                           ) | ||||
|                           ->whereIn('transactions.account_id', $ids) | ||||
|                           ->whereNull('transaction_journals.deleted_at') | ||||
|                           ->orderBy('bills.active', 'DESC') | ||||
|                           ->orderBy('bills.name', 'ASC') | ||||
|                           ->groupBy($fields) | ||||
|                           ->get($fields); | ||||
|             ->leftJoin( | ||||
|                 'transaction_journals', | ||||
|                 static function (JoinClause $join): void { | ||||
|                     $join->on('transaction_journals.bill_id', '=', 'bills.id')->whereNull('transaction_journals.deleted_at'); | ||||
|                 } | ||||
|             ) | ||||
|             ->leftJoin( | ||||
|                 'transactions', | ||||
|                 static function (JoinClause $join): void { | ||||
|                     $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); | ||||
|                 } | ||||
|             ) | ||||
|             ->whereIn('transactions.account_id', $ids) | ||||
|             ->whereNull('transaction_journals.deleted_at') | ||||
|             ->orderBy('bills.active', 'DESC') | ||||
|             ->orderBy('bills.name', 'ASC') | ||||
|             ->groupBy($fields) | ||||
|             ->get($fields) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -243,7 +247,7 @@ class BillRepository implements BillRepositoryInterface | ||||
|     public function getOverallAverage(Bill $bill): array | ||||
|     { | ||||
|         /** @var JournalRepositoryInterface $repos */ | ||||
|         $repos = app(JournalRepositoryInterface::class); | ||||
|         $repos    = app(JournalRepositoryInterface::class); | ||||
|         $repos->setUser($this->user); | ||||
| 
 | ||||
|         // get and sort on currency
 | ||||
| @@ -256,7 +260,7 @@ class BillRepository implements BillRepositoryInterface | ||||
|             $transaction                = $journal->transactions()->where('amount', '<', 0)->first(); | ||||
|             $currencyId                 = (int) $journal->transaction_currency_id; | ||||
|             $currency                   = $journal->transactionCurrency; | ||||
|             $result[$currencyId]        ??= [ | ||||
|             $result[$currencyId] ??= [ | ||||
|                 'sum'                     => '0', | ||||
|                 'count'                   => 0, | ||||
|                 'avg'                     => '0', | ||||
| @@ -281,7 +285,7 @@ class BillRepository implements BillRepositoryInterface | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
|     public function setUser(null | Authenticatable | User $user): void | ||||
|     public function setUser(null|Authenticatable|User $user): void | ||||
|     { | ||||
|         if ($user instanceof User) { | ||||
|             $this->user = $user; | ||||
| @@ -291,8 +295,9 @@ class BillRepository implements BillRepositoryInterface | ||||
|     public function getPaginator(int $size): LengthAwarePaginator | ||||
|     { | ||||
|         return $this->user->bills() | ||||
|                           ->orderBy('active', 'DESC') | ||||
|                           ->orderBy('name', 'ASC')->paginate($size); | ||||
|             ->orderBy('active', 'DESC') | ||||
|             ->orderBy('name', 'ASC')->paginate($size) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -305,13 +310,14 @@ class BillRepository implements BillRepositoryInterface | ||||
|         Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString())); | ||||
| 
 | ||||
|         return $bill->transactionJournals() | ||||
|                     ->before($end)->after($start)->get( | ||||
|             ->before($end)->after($start)->get( | ||||
|                 [ | ||||
|                     'transaction_journals.id', | ||||
|                     'transaction_journals.date', | ||||
|                     'transaction_journals.transaction_group_id', | ||||
|                 ] | ||||
|             ); | ||||
|             ) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -320,10 +326,11 @@ class BillRepository implements BillRepositoryInterface | ||||
|     public function getRulesForBill(Bill $bill): Collection | ||||
|     { | ||||
|         return $this->user->rules() | ||||
|                           ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id') | ||||
|                           ->where('rule_actions.action_type', 'link_to_bill') | ||||
|                           ->where('rule_actions.action_value', $bill->name) | ||||
|                           ->get(['rules.*']); | ||||
|             ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id') | ||||
|             ->where('rule_actions.action_type', 'link_to_bill') | ||||
|             ->where('rule_actions.action_value', $bill->name) | ||||
|             ->get(['rules.*']) | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -334,15 +341,16 @@ class BillRepository implements BillRepositoryInterface | ||||
|      */ | ||||
|     public function getRulesForBills(Collection $collection): array | ||||
|     { | ||||
|         $rules = $this->user->rules() | ||||
|                             ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id') | ||||
|                             ->where('rule_actions.action_type', 'link_to_bill') | ||||
|                             ->get(['rules.id', 'rules.title', 'rule_actions.action_value', 'rules.active']); | ||||
|         $array = []; | ||||
|         $rules  = $this->user->rules() | ||||
|             ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id') | ||||
|             ->where('rule_actions.action_type', 'link_to_bill') | ||||
|             ->get(['rules.id', 'rules.title', 'rule_actions.action_value', 'rules.active']) | ||||
|         ; | ||||
|         $array  = []; | ||||
| 
 | ||||
|         /** @var Rule $rule */ | ||||
|         foreach ($rules as $rule) { | ||||
|             $array[$rule->action_value]   ??= []; | ||||
|             $array[$rule->action_value] ??= []; | ||||
|             $array[$rule->action_value][] = ['id' => $rule->id, 'title' => $rule->title, 'active' => $rule->active]; | ||||
|         } | ||||
|         $return = []; | ||||
| @@ -356,27 +364,28 @@ class BillRepository implements BillRepositoryInterface | ||||
|     public function getYearAverage(Bill $bill, Carbon $date): array | ||||
|     { | ||||
|         /** @var JournalRepositoryInterface $repos */ | ||||
|         $repos = app(JournalRepositoryInterface::class); | ||||
|         $repos    = app(JournalRepositoryInterface::class); | ||||
|         $repos->setUser($this->user); | ||||
| 
 | ||||
|         // get and sort on currency
 | ||||
|         $result = []; | ||||
|         $result   = []; | ||||
| 
 | ||||
|         $journals = $bill->transactionJournals() | ||||
|                          ->where('date', '>=', $date->year . '-01-01 00:00:00') | ||||
|                          ->where('date', '<=', $date->year . '-12-31 23:59:59') | ||||
|                          ->get(); | ||||
|             ->where('date', '>=', $date->year.'-01-01 00:00:00') | ||||
|             ->where('date', '<=', $date->year.'-12-31 23:59:59') | ||||
|             ->get() | ||||
|         ; | ||||
| 
 | ||||
|         /** @var TransactionJournal $journal */ | ||||
|         foreach ($journals as $journal) { | ||||
|             /** @var null|Transaction $transaction */ | ||||
|             $transaction = $journal->transactions()->where('amount', '<', 0)->first(); | ||||
|             $transaction                = $journal->transactions()->where('amount', '<', 0)->first(); | ||||
|             if (null === $transaction) { | ||||
|                 continue; | ||||
|             } | ||||
|             $currencyId                 = (int) $journal->transaction_currency_id; | ||||
|             $currency                   = $journal->transactionCurrency; | ||||
|             $result[$currencyId]        ??= [ | ||||
|             $result[$currencyId] ??= [ | ||||
|                 'sum'                     => '0', | ||||
|                 'count'                   => 0, | ||||
|                 'avg'                     => '0', | ||||
| @@ -420,7 +429,7 @@ class BillRepository implements BillRepositoryInterface | ||||
|      */ | ||||
|     public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon | ||||
|     { | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache        = new CacheProperties(); | ||||
|         $cache->addProperty($bill->id); | ||||
|         $cache->addProperty('nextExpectedMatch'); | ||||
|         $cache->addProperty($date); | ||||
| @@ -428,17 +437,17 @@ class BillRepository implements BillRepositoryInterface | ||||
|             return $cache->get(); | ||||
|         } | ||||
|         // find the most recent date for this bill NOT in the future. Cache this date:
 | ||||
|         $start = clone $bill->date; | ||||
|         $start        = clone $bill->date; | ||||
|         $start->startOfDay(); | ||||
|         app('log')->debug('nextExpectedMatch: Start is ' . $start->format('Y-m-d')); | ||||
|         app('log')->debug('nextExpectedMatch: Start is '.$start->format('Y-m-d')); | ||||
| 
 | ||||
|         while ($start < $date) { | ||||
|             app('log')->debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d H:i:s'), $date->format('Y-m-d H:i:s'))); | ||||
|             $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); | ||||
|             app('log')->debug('Start is now ' . $start->format('Y-m-d H:i:s')); | ||||
|             app('log')->debug('Start is now '.$start->format('Y-m-d H:i:s')); | ||||
|         } | ||||
| 
 | ||||
|         $end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); | ||||
|         $end          = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); | ||||
|         $end->endOfDay(); | ||||
| 
 | ||||
|         // see if the bill was paid in this period.
 | ||||
| @@ -450,8 +459,8 @@ class BillRepository implements BillRepositoryInterface | ||||
|             $start = clone $end; | ||||
|             $end   = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); | ||||
|         } | ||||
|         app('log')->debug('nextExpectedMatch: Final start is ' . $start->format('Y-m-d')); | ||||
|         app('log')->debug('nextExpectedMatch: Matching end is ' . $end->format('Y-m-d')); | ||||
|         app('log')->debug('nextExpectedMatch: Final start is '.$start->format('Y-m-d')); | ||||
|         app('log')->debug('nextExpectedMatch: Matching end is '.$end->format('Y-m-d')); | ||||
| 
 | ||||
|         $cache->store($start); | ||||
| 
 | ||||
| @@ -512,8 +521,8 @@ class BillRepository implements BillRepositoryInterface | ||||
|         foreach ($bills as $bill) { | ||||
| 
 | ||||
|             /** @var Collection $set */ | ||||
|             $set                         = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']); | ||||
|             $currency                    = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency; | ||||
|             $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, | ||||
| @@ -522,7 +531,8 @@ class BillRepository implements BillRepositoryInterface | ||||
|                 'decimal_places' => $currency->decimal_places, | ||||
|                 'sum'            => '0', | ||||
|             ]; | ||||
|             $setAmount                   = '0'; | ||||
|             $setAmount                    = '0'; | ||||
| 
 | ||||
|             /** @var TransactionJournal $transactionJournal */ | ||||
|             foreach ($set as $transactionJournal) { | ||||
|                 $setAmount = bcadd($setAmount, Amount::getAmountFromJournalObject($transactionJournal)); | ||||
| @@ -538,10 +548,10 @@ class BillRepository implements BillRepositoryInterface | ||||
|     public function getActiveBills(): Collection | ||||
|     { | ||||
|         return $this->user->bills() | ||||
|                           ->where('active', true) | ||||
|                           ->orderBy('bills.name', 'ASC') | ||||
|                           ->get(['bills.*', \DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount')]) // @phpstan-ignore-line
 | ||||
|             ; | ||||
|             ->where('active', true) | ||||
|             ->orderBy('bills.name', 'ASC') | ||||
|             ->get(['bills.*', \DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount')]) // @phpstan-ignore-line
 | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     public function sumUnpaidInRange(Carbon $start, Carbon $end): array | ||||
| @@ -555,21 +565,21 @@ class BillRepository implements BillRepositoryInterface | ||||
|         /** @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));
 | ||||
|             // Log::debug(sprintf('min field is %s, max field is %s', $minField, $maxField));
 | ||||
| 
 | ||||
|             if ($total > 0) { | ||||
|                 $currency                     = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency; | ||||
|                 $average                      = bcdiv(bcadd($bill->$maxField, $bill->$minField), '2'); | ||||
|                 $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]        ??= [ | ||||
|                 $return[$currency->id] ??= [ | ||||
|                     'id'             => (string) $currency->id, | ||||
|                     'name'           => $currency->name, | ||||
|                     'symbol'         => $currency->symbol, | ||||
| @@ -607,7 +617,7 @@ class BillRepository implements BillRepositoryInterface | ||||
| 
 | ||||
|             // app('log')->debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
 | ||||
| 
 | ||||
|             $currentStart = clone $nextExpectedMatch; | ||||
|             $currentStart      = clone $nextExpectedMatch; | ||||
|         } | ||||
| 
 | ||||
|         return $set; | ||||
|   | ||||
| @@ -47,9 +47,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface | ||||
| 
 | ||||
|         /** @var AvailableBudget $availableBudget */ | ||||
|         foreach ($availableBudgets as $availableBudget) { | ||||
|             $start = $availableBudget->start_date->format('Y-m-d'); | ||||
|             $end   = $availableBudget->end_date->format('Y-m-d'); | ||||
|             $key   = sprintf('%s-%s-%s', $availableBudget->transaction_currency_id, $start, $end); | ||||
|             $start        = $availableBudget->start_date->format('Y-m-d'); | ||||
|             $end          = $availableBudget->end_date->format('Y-m-d'); | ||||
|             $key          = sprintf('%s-%s-%s', $availableBudget->transaction_currency_id, $start, $end); | ||||
|             if (array_key_exists($key, $exists)) { | ||||
|                 app('log')->debug(sprintf('Found duplicate AB: %s %s, %s-%s. Has been deleted', $availableBudget->transaction_currency_id, $availableBudget->amount, $start, $end)); | ||||
|                 $availableBudget->delete(); | ||||
| @@ -101,21 +101,23 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface | ||||
|     public function find(TransactionCurrency $currency, Carbon $start, Carbon $end): ?AvailableBudget | ||||
|     { | ||||
|         return $this->user->availableBudgets() | ||||
|                           ->where('transaction_currency_id', $currency->id) | ||||
|                           ->where('start_date', $start->format('Y-m-d')) | ||||
|                           ->where('end_date', $end->format('Y-m-d')) | ||||
|                           ->first(); | ||||
|             ->where('transaction_currency_id', $currency->id) | ||||
|             ->where('start_date', $start->format('Y-m-d')) | ||||
|             ->where('end_date', $end->format('Y-m-d')) | ||||
|             ->first() | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string | ||||
|     { | ||||
|         $amount = '0'; | ||||
|         $amount          = '0'; | ||||
| 
 | ||||
|         /** @var null|AvailableBudget $availableBudget */ | ||||
|         $availableBudget = $this->user->availableBudgets() | ||||
|                                       ->where('transaction_currency_id', $currency->id) | ||||
|                                       ->where('start_date', $start->format('Y-m-d')) | ||||
|                                       ->where('end_date', $end->format('Y-m-d'))->first(); | ||||
|             ->where('transaction_currency_id', $currency->id) | ||||
|             ->where('start_date', $start->format('Y-m-d')) | ||||
|             ->where('end_date', $end->format('Y-m-d'))->first() | ||||
|         ; | ||||
|         if (null !== $availableBudget) { | ||||
|             $amount = $availableBudget->amount; | ||||
|         } | ||||
| @@ -127,20 +129,22 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface | ||||
|     { | ||||
|         $return           = []; | ||||
|         $availableBudgets = $this->user->availableBudgets() | ||||
|                                        ->where('start_date', $start->format('Y-m-d')) | ||||
|                                        ->where('end_date', $end->format('Y-m-d'))->get(); | ||||
|             ->where('start_date', $start->format('Y-m-d')) | ||||
|             ->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(); | ||||
|         $convertToNative  = app('preferences')->getForUser($this->user, 'convert_to_native', false)->data; | ||||
|         $default          = app('amount')->getDefaultCurrency(); | ||||
| 
 | ||||
|         /** @var AvailableBudget $availableBudget */ | ||||
|         foreach ($availableBudgets as $availableBudget) { | ||||
|             $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] = $return[$currencyId] ?? '0'; | ||||
|             $return[$currencyId] = bcadd($return[$currencyId], $availableBudget->$field); | ||||
|             $return[$currencyId] ??= '0'; | ||||
|             $return[$currencyId] = bcadd($return[$currencyId], $availableBudget->{$field}); | ||||
|         } | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
| 
 | ||||
| @@ -175,9 +179,10 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface | ||||
|     public function getAvailableBudgetsByExactDate(Carbon $start, Carbon $end): Collection | ||||
|     { | ||||
|         return $this->user->availableBudgets() | ||||
|                           ->where('start_date', '=', $start->format('Y-m-d')) | ||||
|                           ->where('end_date', '=', $end->format('Y-m-d')) | ||||
|                           ->get(); | ||||
|             ->where('start_date', '=', $start->format('Y-m-d')) | ||||
|             ->where('end_date', '=', $end->format('Y-m-d')) | ||||
|             ->get() | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     public function getByCurrencyDate(Carbon $start, Carbon $end, TransactionCurrency $currency): ?AvailableBudget | ||||
| @@ -186,7 +191,8 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface | ||||
|             ->availableBudgets() | ||||
|             ->where('transaction_currency_id', $currency->id) | ||||
|             ->where('start_date', $start->format('Y-m-d')) | ||||
|             ->where('end_date', $end->format('Y-m-d'))->first(); | ||||
|             ->where('end_date', $end->format('Y-m-d'))->first() | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @@ -194,12 +200,13 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface | ||||
|      */ | ||||
|     public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget | ||||
|     { | ||||
|         $availableBudget = $this->user->availableBudgets() | ||||
|                                       ->where('transaction_currency_id', $currency->id) | ||||
|                                       ->where('start_date', $start->format('Y-m-d')) | ||||
|                                       ->where('end_date', $end->format('Y-m-d'))->first(); | ||||
|         $availableBudget         = $this->user->availableBudgets() | ||||
|             ->where('transaction_currency_id', $currency->id) | ||||
|             ->where('start_date', $start->format('Y-m-d')) | ||||
|             ->where('end_date', $end->format('Y-m-d'))->first() | ||||
|         ; | ||||
|         if (null === $availableBudget) { | ||||
|             $availableBudget = new AvailableBudget(); | ||||
|             $availableBudget                = new AvailableBudget(); | ||||
|             $availableBudget->user()->associate($this->user); | ||||
|             $availableBudget->transactionCurrency()->associate($currency); | ||||
|             $availableBudget->start_date    = $start->startOfDay()->format('Y-m-d'); // @phpstan-ignore-line
 | ||||
| @@ -213,7 +220,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface | ||||
|         return $availableBudget; | ||||
|     } | ||||
| 
 | ||||
|     public function setUser(null | Authenticatable | User $user): void | ||||
|     public function setUser(null|Authenticatable|User $user): void | ||||
|     { | ||||
|         if ($user instanceof User) { | ||||
|             $this->user = $user; | ||||
| @@ -226,7 +233,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface | ||||
|         if ($start instanceof Carbon) { | ||||
|             $start = $data['start']->startOfDay(); | ||||
|         } | ||||
|         $end = $data['end']; | ||||
|         $end   = $data['end']; | ||||
|         if ($end instanceof Carbon) { | ||||
|             $end = $data['end']->endOfDay(); | ||||
|         } | ||||
|   | ||||
| @@ -63,7 +63,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             ++$count; | ||||
|             app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total)); | ||||
|         } | ||||
|         $avg = $total; | ||||
|         $avg   = $total; | ||||
|         if ($count > 0) { | ||||
|             $avg = bcdiv($total, (string) $count); | ||||
|         } | ||||
| @@ -85,21 +85,21 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
| 
 | ||||
|         // get all transactions:
 | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector = app(GroupCollectorInterface::class); | ||||
|         $collector    = app(GroupCollectorInterface::class); | ||||
|         $collector->setAccounts($accounts)->setRange($start, $end); | ||||
|         $collector->setBudgets($budgets); | ||||
|         $journals = $collector->getExtractedJournals(); | ||||
|         $journals     = $collector->getExtractedJournals(); | ||||
| 
 | ||||
|         // loop transactions:
 | ||||
|         /** @var array $journal */ | ||||
|         foreach ($journals as $journal) { | ||||
|             // prep data array for currency:
 | ||||
|             $budgetId   = (int) $journal['budget_id']; | ||||
|             $budgetName = $journal['budget_name']; | ||||
|             $currencyId = (int) $journal['currency_id']; | ||||
|             $key        = sprintf('%d-%d', $budgetId, $currencyId); | ||||
|             $budgetId                     = (int) $journal['budget_id']; | ||||
|             $budgetName                   = $journal['budget_name']; | ||||
|             $currencyId                   = (int) $journal['currency_id']; | ||||
|             $key                          = sprintf('%d-%d', $budgetId, $currencyId); | ||||
| 
 | ||||
|             $data[$key]                   ??= [ | ||||
|             $data[$key] ??= [ | ||||
|                 'id'                      => $budgetId, | ||||
|                 'name'                    => sprintf('%s (%s)', $budgetName, $journal['currency_name']), | ||||
|                 'sum'                     => '0', | ||||
| @@ -137,13 +137,13 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             $collector->setBudgets($this->getBudgets()); | ||||
|         } | ||||
|         $collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation(); | ||||
|         $journals = $collector->getExtractedJournals(); | ||||
|         $array    = []; | ||||
|         $journals  = $collector->getExtractedJournals(); | ||||
|         $array     = []; | ||||
| 
 | ||||
|         foreach ($journals as $journal) { | ||||
|             $currencyId = (int) $journal['currency_id']; | ||||
|             $budgetId   = (int) $journal['budget_id']; | ||||
|             $budgetName = (string) $journal['budget_name']; | ||||
|             $currencyId                                                                   = (int) $journal['currency_id']; | ||||
|             $budgetId                                                                     = (int) $journal['budget_id']; | ||||
|             $budgetName                                                                   = (string) $journal['budget_name']; | ||||
| 
 | ||||
|             // catch "no category" entries.
 | ||||
|             if (0 === $budgetId) { | ||||
| @@ -151,7 +151,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             } | ||||
| 
 | ||||
|             // info about the currency:
 | ||||
|             $array[$currencyId] ??= [ | ||||
|             $array[$currencyId]                       ??= [ | ||||
|                 'budgets'                 => [], | ||||
|                 'currency_id'             => $currencyId, | ||||
|                 'currency_name'           => $journal['currency_name'], | ||||
| @@ -186,7 +186,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|         return $array; | ||||
|     } | ||||
| 
 | ||||
|     public function setUser(null | Authenticatable | User $user): void | ||||
|     public function setUser(null|Authenticatable|User $user): void | ||||
|     { | ||||
|         if ($user instanceof User) { | ||||
|             $this->user = $user; | ||||
| @@ -210,8 +210,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|         ?Collection          $accounts = null, | ||||
|         ?Collection          $budgets = null, | ||||
|         ?TransactionCurrency $currency = null | ||||
|     ): array | ||||
|     { | ||||
|     ): array { | ||||
|         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.
 | ||||
| @@ -219,10 +218,10 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
| 
 | ||||
|         // 2024-12-24 disable the exclusion for now.
 | ||||
| 
 | ||||
|         $repository = app(AccountRepositoryInterface::class); | ||||
|         $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; | ||||
| @@ -236,11 +235,12 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|         } | ||||
| 
 | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector = app(GroupCollectorInterface::class); | ||||
|         $collector       = app(GroupCollectorInterface::class); | ||||
|         $collector->setUser($this->user) | ||||
|                   ->setRange($start, $end) | ||||
|             ->setRange($start, $end) | ||||
|             // ->excludeDestinationAccounts($selection)
 | ||||
|                   ->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); | ||||
|             ->setTypes([TransactionTypeEnum::WITHDRAWAL->value]) | ||||
|         ; | ||||
| 
 | ||||
|         if (null !== $accounts) { | ||||
|             $collector->setAccounts($accounts); | ||||
| @@ -253,46 +253,46 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             $collector->setNormalCurrency($currency); | ||||
|         } | ||||
|         $collector->setBudgets($budgets); | ||||
|         $journals = $collector->getExtractedJournals(); | ||||
|         $journals        = $collector->getExtractedJournals(); | ||||
| 
 | ||||
|         // same but for transactions in the foreign currency:
 | ||||
|         if (null !== $currency) { | ||||
|             Log::debug('STOP looking for transactions in the foreign currency.'); | ||||
| 
 | ||||
| //            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;
 | ||||
|             //            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'])
 | ||||
| //            );
 | ||||
|             //            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']; | ||||
|             $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) { | ||||
|                 $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; | ||||
| @@ -302,12 +302,12 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|                 } | ||||
|             } | ||||
|             if (!$convertToNative) { | ||||
|                 $amount                = $journal['amount']; | ||||
|                 $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']; | ||||
|                     $amount                = $journal['foreign_amount']; | ||||
|                     $currencyId            = (int) $journal['foreign_currency_id']; | ||||
|                     $currencyName          = $journal['foreign_currency_name']; | ||||
|                     $currencySymbol        = $journal['foreign_currency_symbol']; | ||||
| @@ -315,7 +315,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|                     $currencyDecimalPlaces = $journal['foreign_currency_decimal_places']; | ||||
|                 } | ||||
|             } | ||||
|             $array[$currencyId]        ??= [ | ||||
|             $array[$currencyId] ??= [ | ||||
|                 'sum'                     => '0', | ||||
|                 'currency_id'             => $currencyId, | ||||
|                 'currency_name'           => $currencyName, | ||||
| @@ -327,6 +327,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             Log::debug(sprintf('Journal #%d adds amount %s %s', $journal['transaction_journal_id'], $currencyCode, $amount)); | ||||
|         } | ||||
|         Log::debug('End of sumExpenses.', $array); | ||||
| 
 | ||||
|         return $array; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -145,26 +145,26 @@ 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']; | ||||
|             $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); | ||||
| @@ -184,7 +184,7 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             $array[$currencyId]        ??= [ | ||||
|             $array[$currencyId] ??= [ | ||||
|                 'sum'                     => '0', | ||||
|                 'currency_id'             => (string) $currencyId, | ||||
|                 'currency_name'           => $currencyName, | ||||
|   | ||||
| @@ -63,13 +63,13 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             $collector->setCategories($this->getCategories()); | ||||
|         } | ||||
|         $collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation(); | ||||
|         $journals = $collector->getExtractedJournals(); | ||||
|         $array    = []; | ||||
|         $journals  = $collector->getExtractedJournals(); | ||||
|         $array     = []; | ||||
| 
 | ||||
|         foreach ($journals as $journal) { | ||||
|             $currencyId   = (int) $journal['currency_id']; | ||||
|             $categoryId   = (int) $journal['category_id']; | ||||
|             $categoryName = (string) $journal['category_name']; | ||||
|             $currencyId                                                                        = (int) $journal['currency_id']; | ||||
|             $categoryId                                                                        = (int) $journal['category_id']; | ||||
|             $categoryName                                                                      = (string) $journal['category_name']; | ||||
| 
 | ||||
|             // catch "no category" entries.
 | ||||
|             if (0 === $categoryId) { | ||||
| @@ -77,7 +77,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             } | ||||
| 
 | ||||
|             // info about the currency:
 | ||||
|             $array[$currencyId] ??= [ | ||||
|             $array[$currencyId]                            ??= [ | ||||
|                 'categories'              => [], | ||||
|                 'currency_id'             => (string) $currencyId, | ||||
|                 'currency_name'           => $journal['currency_name'], | ||||
| @@ -112,7 +112,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|         return $array; | ||||
|     } | ||||
| 
 | ||||
|     public function setUser(null | Authenticatable | User $user): void | ||||
|     public function setUser(null|Authenticatable|User $user): void | ||||
|     { | ||||
|         if ($user instanceof User) { | ||||
|             $this->user = $user; | ||||
| @@ -147,13 +147,13 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             $collector->setCategories($this->getCategories()); | ||||
|         } | ||||
|         $collector->withCategoryInformation()->withAccountInformation(); | ||||
|         $journals = $collector->getExtractedJournals(); | ||||
|         $array    = []; | ||||
|         $journals  = $collector->getExtractedJournals(); | ||||
|         $array     = []; | ||||
| 
 | ||||
|         foreach ($journals as $journal) { | ||||
|             $currencyId   = (int) $journal['currency_id']; | ||||
|             $categoryId   = (int) $journal['category_id']; | ||||
|             $categoryName = (string) $journal['category_name']; | ||||
|             $currencyId                                                                        = (int) $journal['currency_id']; | ||||
|             $categoryId                                                                        = (int) $journal['category_id']; | ||||
|             $categoryName                                                                      = (string) $journal['category_name']; | ||||
| 
 | ||||
|             // catch "no category" entries.
 | ||||
|             if (0 === $categoryId) { | ||||
| @@ -161,7 +161,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             } | ||||
| 
 | ||||
|             // info about the currency:
 | ||||
|             $array[$currencyId] ??= [ | ||||
|             $array[$currencyId]                            ??= [ | ||||
|                 'categories'              => [], | ||||
|                 'currency_id'             => (string) $currencyId, | ||||
|                 'currency_name'           => $journal['currency_name'], | ||||
| @@ -200,7 +200,8 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector = app(GroupCollectorInterface::class); | ||||
|         $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::TRANSFER]) | ||||
|                   ->setDestinationAccounts($accounts)->excludeSourceAccounts($accounts); | ||||
|             ->setDestinationAccounts($accounts)->excludeSourceAccounts($accounts) | ||||
|         ; | ||||
|         if (null !== $categories && $categories->count() > 0) { | ||||
|             $collector->setCategories($categories); | ||||
|         } | ||||
| @@ -208,13 +209,13 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             $collector->setCategories($this->getCategories()); | ||||
|         } | ||||
|         $collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation(); | ||||
|         $journals = $collector->getExtractedJournals(); | ||||
|         $array    = []; | ||||
|         $journals  = $collector->getExtractedJournals(); | ||||
|         $array     = []; | ||||
| 
 | ||||
|         foreach ($journals as $journal) { | ||||
|             $currencyId   = (int) $journal['currency_id']; | ||||
|             $categoryId   = (int) $journal['category_id']; | ||||
|             $categoryName = (string) $journal['category_name']; | ||||
|             $currencyId                                                                        = (int) $journal['currency_id']; | ||||
|             $categoryId                                                                        = (int) $journal['category_id']; | ||||
|             $categoryName                                                                      = (string) $journal['category_name']; | ||||
| 
 | ||||
|             // catch "no category" entries.
 | ||||
|             if (0 === $categoryId) { | ||||
| @@ -222,7 +223,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             } | ||||
| 
 | ||||
|             // info about the currency:
 | ||||
|             $array[$currencyId] ??= [ | ||||
|             $array[$currencyId]                            ??= [ | ||||
|                 'categories'              => [], | ||||
|                 'currency_id'             => (string) $currencyId, | ||||
|                 'currency_name'           => $journal['currency_name'], | ||||
| @@ -262,7 +263,8 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector = app(GroupCollectorInterface::class); | ||||
|         $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::TRANSFER]) | ||||
|                   ->setSourceAccounts($accounts)->excludeDestinationAccounts($accounts); | ||||
|             ->setSourceAccounts($accounts)->excludeDestinationAccounts($accounts) | ||||
|         ; | ||||
|         if (null !== $categories && $categories->count() > 0) { | ||||
|             $collector->setCategories($categories); | ||||
|         } | ||||
| @@ -270,13 +272,13 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             $collector->setCategories($this->getCategories()); | ||||
|         } | ||||
|         $collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation(); | ||||
|         $journals = $collector->getExtractedJournals(); | ||||
|         $array    = []; | ||||
|         $journals  = $collector->getExtractedJournals(); | ||||
|         $array     = []; | ||||
| 
 | ||||
|         foreach ($journals as $journal) { | ||||
|             $currencyId   = (int) $journal['currency_id']; | ||||
|             $categoryId   = (int) $journal['category_id']; | ||||
|             $categoryName = (string) $journal['category_name']; | ||||
|             $currencyId                                                                        = (int) $journal['currency_id']; | ||||
|             $categoryId                                                                        = (int) $journal['category_id']; | ||||
|             $categoryName                                                                      = (string) $journal['category_name']; | ||||
| 
 | ||||
|             // catch "no category" entries.
 | ||||
|             if (0 === $categoryId) { | ||||
| @@ -284,7 +286,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             } | ||||
| 
 | ||||
|             // info about the currency:
 | ||||
|             $array[$currencyId] ??= [ | ||||
|             $array[$currencyId]                            ??= [ | ||||
|                 'categories'              => [], | ||||
|                 'currency_id'             => (string) $currencyId, | ||||
|                 'currency_name'           => $journal['currency_name'], | ||||
| @@ -325,7 +327,7 @@ 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       = app(GroupCollectorInterface::class); | ||||
|         $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); | ||||
| 
 | ||||
|         // default currency information for native stuff.
 | ||||
| @@ -339,19 +341,19 @@ 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']; | ||||
|             $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); | ||||
| @@ -371,7 +373,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             $array[$currencyId]        ??= [ | ||||
|             $array[$currencyId] ??= [ | ||||
|                 'sum'                     => '0', | ||||
|                 'currency_id'             => (string) $currencyId, | ||||
|                 'currency_name'           => $currencyName, | ||||
| @@ -393,7 +395,8 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector = app(GroupCollectorInterface::class); | ||||
|         $collector->setUser($this->user)->setRange($start, $end) | ||||
|                   ->setTypes([TransactionType::DEPOSIT]); | ||||
|             ->setTypes([TransactionType::DEPOSIT]) | ||||
|         ; | ||||
| 
 | ||||
|         if (null !== $accounts && $accounts->count() > 0) { | ||||
|             $collector->setAccounts($accounts); | ||||
| @@ -402,12 +405,12 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             $categories = $this->getCategories(); | ||||
|         } | ||||
|         $collector->setCategories($categories); | ||||
|         $journals = $collector->getExtractedJournals(); | ||||
|         $array    = []; | ||||
|         $journals  = $collector->getExtractedJournals(); | ||||
|         $array     = []; | ||||
| 
 | ||||
|         foreach ($journals as $journal) { | ||||
|             $currencyId                = (int) $journal['currency_id']; | ||||
|             $array[$currencyId]        ??= [ | ||||
|             $array[$currencyId] ??= [ | ||||
|                 'sum'                     => '0', | ||||
|                 'currency_id'             => (string) $currencyId, | ||||
|                 'currency_name'           => $journal['currency_name'], | ||||
| @@ -429,7 +432,8 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|         /** @var GroupCollectorInterface $collector */ | ||||
|         $collector = app(GroupCollectorInterface::class); | ||||
|         $collector->setUser($this->user)->setRange($start, $end) | ||||
|                   ->setTypes([TransactionType::TRANSFER]); | ||||
|             ->setTypes([TransactionType::TRANSFER]) | ||||
|         ; | ||||
| 
 | ||||
|         if (null !== $accounts && $accounts->count() > 0) { | ||||
|             $collector->setAccounts($accounts); | ||||
| @@ -438,12 +442,12 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|             $categories = $this->getCategories(); | ||||
|         } | ||||
|         $collector->setCategories($categories); | ||||
|         $journals = $collector->getExtractedJournals(); | ||||
|         $array    = []; | ||||
|         $journals  = $collector->getExtractedJournals(); | ||||
|         $array     = []; | ||||
| 
 | ||||
|         foreach ($journals as $journal) { | ||||
|             $currencyId                = (int) $journal['currency_id']; | ||||
|             $array[$currencyId]        ??= [ | ||||
|             $array[$currencyId] ??= [ | ||||
|                 'sum'                     => '0', | ||||
|                 'currency_id'             => (string) $currencyId, | ||||
|                 'currency_name'           => $journal['currency_name'], | ||||
|   | ||||
| @@ -58,12 +58,13 @@ class Amount | ||||
|         $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));
 | ||||
|         // 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));
 | ||||
|             // Log::debug(sprintf('Overruled, amount is now %s', $amount));
 | ||||
|         } | ||||
| 
 | ||||
|         return $amount; | ||||
|     } | ||||
| 
 | ||||
| @@ -73,23 +74,24 @@ class Amount | ||||
|      */ | ||||
|     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'; | ||||
|         $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; | ||||
|         $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. | ||||
| @@ -100,15 +102,15 @@ class Amount | ||||
|      */ | ||||
|     public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string | ||||
|     { | ||||
|         $locale   = app('steam')->getLocale(); | ||||
|         $rounded  = app('steam')->bcround($amount, $decimalPlaces); | ||||
|         $locale  = app('steam')->getLocale(); | ||||
|         $rounded = app('steam')->bcround($amount, $decimalPlaces); | ||||
|         $coloured ??= true; | ||||
| 
 | ||||
|         $fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); | ||||
|         $fmt     = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); | ||||
|         $fmt->setSymbol(\NumberFormatter::CURRENCY_SYMBOL, $symbol); | ||||
|         $fmt->setAttribute(\NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces); | ||||
|         $fmt->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces); | ||||
|         $result = (string) $fmt->format((float) $rounded); // intentional float
 | ||||
|         $result  = (string) $fmt->format((float) $rounded); // intentional float
 | ||||
| 
 | ||||
|         if (true === $coloured) { | ||||
|             if (1 === bccomp($rounded, '0')) { | ||||
| @@ -154,7 +156,7 @@ class Amount | ||||
| 
 | ||||
|     public function getDefaultCurrencyByUserGroup(UserGroup $userGroup): TransactionCurrency | ||||
|     { | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache   = new CacheProperties(); | ||||
|         $cache->addProperty('getDefaultCurrencyByGroup'); | ||||
|         $cache->addProperty($userGroup->id); | ||||
|         if ($cache->has()) { | ||||
| @@ -217,20 +219,20 @@ class Amount | ||||
|     private function getLocaleInfo(): array | ||||
|     { | ||||
|         // get config from preference, not from translation:
 | ||||
|         $locale = app('steam')->getLocale(); | ||||
|         $array  = app('steam')->getLocaleArray($locale); | ||||
|         $locale                    = app('steam')->getLocale(); | ||||
|         $array                     = app('steam')->getLocaleArray($locale); | ||||
| 
 | ||||
|         setlocale(LC_MONETARY, $array); | ||||
|         $info = localeconv(); | ||||
|         $info                      = localeconv(); | ||||
| 
 | ||||
|         // correct variables
 | ||||
|         $info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes'); | ||||
|         $info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes'); | ||||
|         $info['n_cs_precedes']     = $this->getLocaleField($info, 'n_cs_precedes'); | ||||
|         $info['p_cs_precedes']     = $this->getLocaleField($info, 'p_cs_precedes'); | ||||
| 
 | ||||
|         $info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space'); | ||||
|         $info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space'); | ||||
|         $info['n_sep_by_space']    = $this->getLocaleField($info, 'n_sep_by_space'); | ||||
|         $info['p_sep_by_space']    = $this->getLocaleField($info, 'p_sep_by_space'); | ||||
| 
 | ||||
|         $fmt = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); | ||||
|         $fmt                       = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); | ||||
| 
 | ||||
|         $info['mon_decimal_point'] = $fmt->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL); | ||||
|         $info['mon_thousands_sep'] = $fmt->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL); | ||||
| @@ -253,7 +255,7 @@ class Amount | ||||
|     public static function getAmountJsConfig(bool $sepBySpace, int $signPosn, string $sign, bool $csPrecedes): string | ||||
|     { | ||||
|         // negative first:
 | ||||
|         $space = ' '; | ||||
|         $space  = ' '; | ||||
| 
 | ||||
|         // require space between symbol and amount?
 | ||||
|         if (false === $sepBySpace) { | ||||
| @@ -262,11 +264,11 @@ class Amount | ||||
| 
 | ||||
|         // there are five possible positions for the "+" or "-" sign (if it is even used)
 | ||||
|         // pos_a and pos_e could be the ( and ) symbol.
 | ||||
|         $posA = ''; // before everything
 | ||||
|         $posB = ''; // before currency symbol
 | ||||
|         $posC = ''; // after currency symbol
 | ||||
|         $posD = ''; // before amount
 | ||||
|         $posE = ''; // after everything
 | ||||
|         $posA   = ''; // before everything
 | ||||
|         $posB   = ''; // before currency symbol
 | ||||
|         $posC   = ''; // after currency symbol
 | ||||
|         $posD   = ''; // before amount
 | ||||
|         $posE   = ''; // after everything
 | ||||
| 
 | ||||
|         // format would be (currency before amount)
 | ||||
|         // AB%sC_D%vE
 | ||||
| @@ -308,11 +310,11 @@ class Amount | ||||
|         } | ||||
| 
 | ||||
|         // default is amount before currency
 | ||||
|         $format = $posA . $posD . '%v' . $space . $posB . '%s' . $posC . $posE; | ||||
|         $format = $posA.$posD.'%v'.$space.$posB.'%s'.$posC.$posE; | ||||
| 
 | ||||
|         if ($csPrecedes) { | ||||
|             // alternative is currency before amount
 | ||||
|             $format = $posA . $posB . '%s' . $posC . $space . $posD . '%v' . $posE; | ||||
|             $format = $posA.$posB.'%s'.$posC.$space.$posD.'%v'.$posE; | ||||
|         } | ||||
| 
 | ||||
|         return $format; | ||||
|   | ||||
| @@ -48,7 +48,6 @@ class FrontpageChartGenerator | ||||
|     public bool                             $convertToNative = false; | ||||
|     public TransactionCurrency              $default; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * FrontpageChartGenerator constructor. | ||||
|      */ | ||||
| @@ -100,10 +99,12 @@ class FrontpageChartGenerator | ||||
|         if (0 === $limits->count()) { | ||||
|             $result = $this->noBudgetLimits($data, $budget); | ||||
|             Log::debug(sprintf('Now DONE processing budget #%d ("%s")', $budget->id, $budget->name)); | ||||
| 
 | ||||
|             return $result; | ||||
|         } | ||||
|         $result = $this->budgetLimits($data, $budget, $limits); | ||||
|         Log::debug(sprintf('Now DONE processing budget #%d ("%s")', $budget->id, $budget->name)); | ||||
| 
 | ||||
|         return $result; | ||||
|     } | ||||
| 
 | ||||
| @@ -132,6 +133,7 @@ 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); | ||||
| @@ -156,8 +158,9 @@ class FrontpageChartGenerator | ||||
|             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); | ||||
|         $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
 | ||||
| @@ -182,7 +185,7 @@ class FrontpageChartGenerator | ||||
|      */ | ||||
|     private function processRow(array $data, Budget $budget, BudgetLimit $limit, array $entry): array | ||||
|     { | ||||
|         $title = sprintf('%s (%s)', $budget->name, $entry['currency_name']); | ||||
|         $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( | ||||
| @@ -193,22 +196,22 @@ class FrontpageChartGenerator | ||||
|                 $limit->end_date->isoFormat($this->monthAndDayFormat) | ||||
|             ); | ||||
|         } | ||||
|         $useNative = $this->convertToNative && $this->default->id !== $limit->transaction_currency_id; | ||||
|         $amount = $limit->amount; | ||||
|         if($useNative) { | ||||
|         $useNative                  = $this->convertToNative && $this->default->id !== $limit->transaction_currency_id; | ||||
|         $amount                     = $limit->amount; | ||||
|         if ($useNative) { | ||||
|             $amount = $limit->native_amount; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         $sumSpent = bcmul($entry['sum'], '-1'); // spent
 | ||||
|         $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
 | ||||
|         $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])); | ||||
|   | ||||
| @@ -67,11 +67,11 @@ class FrontpageChartGenerator | ||||
|     public function generate(): array | ||||
|     { | ||||
|         Log::debug('Now in generate()'); | ||||
|         $categories = $this->repository->getCategories(); | ||||
|         $accounts   = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]); | ||||
|         $categories   = $this->repository->getCategories(); | ||||
|         $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 = []; | ||||
|         $collection   = []; | ||||
| 
 | ||||
|         /** @var Category $category */ | ||||
|         foreach ($categories as $category) { | ||||
| @@ -82,10 +82,10 @@ class FrontpageChartGenerator | ||||
|         // collect for no-category:
 | ||||
|         $collection[] = $this->collectNoCatExpenses($accounts); | ||||
| 
 | ||||
|         $tempData = array_merge(...$collection); | ||||
|         $tempData     = array_merge(...$collection); | ||||
| 
 | ||||
|         // sort temp array by amount.
 | ||||
|         $amounts = array_column($tempData, 'sum_float'); | ||||
|         $amounts      = array_column($tempData, 'sum_float'); | ||||
|         array_multisort($amounts, SORT_ASC, $tempData); | ||||
| 
 | ||||
|         $currencyData = $this->createCurrencyGroups($tempData); | ||||
|   | ||||
| @@ -30,7 +30,6 @@ use FireflyIII\Models\CurrencyExchangeRate; | ||||
| use FireflyIII\Models\TransactionCurrency; | ||||
| use FireflyIII\Models\UserGroup; | ||||
| use FireflyIII\Support\CacheProperties; | ||||
| use FireflyIII\User; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| 
 | ||||
| @@ -101,8 +100,8 @@ class ExchangeRateConverter | ||||
|      */ | ||||
|     private function getRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string | ||||
|     { | ||||
|         $key = $this->getCacheKey($from, $to, $date); | ||||
|         $res = Cache::get($key, null); | ||||
|         $key    = $this->getCacheKey($from, $to, $date); | ||||
|         $res    = Cache::get($key, null); | ||||
| 
 | ||||
|         // find in cache
 | ||||
|         if (null !== $res) { | ||||
| @@ -112,7 +111,7 @@ class ExchangeRateConverter | ||||
|         } | ||||
| 
 | ||||
|         // find in database
 | ||||
|         $rate = $this->getFromDB($from->id, $to->id, $date->format('Y-m-d')); | ||||
|         $rate   = $this->getFromDB($from->id, $to->id, $date->format('Y-m-d')); | ||||
|         if (null !== $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'))); | ||||
| @@ -121,7 +120,7 @@ class ExchangeRateConverter | ||||
|         } | ||||
| 
 | ||||
|         // find reverse in database
 | ||||
|         $rate = $this->getFromDB($to->id, $from->id, $date->format('Y-m-d')); | ||||
|         $rate   = $this->getFromDB($to->id, $from->id, $date->format('Y-m-d')); | ||||
|         if (null !== $rate) { | ||||
|             $rate = bcdiv('1', $rate); | ||||
|             Cache::forever($key, $rate); | ||||
| @@ -159,7 +158,7 @@ class ExchangeRateConverter | ||||
|         if ($from === $to) { | ||||
|             return '1'; | ||||
|         } | ||||
|         $key = sprintf('cer-%d-%d-%s', $from, $to, $date); | ||||
|         $key          = sprintf('cer-%d-%d-%s', $from, $to, $date); | ||||
| 
 | ||||
|         // perhaps the rate has been cached during this particular run
 | ||||
|         $preparedRate = $this->prepared[$date][$from][$to] ?? null; | ||||
| @@ -169,7 +168,7 @@ class ExchangeRateConverter | ||||
|             return $preparedRate; | ||||
|         } | ||||
| 
 | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache        = new CacheProperties(); | ||||
|         $cache->addProperty($key); | ||||
|         if ($cache->has()) { | ||||
|             $rate = $cache->get(); | ||||
| @@ -182,14 +181,15 @@ class ExchangeRateConverter | ||||
|         } | ||||
| 
 | ||||
|         /** @var null|CurrencyExchangeRate $result */ | ||||
|         $result = $this->userGroup->currencyExchangeRates() | ||||
|                         ->where('from_currency_id', $from) | ||||
|                         ->where('to_currency_id', $to) | ||||
|                         ->where('date', '<=', $date) | ||||
|                         ->orderBy('date', 'DESC') | ||||
|                         ->first(); | ||||
|         $result       = $this->userGroup->currencyExchangeRates() | ||||
|             ->where('from_currency_id', $from) | ||||
|             ->where('to_currency_id', $to) | ||||
|             ->where('date', '<=', $date) | ||||
|             ->orderBy('date', 'DESC') | ||||
|             ->first() | ||||
|         ; | ||||
|         ++$this->queryCount; | ||||
|         $rate = (string) $result?->rate; | ||||
|         $rate         = (string) $result?->rate; | ||||
| 
 | ||||
|         if ('' === $rate) { | ||||
|             app('log')->debug(sprintf('ExchangeRateConverter: Found no rate for #%d->#%d (%s) in the DB.', $from, $to, $date)); | ||||
| @@ -229,13 +229,13 @@ class ExchangeRateConverter | ||||
|         if ($euroId === $currency->id) { | ||||
|             return '1'; | ||||
|         } | ||||
|         $rate = $this->getFromDB($currency->id, $euroId, $date->format('Y-m-d')); | ||||
|         $rate   = $this->getFromDB($currency->id, $euroId, $date->format('Y-m-d')); | ||||
| 
 | ||||
|         if (null !== $rate) { | ||||
|             //            app('log')->debug(sprintf('Rate for %s to EUR is %s.', $currency->code, $rate));
 | ||||
|             return $rate; | ||||
|         } | ||||
|         $rate = $this->getFromDB($euroId, $currency->id, $date->format('Y-m-d')); | ||||
|         $rate   = $this->getFromDB($euroId, $currency->id, $date->format('Y-m-d')); | ||||
|         if (null !== $rate) { | ||||
|             return bcdiv('1', $rate); | ||||
|             //            app('log')->debug(sprintf('Inverted rate for %s to EUR is %s.', $currency->code, $rate));
 | ||||
| @@ -264,7 +264,7 @@ class ExchangeRateConverter | ||||
|         if ($cache->has()) { | ||||
|             return (int) $cache->get(); | ||||
|         } | ||||
|         $euro = TransactionCurrency::whereCode('EUR')->first(); | ||||
|         $euro  = TransactionCurrency::whereCode('EUR')->first(); | ||||
|         ++$this->queryCount; | ||||
|         if (null === $euro) { | ||||
|             throw new FireflyException('Cannot find EUR in system, cannot do currency conversion.'); | ||||
| @@ -286,13 +286,14 @@ 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 = $this->userGroup | ||||
|                      ->currencyExchangeRates() | ||||
|                      ->where('from_currency_id', $from->id) | ||||
|                      ->where('to_currency_id', $to->id) | ||||
|                      ->where('date', '<=', $end->format('Y-m-d')) | ||||
|                      ->where('date', '>=', $start->format('Y-m-d')) | ||||
|                      ->orderBy('date', 'DESC')->get(); | ||||
|         $set              = $this->userGroup | ||||
|             ->currencyExchangeRates() | ||||
|             ->where('from_currency_id', $from->id) | ||||
|             ->where('to_currency_id', $to->id) | ||||
|             ->where('date', '<=', $end->format('Y-m-d')) | ||||
|             ->where('date', '>=', $start->format('Y-m-d')) | ||||
|             ->orderBy('date', 'DESC')->get() | ||||
|         ; | ||||
|         ++$this->queryCount; | ||||
|         if (0 === $set->count()) { | ||||
|             Log::debug('No prepared rates found in this period, use the fallback'); | ||||
| @@ -306,10 +307,10 @@ class ExchangeRateConverter | ||||
|         $this->isPrepared = true; | ||||
| 
 | ||||
|         // so there is a fallback just in case. Now loop the set of rates we DO have.
 | ||||
|         $temp  = []; | ||||
|         $count = 0; | ||||
|         $temp             = []; | ||||
|         $count            = 0; | ||||
|         foreach ($set as $rate) { | ||||
|             $date        = $rate->date->format('Y-m-d'); | ||||
|             $date = $rate->date->format('Y-m-d'); | ||||
|             $temp[$date] ??= [ | ||||
|                 $from->id => [ | ||||
|                     $to->id => $rate->rate, | ||||
| @@ -318,11 +319,11 @@ class ExchangeRateConverter | ||||
|             ++$count; | ||||
|         } | ||||
|         Log::debug(sprintf('Found %d rates in this period.', $count)); | ||||
|         $currentStart = clone $start; | ||||
|         $currentStart     = clone $start; | ||||
|         while ($currentStart->lte($end)) { | ||||
|             $currentDate                  = $currentStart->format('Y-m-d'); | ||||
|             $currentDate = $currentStart->format('Y-m-d'); | ||||
|             $this->prepared[$currentDate] ??= []; | ||||
|             $fallback                     = $temp[$currentDate][$from->id][$to->id] ?? $this->fallback[$from->id][$to->id] ?? '0'; | ||||
|             $fallback    = $temp[$currentDate][$from->id][$to->id] ?? $this->fallback[$from->id][$to->id] ?? '0'; | ||||
|             if (0 === count($this->prepared[$currentDate]) && 0 !== bccomp('0', $fallback)) { | ||||
|                 // fill from temp or fallback or from temp (see before)
 | ||||
|                 $this->prepared[$currentDate][$from->id][$to->id] = $fallback; | ||||
|   | ||||
| @@ -39,6 +39,7 @@ trait BasicDataSupport | ||||
|     protected function isInArray(array $array, int $entryId) | ||||
|     { | ||||
|         $key = $this->convertToNative ? 'native_balance' : 'balance'; | ||||
| 
 | ||||
|         return $array[$entryId][$key] ?? '0'; | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -54,7 +54,7 @@ trait ChartGeneration | ||||
|         $cache->addProperty($accounts); | ||||
|         $cache->addProperty($convertToNative); | ||||
|         if ($cache->has()) { | ||||
|              //return $cache->get();
 | ||||
|             // return $cache->get();
 | ||||
|         } | ||||
|         app('log')->debug('Regenerate chart.account.account-balance-chart from scratch.'); | ||||
|         $locale          = app('steam')->getLocale(); | ||||
| @@ -70,10 +70,10 @@ trait ChartGeneration | ||||
| 
 | ||||
|         /** @var Account $account */ | ||||
|         foreach ($accounts as $account) { | ||||
|             $currency      = $accountRepos->getAccountCurrency($account) ?? $default; | ||||
|             $useNative     = $convertToNative && $default->id !== $currency->id; | ||||
|             $field =$useNative ? 'native_balance' : 'balance'; | ||||
|             $currency      = $useNative ? $default : $currency; | ||||
|             $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, | ||||
|   | ||||
| @@ -38,8 +38,8 @@ class Steam | ||||
| { | ||||
|     public function getAccountCurrency(Account $account): ?TransactionCurrency | ||||
|     { | ||||
|         $type = $account->accountType->type; | ||||
|         $list = config('firefly.valid_currency_account_types'); | ||||
|         $type   = $account->accountType->type; | ||||
|         $list   = config('firefly.valid_currency_account_types'); | ||||
| 
 | ||||
|         // return null if not in this list.
 | ||||
|         if (!in_array($type, $list, true)) { | ||||
| @@ -62,7 +62,7 @@ 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('Add value from "%s": %s', $key, $value));
 | ||||
|         } | ||||
|         Log::debug(sprintf('Sum of "%s"-fields is %s', $key, $sum)); | ||||
| 
 | ||||
| @@ -76,7 +76,7 @@ class Steam | ||||
|         $end->addDay()->endOfDay(); | ||||
| 
 | ||||
|         // set up cache
 | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache                = new CacheProperties(); | ||||
|         $cache->addProperty($account->id); | ||||
|         $cache->addProperty('final-balance-in-range'); | ||||
|         $cache->addProperty($start); | ||||
| @@ -85,51 +85,52 @@ class Steam | ||||
|             // return $cache->get();
 | ||||
|         } | ||||
| 
 | ||||
|         $balances                             = []; | ||||
|         $formatted                            = $start->format('Y-m-d'); | ||||
|         $startBalance                         = $this->finalAccountBalance($account, $start); | ||||
|         $defaultCurrency                      = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); | ||||
|         $currency                             = $this->getAccountCurrency($account) ?? $defaultCurrency; | ||||
|         $currencies                           = [ | ||||
|         $balances             = []; | ||||
|         $formatted            = $start->format('Y-m-d'); | ||||
|         $startBalance         = $this->finalAccountBalance($account, $start); | ||||
|         $defaultCurrency      = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); | ||||
|         $currency             = $this->getAccountCurrency($account) ?? $defaultCurrency; | ||||
|         $currencies           = [ | ||||
|             $currency->id        => $currency, | ||||
|             $defaultCurrency->id => $defaultCurrency, | ||||
|         ]; | ||||
|         $startBalance[$defaultCurrency->code] ??= '0'; | ||||
|         $startBalance[$currency->code]        ??= '0'; | ||||
|         $balances[$formatted]                 = $startBalance; | ||||
|         $balances[$formatted] = $startBalance; | ||||
| 
 | ||||
| 
 | ||||
|         // sums up the balance changes per day, for foreign, native and normal amounts.
 | ||||
|         $set = $account->transactions() | ||||
|                        ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') | ||||
|                        ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s')) | ||||
|                        ->where('transaction_journals.date', '<=', $end->format('Y-m-d  H:i:s')) | ||||
|                        ->groupBy('transaction_journals.date') | ||||
|                        ->groupBy('transactions.transaction_currency_id') | ||||
|                        ->groupBy('transactions.foreign_currency_id') | ||||
|                        ->orderBy('transaction_journals.date', 'ASC') | ||||
|                        ->whereNull('transaction_journals.deleted_at') | ||||
|                        ->get( | ||||
|                            [ // @phpstan-ignore-line
 | ||||
|                              'transaction_journals.date', | ||||
|                              'transactions.transaction_currency_id', | ||||
|                              \DB::raw('SUM(transactions.amount) AS modified'), | ||||
|                              'transactions.foreign_currency_id', | ||||
|                              \DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'), | ||||
|                              \DB::raw('SUM(transactions.native_amount) AS modified_native'), | ||||
|                            ] | ||||
|                        ); | ||||
|         $set                  = $account->transactions() | ||||
|             ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') | ||||
|             ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s')) | ||||
|             ->where('transaction_journals.date', '<=', $end->format('Y-m-d  H:i:s')) | ||||
|             ->groupBy('transaction_journals.date') | ||||
|             ->groupBy('transactions.transaction_currency_id') | ||||
|             ->groupBy('transactions.foreign_currency_id') | ||||
|             ->orderBy('transaction_journals.date', 'ASC') | ||||
|             ->whereNull('transaction_journals.deleted_at') | ||||
|             ->get( | ||||
|                 [ // @phpstan-ignore-line
 | ||||
|                     'transaction_journals.date', | ||||
|                     'transactions.transaction_currency_id', | ||||
|                     \DB::raw('SUM(transactions.amount) AS modified'), | ||||
|                     'transactions.foreign_currency_id', | ||||
|                     \DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'), | ||||
|                     \DB::raw('SUM(transactions.native_amount) AS modified_native'), | ||||
|                 ] | ||||
|             ) | ||||
|         ; | ||||
| 
 | ||||
|         $currentBalance = $startBalance; | ||||
|         $currentBalance       = $startBalance; | ||||
| 
 | ||||
|         /** @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); | ||||
|             $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) { | ||||
| @@ -138,7 +139,7 @@ class Steam | ||||
|             } | ||||
| 
 | ||||
|             // always add the native balance, even if it ends up at zero.
 | ||||
|             $currentBalance['native_balance'] = bcadd($currentBalance['native_balance'], $nativeModified); | ||||
|             $currentBalance['native_balance']   = bcadd($currentBalance['native_balance'], $nativeModified); | ||||
| 
 | ||||
|             // add modified foreign to the array
 | ||||
|             if (null !== $entry->foreign_currency_id) { | ||||
| @@ -184,10 +185,10 @@ class Steam | ||||
|         // Log::debug(sprintf('Trying bcround("%s",%d)', $number, $precision));
 | ||||
|         if (str_contains($number, '.')) { | ||||
|             if ('-' !== $number[0]) { | ||||
|                 return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision); | ||||
|                 return bcadd($number, '0.'.str_repeat('0', $precision).'5', $precision); | ||||
|             } | ||||
| 
 | ||||
|             return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision); | ||||
|             return bcsub($number, '0.'.str_repeat('0', $precision).'5', $precision); | ||||
|         } | ||||
| 
 | ||||
|         return $number; | ||||
| @@ -272,7 +273,6 @@ class Steam | ||||
|      * 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 | ||||
|     { | ||||
| @@ -282,67 +282,71 @@ class Steam | ||||
|         $accountCurrency = $this->getAccountCurrency($account); | ||||
|         $hasCurrency     = null !== $accountCurrency; | ||||
|         $currency        = $hasCurrency ? $accountCurrency : $native; | ||||
|         $return = []; | ||||
|         $return          = []; | ||||
| 
 | ||||
|         // first, the "balance", as described earlier.
 | ||||
|         if ($convertToNative) { | ||||
|             // normal balance
 | ||||
|             $return['balance'] = (string) $account->transactions() | ||||
|                                                   ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|                                                   ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|                                                   ->where('transactions.transaction_currency_id', $native->id) | ||||
|                                                   ->sum('transactions.amount'); | ||||
|             $return['balance']        = (string) $account->transactions() | ||||
|                 ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|                 ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|                 ->where('transactions.transaction_currency_id', $native->id) | ||||
|                 ->sum('transactions.amount') | ||||
|             ; | ||||
|             // plus virtual balance, if the account has a virtual_balance in the native currency
 | ||||
|             if($native->id === $accountCurrency?->id) { | ||||
|             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'); | ||||
|                 ->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'); | ||||
|                 ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|                 ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|                 ->whereNot('transactions.transaction_currency_id', $native->id) | ||||
|                 ->where('transactions.foreign_currency_id', $native->id) | ||||
|                 ->sum('transactions.foreign_amount') | ||||
|             ; | ||||
|             $return['native_balance'] = bcadd($return['native_balance'], $sum); | ||||
| 
 | ||||
|             Log::debug(sprintf('Foreign amount transactions add (%s only) %s, total native_balance is now %s', $native->code, $sum, $return['native_balance'])); | ||||
|         } | ||||
| 
 | ||||
|         // balance(s) in other (all) currencies.
 | ||||
|         $array  = $account->transactions() | ||||
|                           ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|                           ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') | ||||
|                           ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|                           ->get(['transaction_currencies.code', 'transactions.amount'])->toArray(); | ||||
|         $others = $this->groupAndSumTransactions($array, 'code', 'amount'); | ||||
|         $array           = $account->transactions() | ||||
|             ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|             ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') | ||||
|             ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|             ->get(['transaction_currencies.code', 'transactions.amount'])->toArray() | ||||
|         ; | ||||
|         $others          = $this->groupAndSumTransactions($array, 'code', 'amount'); | ||||
|         Log::debug('All balances are (joined)', $others); | ||||
|         // if the account has no own currency preference, drop balance in favor of native balance
 | ||||
|         if ($hasCurrency && !$convertToNative) { | ||||
|             $return['balance'] = $others[$currency->code] ?? '0'; | ||||
|             $return['balance']        = $others[$currency->code] ?? '0'; | ||||
|             $return['native_balance'] = $others[$currency->code] ?? '0'; | ||||
|             Log::debug(sprintf('Set balance + native_balance to %s', $return['balance'])); | ||||
|         } | ||||
| 
 | ||||
|         // if the currency is the same as the native currency, set the native_balance to the balance for consistency.
 | ||||
| //        if($currency->id === $native->id) {
 | ||||
| //            $return['native_balance'] = $return['balance'];
 | ||||
| //        }
 | ||||
|         //        if($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']); | ||||
|             $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']); | ||||
| @@ -388,15 +392,15 @@ class Steam | ||||
|     { | ||||
|         $list = []; | ||||
| 
 | ||||
|         $set = auth()->user()->transactions() | ||||
|                      ->whereIn('transactions.account_id', $accounts) | ||||
|                      ->groupBy(['transactions.account_id', 'transaction_journals.user_id']) | ||||
|                      ->get(['transactions.account_id', \DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line
 | ||||
|         $set  = auth()->user()->transactions() | ||||
|             ->whereIn('transactions.account_id', $accounts) | ||||
|             ->groupBy(['transactions.account_id', 'transaction_journals.user_id']) | ||||
|             ->get(['transactions.account_id', \DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line
 | ||||
|         ; | ||||
| 
 | ||||
|         /** @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; | ||||
|         } | ||||
| @@ -471,9 +475,9 @@ class Steam | ||||
|     public function getSafeUrl(string $unknownUrl, string $safeUrl): string | ||||
|     { | ||||
|         // Log::debug(sprintf('getSafeUrl(%s, %s)', $unknownUrl, $safeUrl));
 | ||||
|         $returnUrl   = $safeUrl; | ||||
|         $unknownHost = parse_url($unknownUrl, PHP_URL_HOST); | ||||
|         $safeHost    = parse_url($safeUrl, PHP_URL_HOST); | ||||
|         $returnUrl      = $safeUrl; | ||||
|         $unknownHost    = parse_url($unknownUrl, PHP_URL_HOST); | ||||
|         $safeHost       = parse_url($safeUrl, PHP_URL_HOST); | ||||
| 
 | ||||
|         if (null !== $unknownHost && $unknownHost === $safeHost) { | ||||
|             $returnUrl = $unknownUrl; | ||||
| @@ -510,7 +514,7 @@ class Steam | ||||
|      */ | ||||
|     public function floatalize(string $value): string | ||||
|     { | ||||
|         $value = strtoupper($value); | ||||
|         $value  = strtoupper($value); | ||||
|         if (!str_contains($value, 'E')) { | ||||
|             return $value; | ||||
|         } | ||||
|   | ||||
| @@ -75,7 +75,7 @@ class AmountFormat extends AbstractExtension | ||||
|             $this->formatAmountByAccount(), | ||||
|             $this->formatAmountBySymbol(), | ||||
|             $this->formatAmountByCurrency(), | ||||
|             $this->formatAmountByCode() | ||||
|             $this->formatAmountByCode(), | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
| @@ -111,6 +111,7 @@ class AmountFormat extends AbstractExtension | ||||
|             'formatAmountByCode', | ||||
|             static function (string $amount, string $code, ?bool $coloured = null): string { | ||||
|                 $coloured ??= true; | ||||
| 
 | ||||
|                 /** @var TransactionCurrency $currency */ | ||||
|                 $currency = TransactionCurrency::whereCode($code)->first(); | ||||
| 
 | ||||
| @@ -128,8 +129,8 @@ class AmountFormat extends AbstractExtension | ||||
|         return new TwigFunction( | ||||
|             'formatAmountBySymbol', | ||||
|             static function (string $amount, string $symbol, ?int $decimalPlaces = null, ?bool $coloured = null): string { | ||||
|                 $decimalPlaces            ??= 2; | ||||
|                 $coloured                 ??= true; | ||||
|                 $decimalPlaces ??= 2; | ||||
|                 $coloured      ??= true; | ||||
|                 $currency                 = new TransactionCurrency(); | ||||
|                 $currency->symbol         = $symbol; | ||||
|                 $currency->decimal_places = $decimalPlaces; | ||||
|   | ||||
| @@ -69,9 +69,9 @@ class General extends AbstractExtension | ||||
|                 $date            = session('end', today(config('app.timezone'))->endOfMonth()); | ||||
|                 $info            = Steam::finalAccountBalance($account, $date); | ||||
|                 $currency        = Steam::getAccountCurrency($account); | ||||
|                 $default          = Amount::getDefaultCurrency(); | ||||
|                 $default         = Amount::getDefaultCurrency(); | ||||
|                 $convertToNative = app('preferences')->get('convert_to_native', false)->data; | ||||
|                 $useNative     = $convertToNative && $default->id !== $currency->id; | ||||
|                 $useNative       = $convertToNative && $default->id !== $currency->id; | ||||
|                 $strings         = []; | ||||
|                 foreach ($info as $key => $balance) { | ||||
|                     if ('balance' === $key) { | ||||
| @@ -79,6 +79,7 @@ class General extends AbstractExtension | ||||
|                         if (!$useNative) { | ||||
|                             $strings[] = app('amount')->formatAnything($currency, $balance, false); | ||||
|                         } | ||||
| 
 | ||||
|                         continue; | ||||
|                     } | ||||
|                     if ('native_balance' === $key) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user