mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 02:36:28 +00:00 
			
		
		
		
	Frontpage seems to be multi currency aware.
This commit is contained in:
		| @@ -322,6 +322,7 @@ 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')); | ||||
| @@ -369,6 +370,7 @@ class BasicController extends Controller | ||||
|                 'sub_title'               => '', | ||||
|             ]; | ||||
|         } | ||||
|         Log::debug('End of getNetWorthInfo'); | ||||
| 
 | ||||
|         return $return; | ||||
|     } | ||||
|   | ||||
| @@ -108,6 +108,7 @@ class RecalculateNativeAmounts extends Command | ||||
|     private function recalculatePiggyBanks(UserGroup $userGroup, TransactionCurrency $currency): void | ||||
|     { | ||||
|         $converter  = new ExchangeRateConverter(); | ||||
|         $converter->setUserGroup($userGroup); | ||||
|         $converter->setIgnoreSettings(true); | ||||
|         $repository = app(PiggyBankRepositoryInterface::class); | ||||
|         $repository->setUserGroup($userGroup); | ||||
|   | ||||
| @@ -48,6 +48,7 @@ class AvailableBudgetObserver | ||||
|         $availableBudget->native_amount = null; | ||||
|         if ($availableBudget->transactionCurrency->id !== $userCurrency->id) { | ||||
|             $converter                      = new ExchangeRateConverter(); | ||||
|             $converter->setUserGroup($availableBudget->user->userGroup); | ||||
|             $converter->setIgnoreSettings(true); | ||||
|             $availableBudget->native_amount = $converter->convert($availableBudget->transactionCurrency, $userCurrency, today(), $availableBudget->amount); | ||||
|         } | ||||
|   | ||||
| @@ -73,12 +73,14 @@ class TransactionObserver | ||||
|         // first normal amount
 | ||||
|         if ($transaction->transactionCurrency->id !== $userCurrency->id && (null === $transaction->foreign_currency_id || (null !== $transaction->foreign_currency_id && $transaction->foreign_currency_id !== $userCurrency->id))) { | ||||
|             $converter                  = new ExchangeRateConverter(); | ||||
|             $converter->setUserGroup($transaction->transactionJournal->user->userGroup); | ||||
|             $converter->setIgnoreSettings(true); | ||||
|             $transaction->native_amount = $converter->convert($transaction->transactionCurrency, $userCurrency, $transaction->transactionJournal->date, $transaction->amount); | ||||
|         } | ||||
|         // then foreign amount
 | ||||
|         if ($transaction->foreignCurrency?->id !== $userCurrency->id && null !== $transaction->foreign_amount && null !== $transaction->foreignCurrency) { | ||||
|             $converter                          = new ExchangeRateConverter(); | ||||
|             $converter->setUserGroup($transaction->transactionJournal->user->userGroup); | ||||
|             $converter->setIgnoreSettings(true); | ||||
|             $transaction->native_foreign_amount = $converter->convert($transaction->foreignCurrency, $userCurrency, $transaction->transactionJournal->date, $transaction->foreign_amount); | ||||
|         } | ||||
|   | ||||
| @@ -874,6 +874,16 @@ class GroupCollector implements GroupCollectorInterface | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Limit results to a specific currency, only normal one. | ||||
|      */ | ||||
|     public function setNormalCurrency(TransactionCurrency $currency): GroupCollectorInterface | ||||
|     { | ||||
|         $this->query->where('source.transaction_currency_id', $currency->id); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function setEndRow(int $endRow): self | ||||
|     { | ||||
|         $this->endRow = $endRow; | ||||
|   | ||||
| @@ -457,6 +457,11 @@ interface GroupCollectorInterface | ||||
|      */ | ||||
|     public function setCurrency(TransactionCurrency $currency): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Limit results to a specific currency, either foreign or normal one. | ||||
|      */ | ||||
|     public function setNormalCurrency(TransactionCurrency $currency): self; | ||||
| 
 | ||||
|     /** | ||||
|      * Set destination accounts. | ||||
|      */ | ||||
|   | ||||
| @@ -83,6 +83,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($convertToNative); | ||||
|         $cache->addProperty('chart.account.expense-accounts'); | ||||
|         if ($cache->has()) { | ||||
|             // return response()->json($cache->get());
 | ||||
|              // return response()->json($cache->get());
 | ||||
|         } | ||||
|         $start->subDay(); | ||||
| 
 | ||||
| @@ -113,7 +114,6 @@ class AccountController extends Controller | ||||
|         $startBalances = app('steam')->finalAccountsBalance($accounts, $start); | ||||
|         $endBalances   = app('steam')->finalAccountsBalance($accounts, $end); | ||||
| 
 | ||||
|         // loop the end balances. This is an array for each account ($expenses)
 | ||||
|         // 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)); | ||||
| @@ -518,11 +518,13 @@ class AccountController extends Controller | ||||
|         /** @var Carbon $end */ | ||||
|         $end           = clone session('end', today(config('app.timezone'))->endOfMonth()); | ||||
|         $cache         = new CacheProperties(); | ||||
|         $convertToNative = app('preferences')->get('convert_to_native', false)->data; | ||||
|         $cache->addProperty($start); | ||||
|         $cache->addProperty($end); | ||||
|         $cache->addProperty($convertToNative); | ||||
|         $cache->addProperty('chart.account.revenue-accounts'); | ||||
|         if ($cache->has()) { | ||||
|             return response()->json($cache->get()); | ||||
|             // return response()->json($cache->get());
 | ||||
|         } | ||||
|         $start->subDay(); | ||||
| 
 | ||||
| @@ -530,9 +532,10 @@ class AccountController extends Controller | ||||
|         $currencies    = []; | ||||
|         $chartData     = []; | ||||
|         $tempData      = []; | ||||
|         $default       = Amount::getDefaultCurrency(); | ||||
| 
 | ||||
|         // grab all accounts and names
 | ||||
|         $accounts      = $this->accountRepository->getAccountsByType([AccountType::REVENUE]); | ||||
|         $accounts      = $this->accountRepository->getAccountsByType([AccountTypeEnum::REVENUE->value]); | ||||
|         $accountNames  = $this->extractNames($accounts); | ||||
| 
 | ||||
|         // grab all balances
 | ||||
| @@ -540,33 +543,49 @@ class AccountController extends Controller | ||||
|         $endBalances   = app('steam')->finalAccountsBalance($accounts, $end); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         // loop the end balances. This is an array for each account ($expenses)
 | ||||
|         foreach ($endBalances as $accountId => $expenses) { | ||||
|             $accountId = (int) $accountId; | ||||
|             // loop each expense entry (each entry can be a different currency).
 | ||||
|             foreach ($expenses as $currencyCode => $endAmount) { | ||||
|                 if (3 !== strlen($currencyCode)) { | ||||
| // loop the accounts, then check for balance and currency info.
 | ||||
|         foreach($accounts as $account) { | ||||
|             Log::debug(sprintf('Now in account #%d ("%s")', $account->id, $account->name)); | ||||
|             $expenses = $endBalances[$account->id] ?? false; | ||||
|             if(false === $expenses) { | ||||
|                 Log::error(sprintf('Found no end balance for account #%d',$account->id)); | ||||
|                 continue; | ||||
|             } | ||||
|             /** | ||||
|              * @var string $key | ||||
|              * @var string $endBalance | ||||
|              */ | ||||
|             foreach ($expenses as $key => $endBalance) { | ||||
|                 if(!$convertToNative && 'native_balance' === $key) { | ||||
|                     Log::debug(sprintf('[a] Will skip expense array "%s"', $key)); | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 if($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 = $convertToNative ? $default->code: $key; | ||||
|                 Log::debug(sprintf('Search code is %s', $searchCode)); | ||||
|                 // see if there is an accompanying start amount.
 | ||||
|                 // grab the difference and find the currency.
 | ||||
|                 $startAmount = (string) ($startBalances[$accountId][$currencyCode] ?? '0'); | ||||
|                 $diff        = bcsub((string) $endAmount, $startAmount); | ||||
|                 $currencies[$currencyCode] ??= $this->currencyRepository->findByCode($currencyCode); | ||||
|                 $startBalance = ($startBalances[$account->id][$key] ?? '0'); | ||||
|                 Log::debug(sprintf('Start balance is %s', $startBalance)); | ||||
|                 $diff        = bcsub($endBalance, $startBalance); | ||||
|                 $currencies[$searchCode] ??= $this->currencyRepository->findByCode($searchCode); | ||||
|                 if (0 !== bccomp($diff, '0')) { | ||||
|                     // store the values in a temporary array.
 | ||||
|                     $tempData[] = [ | ||||
|                         'name'        => $accountNames[$accountId], | ||||
|                         'name'        => $accountNames[$account->id], | ||||
|                         'difference'  => $diff, | ||||
|                         'diff_float'  => (float) $diff, // intentional float
 | ||||
|                         'currency_id' => $currencies[$currencyCode]->id, | ||||
|                         'currency_id' => $currencies[$searchCode]->id, | ||||
|                     ]; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         // recreate currencies, but on ID instead of code.
 | ||||
|         $newCurrencies = []; | ||||
|         foreach ($currencies as $currency) { | ||||
|   | ||||
| @@ -563,11 +563,12 @@ class BillRepository implements BillRepositoryInterface | ||||
| 
 | ||||
|             $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'); | ||||
|                 Log::debug(sprintf('Amount to pay is %s %s (%d times)', $currency->code, $average, $total)); | ||||
|                 $return[$currency->id]        ??= [ | ||||
|                     'id'             => (string) $currency->id, | ||||
|                     'name'           => $currency->name, | ||||
|   | ||||
| @@ -25,6 +25,7 @@ declare(strict_types=1); | ||||
| namespace FireflyIII\Repositories\Budget; | ||||
| 
 | ||||
| use Carbon\Carbon; | ||||
| use FireflyIII\Enums\TransactionTypeEnum; | ||||
| use FireflyIII\Helpers\Collector\GroupCollectorInterface; | ||||
| use FireflyIII\Models\Account; | ||||
| use FireflyIII\Models\Budget; | ||||
| @@ -239,7 +240,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|         $collector->setUser($this->user) | ||||
|                   ->setRange($start, $end) | ||||
|             // ->excludeDestinationAccounts($selection)
 | ||||
|                   ->setTypes([TransactionType::WITHDRAWAL]); | ||||
|                   ->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); | ||||
| 
 | ||||
|         if (null !== $accounts) { | ||||
|             $collector->setAccounts($accounts); | ||||
| @@ -249,26 +250,29 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|         } | ||||
|         if (null !== $currency) { | ||||
|             Log::debug(sprintf('Limit to currency %s', $currency->code)); | ||||
|             $collector->setCurrency($currency); | ||||
|             $collector->setNormalCurrency($currency); | ||||
|         } | ||||
|         $collector->setBudgets($budgets); | ||||
|         $journals = $collector->getExtractedJournals(); | ||||
| 
 | ||||
|         // same but for transactions in the foreign currency:
 | ||||
|         if (null !== $currency) { | ||||
|             // app('log')->debug(sprintf('Currency is "%s".', $currency->name));
 | ||||
|             /** @var GroupCollectorInterface $collector */ | ||||
|             $collector = app(GroupCollectorInterface::class); | ||||
|             $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setForeignCurrency($currency)->setBudgets($budgets); | ||||
|             Log::debug('STOP looking for transactions in the foreign currency.'); | ||||
| 
 | ||||
|             if (null !== $accounts) { | ||||
|                 $collector->setAccounts($accounts); | ||||
|             } | ||||
|             $result = $collector->getExtractedJournals(); | ||||
|             // app('log')->debug(sprintf('Found %d journals with currency %s.', count($result), $currency->code));
 | ||||
|             // do not use array_merge because you want keys to overwrite (otherwise you get double results):
 | ||||
|             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 = []; | ||||
| 
 | ||||
| @@ -289,6 +293,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|                 $useNative = $default->id !== (int) $journal['currency_id']; | ||||
|                 $amount                = Amount::getAmountFromJournal($journal); | ||||
|                 if($useNative) { | ||||
|                     Log::debug(sprintf('Journal #%d switches to native amount (original is %s)', $journal['transaction_journal_id'], $journal['currency_code'])); | ||||
|                     $currencyId            = $default->id; | ||||
|                     $currencyName          = $default->name; | ||||
|                     $currencySymbol        = $default->symbol; | ||||
| @@ -301,6 +306,7 @@ class OperationsRepository implements OperationsRepositoryInterface | ||||
|                 // if the amount is not in $currency (but should be), use the foreign_amount if that one is correct.
 | ||||
|                 // otherwise, ignore the transaction all together.
 | ||||
|                 if (null !== $currency && $currencyId !== $currency->id && $currency->id === (int) $journal['foreign_currency_id']) { | ||||
|                     Log::debug(sprintf('Journal #%d switches to foreign amount because it matches native.', $journal['transaction_journal_id'])); | ||||
|                     $amount = $journal['foreign_amount']; | ||||
|                     $currencyId            = (int) $journal['foreign_currency_id']; | ||||
|                     $currencyName          = $journal['foreign_currency_name']; | ||||
|   | ||||
| @@ -150,8 +150,7 @@ class FrontpageChartGenerator | ||||
|         $useNative = $this->convertToNative && $this->default->id !== $limit->transaction_currency_id; | ||||
|         $currency  = $limit->transactionCurrency; | ||||
|         if ($useNative) { | ||||
|             Log::debug(sprintf('Processing limit #%d with %s %s', $limit->id, $this->default->code, $limit->native_amount)); | ||||
|             $currency = null; | ||||
|             Log::debug(sprintf('Processing limit #%d with (native) %s %s', $limit->id, $this->default->code, $limit->native_amount)); | ||||
|         } | ||||
|         if (!$useNative) { | ||||
|             Log::debug(sprintf('Processing limit #%d with %s %s', $limit->id, $limit->transactionCurrency->code, $limit->amount)); | ||||
| @@ -167,6 +166,9 @@ class FrontpageChartGenerator | ||||
|                 Log::debug(sprintf('Process spent row (%s)', $entry['currency_code'])); | ||||
|                 $data = $this->processRow($data, $budget, $limit, $entry); | ||||
|             } | ||||
|             if (!($entry['currency_id'] === $limit->transaction_currency_id || $useNative)) { | ||||
|                 Log::debug(sprintf('Skipping spent row (%s).', $entry['currency_code'])); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $data; | ||||
| @@ -181,6 +183,7 @@ class FrontpageChartGenerator | ||||
|     private function processRow(array $data, Budget $budget, BudgetLimit $limit, array $entry): array | ||||
|     { | ||||
|         $title = sprintf('%s (%s)', $budget->name, $entry['currency_name']); | ||||
|         Log::debug(sprintf('Title is "%s"', $title)); | ||||
|         if ($limit->start_date->startOfDay()->ne($this->start->startOfDay()) || $limit->end_date->startOfDay()->ne($this->end->startOfDay())) { | ||||
|             $title = sprintf( | ||||
|                 '%s (%s) (%s - %s)', | ||||
| @@ -190,15 +193,26 @@ class FrontpageChartGenerator | ||||
|                 $limit->end_date->isoFormat($this->monthAndDayFormat) | ||||
|             ); | ||||
|         } | ||||
|         $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
 | ||||
|         $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, $limit->amount) ? $limit->amount : $sumSpent);                              // spent
 | ||||
|         $data[1]['entries'][$title] = bcadd($data[1]['entries'][$title],1 === bccomp($limit->amount, $sumSpent) ? bcadd($entry['sum'], $limit->amount) : '0');              // left to spent
 | ||||
|         $data[2]['entries'][$title] = bcadd($data[2]['entries'][$title],1 === bccomp($limit->amount, $sumSpent) ? '0' : bcmul(bcadd($entry['sum'], $limit->amount), '-1')); // overspent
 | ||||
|         $data[0]['entries'][$title] = bcadd($data[0]['entries'][$title], 1 === bccomp($sumSpent, $amount) ? $amount : $sumSpent);                              // spent
 | ||||
|         $data[1]['entries'][$title] = bcadd($data[1]['entries'][$title],1 === bccomp($amount, $sumSpent) ? bcadd($entry['sum'], $amount) : '0');              // left to spent
 | ||||
|         $data[2]['entries'][$title] = bcadd($data[2]['entries'][$title],1 === bccomp($amount, $sumSpent) ? '0' : bcmul(bcadd($entry['sum'], $amount), '-1')); // overspent
 | ||||
| 
 | ||||
|         Log::debug(sprintf('Amount [spent]     is now %s.', $data[0]['entries'][$title])); | ||||
|         Log::debug(sprintf('Amount [left]      is now %s.', $data[1]['entries'][$title])); | ||||
|         Log::debug(sprintf('Amount [overspent] is now %s.', $data[2]['entries'][$title])); | ||||
| 
 | ||||
|         return $data; | ||||
|     } | ||||
|   | ||||
| @@ -28,7 +28,9 @@ use Carbon\Carbon; | ||||
| use FireflyIII\Exceptions\FireflyException; | ||||
| 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; | ||||
| 
 | ||||
| @@ -45,6 +47,20 @@ class ExchangeRateConverter | ||||
|     private array $prepared        = []; | ||||
|     private int   $queryCount      = 0; | ||||
| 
 | ||||
|     private UserGroup $userGroup; | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|         if (auth()->check()) { | ||||
|             $this->userGroup = auth()->user()->userGroup; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function setUserGroup(UserGroup $userGroup): void | ||||
|     { | ||||
|         $this->userGroup = $userGroup; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @throws FireflyException | ||||
|      */ | ||||
| @@ -85,8 +101,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) { | ||||
| @@ -96,7 +112,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'))); | ||||
| @@ -105,11 +121,11 @@ 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); | ||||
|             Log::debug(sprintf('ExchangeRateConverter: Return DB rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d'))); | ||||
|             Log::debug(sprintf('ExchangeRateConverter: Return inverse DB rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d'))); | ||||
| 
 | ||||
|             return $rate; | ||||
|         } | ||||
| @@ -143,7 +159,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; | ||||
| @@ -153,7 +169,7 @@ class ExchangeRateConverter | ||||
|             return $preparedRate; | ||||
|         } | ||||
| 
 | ||||
|         $cache        = new CacheProperties(); | ||||
|         $cache = new CacheProperties(); | ||||
|         $cache->addProperty($key); | ||||
|         if ($cache->has()) { | ||||
|             $rate = $cache->get(); | ||||
| @@ -166,16 +182,14 @@ class ExchangeRateConverter | ||||
|         } | ||||
| 
 | ||||
|         /** @var null|CurrencyExchangeRate $result */ | ||||
|         $result       = auth()->user() | ||||
|             ?->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)); | ||||
| @@ -215,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));
 | ||||
| @@ -250,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.'); | ||||
| @@ -272,14 +286,13 @@ class ExchangeRateConverter | ||||
|         $start->startOfDay(); | ||||
|         $end->endOfDay(); | ||||
|         Log::debug(sprintf('Preparing for %s to %s between %s and %s', $from->code, $to->code, $start->format('Y-m-d'), $end->format('Y-m-d'))); | ||||
|         $set              = auth()->user() | ||||
|             ->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'); | ||||
| @@ -293,10 +306,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, | ||||
| @@ -305,11 +318,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; | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -62,7 +62,9 @@ class Steam | ||||
|             $value = (string) ($transaction[$key] ?? '0'); | ||||
|             $value = '' === $value ? '0' : $value; | ||||
|             $sum   = bcadd($sum, $value); | ||||
|             //Log::debug(sprintf('Add value from "%s": %s', $key, $value));
 | ||||
|         } | ||||
|         Log::debug(sprintf('Sum of "%s"-fields is %s', $key, $sum)); | ||||
| 
 | ||||
|         return $sum; | ||||
|     } | ||||
| @@ -257,64 +259,95 @@ class Steam | ||||
|      * THAT currency. | ||||
|      * "native_balance" the balance according to the "native_amount" + "native_foreign_amount" fields. | ||||
|      * "ABC" the balance in this particular currency code (may repeat for each found currency). | ||||
|      * | ||||
|      * Het maakt niet uit of de native currency wel of niet gelijk is aan de account currency. | ||||
|      * Optelsom zou hetzelfde moeten zijn. Als het EUR is en de rekening ook is native_amount 0. | ||||
|      * Zo niet is amount 0 en native_amount het bedrag. | ||||
|      * | ||||
|      * Eerst een som van alle transacties in de native currency. Alle EUR bij elkaar opgeteld. | ||||
|      * Om te weten wat er nog meer op de rekening gebeurt, pak alles waar currency niet EUR is, en de foreign ook niet, | ||||
|      * en tel native_amount erbij op. | ||||
|      * Daarna pak je alle transacties waar currency niet EUR is, en de foreign wel, en tel foreign_amount erbij op. | ||||
|      * | ||||
|      * Wil je niks weten van native currencies, pak je: | ||||
|      * | ||||
|      * Eerst een som van alle transacties gegroepeerd op currency. Einde. | ||||
|      * | ||||
|      */ | ||||
|     public function finalAccountBalance(Account $account, Carbon $date): array | ||||
|     { | ||||
|         Log::debug(sprintf('Now in finalAccountBalance(#%d, "%s", "%s")', $account->id, $account->name, $date->format('Y-m-d H:i:s'))); | ||||
|         $native          = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); | ||||
|         $convertToNative = app('preferences')->getForUser($account->user, 'convert_to_native', false)->data; | ||||
|         $accountCurrency = $this->getAccountCurrency($account); | ||||
|         $hasCurrency     = null !== $accountCurrency; | ||||
|         $currency        = $accountCurrency ?? $native; | ||||
|         $currency        = $hasCurrency ? $accountCurrency : $native; | ||||
|         if (!$hasCurrency) { | ||||
|             Log::debug('Gave account fake currency.'); | ||||
|             //Log::debug('Pretend native currency is fake, because account has no currency.');
 | ||||
|             // fake currency
 | ||||
|             $native = new TransactionCurrency(); | ||||
|             //$native = new TransactionCurrency();
 | ||||
|         } | ||||
|         $return = [ | ||||
|             'native_balance' => '0', | ||||
|         ]; | ||||
|         Log::debug(sprintf('Now in finalAccountBalance("%s", "%s")', $account->name, $date->format('Y-m-d H:i:s'))); | ||||
|         //unset($accountCurrency);
 | ||||
|         $return = []; | ||||
| 
 | ||||
|         // first, the "balance", as described earlier.
 | ||||
|         $array             = $account->transactions() | ||||
|                                      ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|                                      ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|                                      ->where('transactions.transaction_currency_id', $currency->id) | ||||
|                                      ->get(['transactions.amount'])->toArray(); | ||||
|         $return['balance'] = $this->sumTransactions($array, 'amount'); | ||||
|         //Log::debug(sprintf('balance is %s', $return['balance']));
 | ||||
|         // add virtual balance:
 | ||||
|         $return['balance'] = bcadd('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance, $return['balance']); | ||||
|         Log::debug(sprintf('balance is %s (with virtual balance)', $return['balance'])); | ||||
|         if ($convertToNative) { | ||||
|             // normal balance
 | ||||
|             $return['balance'] = (string) $account->transactions() | ||||
|                                                   ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|                                                   ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|                                                   ->where('transactions.transaction_currency_id', $native->id) | ||||
|                                                   ->sum('transactions.amount'); | ||||
|             // plus virtual balance, if the account has a virtual_balance in the native currency
 | ||||
|             if($native->id === $accountCurrency?->id) { | ||||
|                 $return['balance'] = bcadd('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance, $return['balance']); | ||||
|             } | ||||
|             Log::debug(sprintf('balance is (%s only) %s (with virtual balance)', $native->code, $return['balance'])); | ||||
| 
 | ||||
|         // then, native balance (if necessary(
 | ||||
|         if ($native->id !== $currency->id) { | ||||
|             Log::debug('Will grab native balance for transactions on this account.'); | ||||
|             $array                    = $account->transactions() | ||||
|                                                 ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|                                                 ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|                                                 ->get(['transactions.native_amount'])->toArray(); | ||||
|             $return['native_balance'] = $this->sumTransactions($array, 'native_amount'); | ||||
| //                        Log::debug(sprintf('native_balance is %s', $return['native_balance']));
 | ||||
|             // native balance
 | ||||
|             $return['native_balance'] = (string) $account->transactions() | ||||
|                                                          ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|                                                          ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|                                                          ->whereNot('transactions.transaction_currency_id', $native->id) | ||||
|                                                          ->sum('transactions.native_amount'); | ||||
|             // plus native virtual balance.
 | ||||
|             $return['native_balance'] = bcadd('' === (string) $account->native_virtual_balance ? '0' : $account->native_virtual_balance, $return['native_balance']); | ||||
|             Log::debug(sprintf('native_balance is %s (with virtual balance)', $return['native_balance'])); | ||||
|             Log::debug(sprintf('native_balance is (all transactions to %s) %s (with virtual balance)', $native->code, $return['native_balance'])); | ||||
| 
 | ||||
|             // plus foreign transactions in THIS currency.
 | ||||
|             $sum                      = (string) $account->transactions() | ||||
|                                                          ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|                                                          ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|                                                          ->whereNot('transactions.transaction_currency_id', $native->id) | ||||
|                                                          ->where('transactions.foreign_currency_id', $native->id) | ||||
|                                                          ->sum('transactions.foreign_amount'); | ||||
|             $return['native_balance'] = bcadd($return['native_balance'], $sum); | ||||
| 
 | ||||
|             Log::debug(sprintf('Foreign amount transactions add (%s only) %s, total native_balance is now %s', $native->code, $sum, $return['native_balance'])); | ||||
|         } | ||||
| 
 | ||||
|         // balance(s) in other currencies.
 | ||||
|         // balance(s) in other (all) currencies.
 | ||||
|         $array  = $account->transactions() | ||||
|                           ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||
|                           ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') | ||||
|                           ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) | ||||
|                           ->get(['transaction_currencies.code', 'transactions.amount'])->toArray(); | ||||
|         $others = $this->groupAndSumTransactions($array, 'code', 'amount'); | ||||
| 
 | ||||
|         Log::debug('All balances are (joined)', $others); | ||||
|         // if the account has no own currency preference, drop balance in favor of native balance
 | ||||
|         if ($hasCurrency && !$convertToNative) { | ||||
|             $return['balance'] = $others[$currency->code] ?? null; | ||||
|             $return['native_balance'] = $others[$currency->code] ?? null; | ||||
|             Log::debug(sprintf('Set balance + native_balance to %s', $return['balance'])); | ||||
|         } | ||||
|         if (!$hasCurrency) { | ||||
|             Log::debug('Account has no currency preference, dropping balance in favor of native balance.'); | ||||
|             $return['native_balance'] = 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']); | ||||
|         } | ||||
| 
 | ||||
|         Log::debug('All others are (joined)', $others); | ||||
| 
 | ||||
|         return array_merge($return, $others); | ||||
|     } | ||||
| 
 | ||||
|   | ||||
| @@ -79,10 +79,6 @@ class General extends AbstractExtension | ||||
|                         if (!$useNative) { | ||||
|                             $strings[] = app('amount')->formatAnything($currency, $balance, false); | ||||
|                         } | ||||
|                         if($useNative) { | ||||
|                             $strings[] =sprintf('(%s)', app('amount')->formatAnything($currency, $balance, false)); | ||||
|                         } | ||||
| 
 | ||||
|                         continue; | ||||
|                     } | ||||
|                     if ('native_balance' === $key) { | ||||
| @@ -94,7 +90,7 @@ class General extends AbstractExtension | ||||
|                         continue; | ||||
|                     } | ||||
|                     // for multi currency accounts.
 | ||||
|                     if ($key !== $currency->code) { | ||||
|                     if ($useNative && $key !== $default->code) { | ||||
|                         $strings[] = app('amount')->formatAnything(TransactionCurrency::where('code', $key)->first(), $balance, false); | ||||
|                     } | ||||
|                 } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user