diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index 10e7b35120..271e7c8255 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -746,7 +746,7 @@ class GroupCollector implements GroupCollectorInterface $currentCollection = $collection; $countFilters = count($this->postFilters); $countCollection = count($currentCollection); - if (0 === $countFilters && 0 === $countCollection) { + if (0 === $countFilters) { return $currentCollection; } app('log')->debug(sprintf('GroupCollector: postFilterCollection has %d filter(s) and %d transaction(s).', count($this->postFilters), count($currentCollection))); diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index ccaaf6ebe3..ed15024f06 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -36,6 +36,7 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\CacheProperties; +use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Facades\Steam; use FireflyIII\Support\Http\Controllers\AugumentData; use FireflyIII\Support\Http\Controllers\ChartGeneration; @@ -87,12 +88,14 @@ class AccountController extends Controller /** @var Carbon $end */ $end = clone session('end', today(config('app.timezone'))->endOfMonth()); + $convertToNative = app('preferences')->get('convert_to_native', false)->data; $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); + $cache->addProperty($convertToNative); $cache->addProperty('chart.account.expense-accounts'); if ($cache->has()) { - return response()->json($cache->get()); + // return response()->json($cache->get()); } $start->subDay(); @@ -100,6 +103,7 @@ class AccountController extends Controller $currencies = []; $chartData = []; $tempData = []; + $default = Amount::getDefaultCurrency(); // grab all accounts and names $accounts = $this->accountRepository->getAccountsByType([AccountTypeEnum::EXPENSE->value]); @@ -110,25 +114,43 @@ 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, ]; } } @@ -140,8 +162,6 @@ class AccountController extends Controller } $currencies = $newCurrencies; - - // sort temp array by amount. $amounts = array_column($tempData, 'diff_float'); array_multisort($amounts, SORT_DESC, $tempData); diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index cbb14c51ff..06f7042592 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -38,10 +38,12 @@ use FireflyIII\Repositories\Budget\NoBudgetRepositoryInterface; use FireflyIII\Repositories\Budget\OperationsRepositoryInterface; use FireflyIII\Support\CacheProperties; use FireflyIII\Support\Chart\Budget\FrontpageChartGenerator; +use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Http\Controllers\AugumentData; use FireflyIII\Support\Http\Controllers\DateCalculation; use Illuminate\Http\JsonResponse; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; /** * Class BudgetController. @@ -83,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'); @@ -105,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: @@ -133,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); @@ -150,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'); @@ -162,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); @@ -177,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; @@ -198,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; @@ -214,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'], @@ -230,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); @@ -261,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; @@ -276,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'], @@ -290,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]; @@ -301,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); @@ -319,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; @@ -334,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'], @@ -350,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]; @@ -363,7 +365,7 @@ class BudgetController extends Controller ]; } - $data = $this->generator->multiCurrencyPieChart($chartData); + $data = $this->generator->multiCurrencyPieChart($chartData); $cache->store($data); return response()->json($data); @@ -374,25 +376,28 @@ 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()); + $convertToNative = app('preferences')->get('convert_to_native', false)->data; // chart properties for cache: - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); + $cache->addProperty($convertToNative); $cache->addProperty('chart.budget.frontpage'); if ($cache->has()) { - return response()->json($cache->get()); + // return response()->json($cache->get()); } - + Log::debug(sprintf('Regenerate frontpage chart from scratch.')); $chartGenerator = app(FrontpageChartGenerator::class); $chartGenerator->setUser(auth()->user()); $chartGenerator->setStart($start); $chartGenerator->setEnd($end); + $chartGenerator->convertToNative = $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); @@ -408,7 +413,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); @@ -437,11 +442,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; @@ -450,7 +455,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); } @@ -460,11 +465,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); @@ -476,7 +481,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); @@ -500,7 +505,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); diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 1605f164eb..3600090e84 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -49,8 +49,7 @@ class CategoryController extends Controller use ChartGeneration; use DateCalculation; - /** @var GeneratorInterface Chart generation methods. */ - protected $generator; + protected GeneratorInterface $generator; /** * CategoryController constructor. @@ -71,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()) { @@ -79,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); @@ -105,15 +104,17 @@ 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()); + $convertToNative = app('preferences')->get('convert_to_native', false)->data; // chart properties for cache: - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); + $cache->addProperty($convertToNative); $cache->addProperty('chart.category.frontpage'); if ($cache->has()) { - return response()->json($cache->get()); + // return response()->json($cache->get()); } $frontpageGenerator = new FrontpageChartGenerator($start, $end); @@ -139,7 +140,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 +161,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 +170,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 +186,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 +206,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 +215,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 +241,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 +256,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); diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index e0adf91345..0a13fdf53f 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -57,7 +57,7 @@ class BudgetLimit extends Model 'deleted' => Deleted::class, ]; - protected $fillable = ['budget_id', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'amount', 'transaction_currency_id']; + protected $fillable = ['budget_id', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'amount', 'transaction_currency_id','native_amount']; /** * Route binder. Converts the key in the URL to the specified object (or throw 404). diff --git a/app/Models/TransactionType.php b/app/Models/TransactionType.php index ca0db35540..b807264dec 100644 --- a/app/Models/TransactionType.php +++ b/app/Models/TransactionType.php @@ -37,25 +37,25 @@ class TransactionType extends Model use ReturnsIntegerIdTrait; use SoftDeletes; - #[\Deprecated] + #[\Deprecated] /** @deprecated */ public const string DEPOSIT = 'Deposit'; - #[\Deprecated] + #[\Deprecated] /** @deprecated */ public const string INVALID = 'Invalid'; - #[\Deprecated] + #[\Deprecated] /** @deprecated */ public const string LIABILITY_CREDIT = 'Liability credit'; - #[\Deprecated] + #[\Deprecated] /** @deprecated */ public const string OPENING_BALANCE = 'Opening balance'; - #[\Deprecated] + #[\Deprecated] /** @deprecated */ public const string RECONCILIATION = 'Reconciliation'; - #[\Deprecated] + #[\Deprecated] /** @deprecated */ public const string TRANSFER = 'Transfer'; - #[\Deprecated] + #[\Deprecated] /** @deprecated */ public const string WITHDRAWAL = 'Withdrawal'; protected $casts diff --git a/app/Repositories/Budget/OperationsRepository.php b/app/Repositories/Budget/OperationsRepository.php index e3c49d4b03..0acfcec310 100644 --- a/app/Repositories/Budget/OperationsRepository.php +++ b/app/Repositories/Budget/OperationsRepository.php @@ -211,6 +211,7 @@ class OperationsRepository implements OperationsRepositoryInterface ?TransactionCurrency $currency = null ): 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. // TODO this filter must be somewhere in AccountRepositoryInterface because I suspect its needed more often (A113) @@ -237,7 +238,7 @@ class OperationsRepository implements OperationsRepositoryInterface $collector = app(GroupCollectorInterface::class); $collector->setUser($this->user) ->setRange($start, $end) - // ->excludeDestinationAccounts($selection) + // ->excludeDestinationAccounts($selection) ->setTypes([TransactionType::WITHDRAWAL]); if (null !== $accounts) { @@ -247,6 +248,7 @@ class OperationsRepository implements OperationsRepositoryInterface $budgets = $this->getBudgets(); } if (null !== $currency) { + Log::debug(sprintf('Limit to currency %s', $currency->code)); $collector->setCurrency($currency); } $collector->setBudgets($budgets); @@ -265,26 +267,47 @@ class OperationsRepository implements OperationsRepositoryInterface $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 = []; foreach ($journals as $journal) { +// Log::debug(sprintf('Journal #%d.', $journal['transaction_journal_id'])); +// Log::debug(sprintf('Amounts: %1$s %2$s (amount), %3$s %4$s (foreign_amount), %5$s %6$s (native_amount) %5$s %7$s (foreign native amount)', +// $journal['currency_code'], $journal['amount'], $journal['foreign_currency_code'], $journal['foreign_amount'], +// $default->code, $journal['native_amount'], $journal['native_foreign_amount']) +// ); + // TODO same as in category::sumexpenses + $amount = '0'; $currencyId = (int) $journal['currency_id']; $currencyName = $journal['currency_name']; $currencySymbol = $journal['currency_symbol']; $currencyCode = $journal['currency_code']; $currencyDecimalPlaces = $journal['currency_decimal_places']; - - // if the user wants everything in native currency, use it. - // if the foreign amount is in this currency it's OK because Amount::getAmountFromJournal catches that. - if ($convertToNative && $default->id !== (int) $journal['currency_id']) { - // use default currency info - $currencyId = $default->id; - $currencyName = $default->name; - $currencySymbol = $default->symbol; - $currencyCode = $default->code; - $currencyDecimalPlaces = $default->decimal_places; + if ($convertToNative) { + $useNative = $default->id !== (int) $journal['currency_id']; + $amount = Amount::getAmountFromJournal($journal); + if($useNative) { + $currencyId = $default->id; + $currencyName = $default->name; + $currencySymbol = $default->symbol; + $currencyCode = $default->code; + $currencyDecimalPlaces = $default->decimal_places; + } + } + if (!$convertToNative) { + $amount = $journal['amount']; + // if the amount is not in $currency (but should be), use the foreign_amount if that one is correct. + // otherwise, ignore the transaction all together. + if (null !== $currency && $currencyId !== $currency->id && $currency->id === (int) $journal['foreign_currency_id']) { + $amount = $journal['foreign_amount']; + $currencyId = (int) $journal['foreign_currency_id']; + $currencyName = $journal['foreign_currency_name']; + $currencySymbol = $journal['foreign_currency_symbol']; + $currencyCode = $journal['foreign_currency_code']; + $currencyDecimalPlaces = $journal['foreign_currency_decimal_places']; + } } $array[$currencyId] ??= [ 'sum' => '0', @@ -294,11 +317,10 @@ class OperationsRepository implements OperationsRepositoryInterface 'currency_code' => $currencyCode, 'currency_decimal_places' => $currencyDecimalPlaces, ]; - $amount = Amount::getAmountFromJournal($journal); $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($amount)); Log::debug(sprintf('Journal #%d adds amount %s %s', $journal['transaction_journal_id'], $currencyCode, $amount)); } - + Log::debug('End of sumExpenses.', $array); return $array; } } diff --git a/app/Repositories/Category/NoCategoryRepository.php b/app/Repositories/Category/NoCategoryRepository.php index 5f879700b4..a5c8dfe2e5 100644 --- a/app/Repositories/Category/NoCategoryRepository.php +++ b/app/Repositories/Category/NoCategoryRepository.php @@ -27,9 +27,11 @@ namespace FireflyIII\Repositories\Category; use Carbon\Carbon; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\TransactionType; +use FireflyIII\Support\Facades\Amount; use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; /** * Class NoCategoryRepository @@ -151,18 +153,46 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface } $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) { - $currencyId = (int) $journal['currency_id']; - $array[$currencyId] ??= [ + // Almost the same as in \FireflyIII\Repositories\Budget\OperationsRepository::sumExpenses + $amount = '0'; + $currencyId = (int) $journal['currency_id']; + $currencyName = $journal['currency_name']; + $currencySymbol = $journal['currency_symbol']; + $currencyCode = $journal['currency_code']; + $currencyDecimalPlaces = $journal['currency_decimal_places']; + if ($convertToNative) { + $useNative = $default->id !== (int) $journal['currency_id']; + $amount = Amount::getAmountFromJournal($journal); + if ($useNative) { + $currencyId = $default->id; + $currencyName = $default->name; + $currencySymbol = $default->symbol; + $currencyCode = $default->code; + $currencyDecimalPlaces = $default->decimal_places; + } + Log::debug(sprintf('[a] Add amount %s %s', $currencyCode, $amount)); + } + if (!$convertToNative) { + // ignore the amount in foreign currency. + Log::debug(sprintf('[b] Add amount %s %s', $currencyCode, $journal['amount'])); + $amount = $journal['amount']; + } + + + $array[$currencyId] ??= [ 'sum' => '0', - 'currency_id' => $currencyId, - 'currency_name' => $journal['currency_name'], - 'currency_symbol' => $journal['currency_symbol'], - 'currency_code' => $journal['currency_code'], - 'currency_decimal_places' => $journal['currency_decimal_places'], + 'currency_id' => (string) $currencyId, + 'currency_name' => $currencyName, + 'currency_symbol' => $currencySymbol, + 'currency_code' => $currencyCode, + 'currency_decimal_places' => $currencyDecimalPlaces, ]; - $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount'] ?? '0')); + $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($amount)); } return $array; diff --git a/app/Repositories/Category/OperationsRepository.php b/app/Repositories/Category/OperationsRepository.php index 73028534e4..d51264dd61 100644 --- a/app/Repositories/Category/OperationsRepository.php +++ b/app/Repositories/Category/OperationsRepository.php @@ -25,11 +25,14 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Category; use Carbon\Carbon; +use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\TransactionType; +use FireflyIII\Support\Facades\Amount; use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; /** * Class OperationsRepository @@ -60,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) { @@ -74,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'], @@ -109,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; @@ -144,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) { @@ -158,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'], @@ -197,8 +200,7 @@ 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); } @@ -206,13 +208,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) { @@ -220,7 +222,7 @@ class OperationsRepository implements OperationsRepositoryInterface } // info about the currency: - $array[$currencyId] ??= [ + $array[$currencyId] ??= [ 'categories' => [], 'currency_id' => (string) $currencyId, 'currency_name' => $journal['currency_name'], @@ -260,8 +262,7 @@ 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); } @@ -269,13 +270,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) { @@ -283,7 +284,7 @@ class OperationsRepository implements OperationsRepositoryInterface } // info about the currency: - $array[$currencyId] ??= [ + $array[$currencyId] ??= [ 'categories' => [], 'currency_id' => (string) $currencyId, 'currency_name' => $journal['currency_name'], @@ -325,10 +326,11 @@ class OperationsRepository implements OperationsRepositoryInterface { /** @var GroupCollectorInterface $collector */ $collector = app(GroupCollectorInterface::class); - $collector->setUser($this->user)->setRange($start, $end) - ->setTypes([TransactionType::WITHDRAWAL]) - ; + $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); + // default currency information for native stuff. + $convertToNative = app('preferences')->get('convert_to_native', false)->data; + $default = app('amount')->getDefaultCurrency(); if (null !== $accounts && $accounts->count() > 0) { $collector->setAccounts($accounts); } @@ -337,20 +339,47 @@ class OperationsRepository implements OperationsRepositoryInterface } $collector->setCategories($categories); $collector->withCategoryInformation(); - $journals = $collector->getExtractedJournals(); - $array = []; + $journals = $collector->getExtractedJournals(); + $array = []; + + Log::debug(sprintf('Collected %d journals', count($journals))); foreach ($journals as $journal) { - $currencyId = (int) $journal['currency_id']; - $array[$currencyId] ??= [ + // Almost the same as in \FireflyIII\Repositories\Budget\OperationsRepository::sumExpenses + $amount = '0'; + $currencyId = (int) $journal['currency_id']; + $currencyName = $journal['currency_name']; + $currencySymbol = $journal['currency_symbol']; + $currencyCode = $journal['currency_code']; + $currencyDecimalPlaces = $journal['currency_decimal_places']; + if ($convertToNative) { + $useNative = $default->id !== (int) $journal['currency_id']; + $amount = Amount::getAmountFromJournal($journal); + if ($useNative) { + $currencyId = $default->id; + $currencyName = $default->name; + $currencySymbol = $default->symbol; + $currencyCode = $default->code; + $currencyDecimalPlaces = $default->decimal_places; + } + Log::debug(sprintf('[a] Add amount %s %s', $currencyCode, $amount)); + } + if (!$convertToNative) { + // ignore the amount in foreign currency. + Log::debug(sprintf('[b] Add amount %s %s', $currencyCode, $journal['amount'])); + $amount = $journal['amount']; + } + + + $array[$currencyId] ??= [ 'sum' => '0', 'currency_id' => (string) $currencyId, - 'currency_name' => $journal['currency_name'], - 'currency_symbol' => $journal['currency_symbol'], - 'currency_code' => $journal['currency_code'], - 'currency_decimal_places' => (int) $journal['currency_decimal_places'], + 'currency_name' => $currencyName, + 'currency_symbol' => $currencySymbol, + 'currency_code' => $currencyCode, + 'currency_decimal_places' => $currencyDecimalPlaces, ]; - $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount'])); + $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($amount)); } return $array; @@ -364,8 +393,7 @@ 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); @@ -374,12 +402,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'], @@ -401,8 +429,7 @@ 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); @@ -411,12 +438,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'], diff --git a/app/Support/Amount.php b/app/Support/Amount.php index a185a6c891..8674225ecd 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -30,6 +30,7 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\UserGroup; use FireflyIII\User; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; /** * Class Amount. @@ -57,9 +58,11 @@ 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)); // fallback, the transaction has a foreign amount in $currency. - if ($convertToNative && null !== $journal['foreign_amount'] && $currency->id === $journal['foreign_currency_id']) { + if ($convertToNative && null !== $journal['foreign_amount'] && $currency->id === (int)$journal['foreign_currency_id']) { $amount = $journal['foreign_amount']; + //Log::debug(sprintf('Overruled, amount is now %s', $amount)); } return $amount; } diff --git a/app/Support/Chart/Budget/FrontpageChartGenerator.php b/app/Support/Chart/Budget/FrontpageChartGenerator.php index 5c08cb03d2..f43cf44c18 100644 --- a/app/Support/Chart/Budget/FrontpageChartGenerator.php +++ b/app/Support/Chart/Budget/FrontpageChartGenerator.php @@ -26,11 +26,13 @@ namespace FireflyIII\Support\Chart\Budget; use Carbon\Carbon; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; +use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\OperationsRepositoryInterface; use FireflyIII\User; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; /** * Class FrontpageChartGenerator @@ -43,6 +45,9 @@ class FrontpageChartGenerator private Carbon $end; private string $monthAndDayFormat; private Carbon $start; + public bool $convertToNative = false; + public TransactionCurrency $default; + /** * FrontpageChartGenerator constructor. @@ -62,6 +67,7 @@ class FrontpageChartGenerator */ public function generate(): array { + Log::debug('Now in generate for budget chart.'); $budgets = $this->budgetRepository->getActiveBudgets(); $data = [ ['label' => (string) trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'], @@ -74,6 +80,7 @@ class FrontpageChartGenerator foreach ($budgets as $budget) { $data = $this->processBudget($data, $budget); } + Log::debug('DONE with generate budget chart.'); return $data; } @@ -85,15 +92,19 @@ class FrontpageChartGenerator */ private function processBudget(array $data, Budget $budget): array { + Log::debug(sprintf('Now processing budget #%d ("%s")', $budget->id, $budget->name)); // get all limits: $limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end); - + Log::debug(sprintf('Found %d limit(s) for budget #%d.', $limits->count(), $budget->id)); // if no limits if (0 === $limits->count()) { - return $this->noBudgetLimits($data, $budget); + $result = $this->noBudgetLimits($data, $budget); + Log::debug(sprintf('Now DONE processing budget #%d ("%s")', $budget->id, $budget->name)); + return $result; } - - return $this->budgetLimits($data, $budget, $limits); + $result = $this->budgetLimits($data, $budget, $limits); + Log::debug(sprintf('Now DONE processing budget #%d ("%s")', $budget->id, $budget->name)); + return $result; } /** @@ -120,10 +131,12 @@ class FrontpageChartGenerator */ private function budgetLimits(array $data, Budget $budget, Collection $limits): array { + Log::debug('Start processing budget limits.'); /** @var BudgetLimit $limit */ foreach ($limits as $limit) { $data = $this->processLimit($data, $budget, $limit); } + Log::debug('Done processing budget limits.'); return $data; } @@ -134,12 +147,24 @@ class FrontpageChartGenerator */ private function processLimit(array $data, Budget $budget, BudgetLimit $limit): array { - $spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $limit->transactionCurrency); + $useNative = $this->convertToNative && $this->default->id !== $limit->transaction_currency_id; + $currency = $limit->transactionCurrency; + if ($useNative) { + Log::debug(sprintf('Processing limit #%d with %s %s', $limit->id, $this->default->code, $limit->native_amount)); + $currency = null; + } + if (!$useNative) { + Log::debug(sprintf('Processing limit #%d with %s %s', $limit->id, $limit->transactionCurrency->code, $limit->amount)); + } + $spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $currency); + Log::debug(sprintf('Spent array has %d entries.', count($spent))); /** @var array $entry */ foreach ($spent as $entry) { // only spent the entry where the entry's currency matches the budget limit's currency - if ($entry['currency_id'] === $limit->transaction_currency_id) { + // or when useNative is true. + if ($entry['currency_id'] === $limit->transaction_currency_id || $useNative) { + Log::debug(sprintf('Process spent row (%s)', $entry['currency_code'])); $data = $this->processRow($data, $budget, $limit, $entry); } } @@ -155,7 +180,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']); if ($limit->start_date->startOfDay()->ne($this->start->startOfDay()) || $limit->end_date->startOfDay()->ne($this->end->startOfDay())) { $title = sprintf( '%s (%s) (%s - %s)', @@ -165,11 +190,15 @@ class FrontpageChartGenerator $limit->end_date->isoFormat($this->monthAndDayFormat) ); } - $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] = 1 === bccomp($sumSpent, $limit->amount) ? $limit->amount : $sumSpent; // spent - $data[1]['entries'][$title] = 1 === bccomp($limit->amount, $sumSpent) ? bcadd($entry['sum'], $limit->amount) : '0'; // left to spent - $data[2]['entries'][$title] = 1 === bccomp($limit->amount, $sumSpent) ? '0' : bcmul(bcadd($entry['sum'], $limit->amount), '-1'); // overspent + + $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 return $data; } diff --git a/app/Support/Chart/Category/FrontpageChartGenerator.php b/app/Support/Chart/Category/FrontpageChartGenerator.php index 3951673950..d46992d107 100644 --- a/app/Support/Chart/Category/FrontpageChartGenerator.php +++ b/app/Support/Chart/Category/FrontpageChartGenerator.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Chart\Category; use Carbon\Carbon; -use FireflyIII\Models\AccountType; +use FireflyIII\Enums\AccountTypeEnum; use FireflyIII\Models\Category; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; @@ -33,6 +33,7 @@ use FireflyIII\Repositories\Category\NoCategoryRepositoryInterface; use FireflyIII\Repositories\Category\OperationsRepositoryInterface; use FireflyIII\Support\Http\Controllers\AugumentData; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; /** * Class FrontpageChartGenerator @@ -65,13 +66,12 @@ class FrontpageChartGenerator public function generate(): array { - $categories = $this->repository->getCategories(); - $accounts = $this->accountRepos->getAccountsByType( - [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::ASSET, AccountType::DEFAULT] - ); + 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]); // 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); @@ -95,9 +95,11 @@ class FrontpageChartGenerator private function collectExpenses(Category $category, Collection $accounts): array { + Log::debug(sprintf('Collect expenses for category #%d ("%s")', $category->id, $category->name)); $spent = $this->opsRepos->sumExpenses($this->start, $this->end, $accounts, new Collection([$category])); $tempData = []; foreach ($spent as $currency) { + Log::debug(sprintf('Spent %s %s', $currency['currency_code'], $currency['sum'])); $this->addCurrency($currency); $tempData[] = [ 'name' => $category->name, diff --git a/app/Support/Http/Controllers/ChartGeneration.php b/app/Support/Http/Controllers/ChartGeneration.php index 88a6322ba2..738cd97142 100644 --- a/app/Support/Http/Controllers/ChartGeneration.php +++ b/app/Support/Http/Controllers/ChartGeneration.php @@ -46,14 +46,15 @@ trait ChartGeneration protected function accountBalanceChart(Collection $accounts, Carbon $start, Carbon $end): array // chart helper method. { // chart properties for cache: + $convertToNative = app('preferences')->get('convert_to_native', false)->data; $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty('chart.account.account-balance-chart'); $cache->addProperty($accounts); - $convertToNative = app('preferences')->get('convert_to_native', false)->data; + $cache->addProperty($convertToNative); if ($cache->has()) { - // return $cache->get(); + return $cache->get(); } app('log')->debug('Regenerate chart.account.account-balance-chart from scratch.'); $locale = app('steam')->getLocale(); @@ -69,16 +70,10 @@ trait ChartGeneration /** @var Account $account */ foreach ($accounts as $account) { - // TODO we can use getAccountCurrency instead. - $currency = $accountRepos->getAccountCurrency($account); - if (null === $currency) { - $currency = $default; - } - // if the user prefers the native currency, overrule the currency of the account. - if ($currency->id !== $default->id && $convertToNative) { - $currency = $default; - } - + $currency = $accountRepos->getAccountCurrency($account) ?? $default; + $useNative = $convertToNative && $default->id !== $currency->id; + $field =$useNative ? 'native_balance' : 'balance'; + $currency = $useNative ? $default : $currency; $currentSet = [ 'label' => $account->name, 'currency_symbol' => $currency->symbol, @@ -94,7 +89,7 @@ trait ChartGeneration $balance = $range[$format] ?? $previous; $previous = $balance; $currentStart->addDay(); - $currentSet['entries'][$label] = $balance['balance']; // TODO or native_balance + $currentSet['entries'][$label] = $balance[$field]; } $chartData[] = $currentSet; } diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 70518f43af..babe606e75 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -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)) { @@ -74,7 +74,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); @@ -83,52 +83,51 @@ 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) { @@ -137,7 +136,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) { @@ -183,10 +182,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; @@ -261,47 +260,60 @@ class Steam */ public function finalAccountBalance(Account $account, Carbon $date): array { - $native = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); - $currency = $this->getAccountCurrency($account) ?? $native; - $return = [ + $native = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); + $accountCurrency = $this->getAccountCurrency($account); + $hasCurrency = null !== $accountCurrency; + $currency = $accountCurrency ?? $native; + if (!$hasCurrency) { + Log::debug('Gave account fake currency.'); + // fake currency + $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'))); // 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() - ; + ->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'])); + //Log::debug(sprintf('balance is %s', $return['balance'])); // add virtual balance: $return['balance'] = bcadd('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance, $return['balance']); - // Log::debug(sprintf('balance is %s (with virtual balance)', $return['balance'])); + Log::debug(sprintf('balance is %s (with virtual balance)', $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() - ; + ->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'])); +// Log::debug(sprintf('native_balance is %s', $return['native_balance'])); $return['native_balance'] = bcadd('' === (string) $account->native_virtual_balance ? '0' : $account->native_virtual_balance, $return['native_balance']); - // Log::debug(sprintf('native_balance is %s (with virtual balance)', $return['native_balance'])); + Log::debug(sprintf('native_balance is %s (with virtual balance)', $return['native_balance'])); } // balance(s) in other currencies. - $array = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) - ->get(['transaction_currencies.code', 'transactions.amount'])->toArray() - ; - $others = $this->groupAndSumTransactions($array, 'code', 'amount'); - // Log::debug('All others are (joined)', $others); + $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'); + + // if the account has no own currency preference, drop balance in favor of native 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']); + unset($return['balance']); + } + + Log::debug('All others are (joined)', $others); return array_merge($return, $others); } @@ -343,17 +355,17 @@ 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; + $list[(int) $entry->account_id] = $date; } return $list; @@ -426,9 +438,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; @@ -465,7 +477,7 @@ class Steam */ public function floatalize(string $value): string { - $value = strtoupper($value); + $value = strtoupper($value); if (!str_contains($value, 'E')) { return $value; } diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 91f36d4c90..80db203ee9 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -69,26 +69,31 @@ class General extends AbstractExtension $date = session('end', today(config('app.timezone'))->endOfMonth()); $info = Steam::finalAccountBalance($account, $date); $currency = Steam::getAccountCurrency($account); - $native = Amount::getDefaultCurrency(); + $default = Amount::getDefaultCurrency(); $convertToNative = app('preferences')->get('convert_to_native', false)->data; + $useNative = $convertToNative && $default->id !== $currency->id; $strings = []; foreach ($info as $key => $balance) { if ('balance' === $key) { // balance in account currency. - if (!$convertToNative || $currency->code === $native->code) { + if (!$useNative) { $strings[] = app('amount')->formatAnything($currency, $balance, false); } + if($useNative) { + $strings[] =sprintf('(%s)', app('amount')->formatAnything($currency, $balance, false)); + } continue; } if ('native_balance' === $key) { // balance in native currency. - if ($convertToNative) { - $strings[] = app('amount')->formatAnything($native, $balance, false); + if ($useNative) { + $strings[] = app('amount')->formatAnything($default, $balance, false); } continue; } + // for multi currency accounts. if ($key !== $currency->code) { $strings[] = app('amount')->formatAnything(TransactionCurrency::where('code', $key)->first(), $balance, false); }