Convert more charts.

This commit is contained in:
James Cole
2024-12-24 10:29:07 +01:00
parent e8ef630424
commit 7e2e49e129
15 changed files with 454 additions and 303 deletions

View File

@@ -746,7 +746,7 @@ class GroupCollector implements GroupCollectorInterface
$currentCollection = $collection; $currentCollection = $collection;
$countFilters = count($this->postFilters); $countFilters = count($this->postFilters);
$countCollection = count($currentCollection); $countCollection = count($currentCollection);
if (0 === $countFilters && 0 === $countCollection) { if (0 === $countFilters) {
return $currentCollection; return $currentCollection;
} }
app('log')->debug(sprintf('GroupCollector: postFilterCollection has %d filter(s) and %d transaction(s).', count($this->postFilters), count($currentCollection))); app('log')->debug(sprintf('GroupCollector: postFilterCollection has %d filter(s) and %d transaction(s).', count($this->postFilters), count($currentCollection)));

View File

@@ -36,6 +36,7 @@ use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam; use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\AugumentData; use FireflyIII\Support\Http\Controllers\AugumentData;
use FireflyIII\Support\Http\Controllers\ChartGeneration; use FireflyIII\Support\Http\Controllers\ChartGeneration;
@@ -87,12 +88,14 @@ class AccountController extends Controller
/** @var Carbon $end */ /** @var Carbon $end */
$end = clone session('end', today(config('app.timezone'))->endOfMonth()); $end = clone session('end', today(config('app.timezone'))->endOfMonth());
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty($convertToNative);
$cache->addProperty('chart.account.expense-accounts'); $cache->addProperty('chart.account.expense-accounts');
if ($cache->has()) { if ($cache->has()) {
return response()->json($cache->get()); // return response()->json($cache->get());
} }
$start->subDay(); $start->subDay();
@@ -100,6 +103,7 @@ class AccountController extends Controller
$currencies = []; $currencies = [];
$chartData = []; $chartData = [];
$tempData = []; $tempData = [];
$default = Amount::getDefaultCurrency();
// grab all accounts and names // grab all accounts and names
$accounts = $this->accountRepository->getAccountsByType([AccountTypeEnum::EXPENSE->value]); $accounts = $this->accountRepository->getAccountsByType([AccountTypeEnum::EXPENSE->value]);
@@ -110,25 +114,43 @@ class AccountController extends Controller
$endBalances = app('steam')->finalAccountsBalance($accounts, $end); $endBalances = app('steam')->finalAccountsBalance($accounts, $end);
// loop the end balances. This is an array for each account ($expenses) // loop the end balances. This is an array for each account ($expenses)
foreach ($endBalances as $accountId => $expenses) { // loop the accounts, then check for balance and currency info.
$accountId = (int) $accountId; foreach($accounts as $account) {
// loop each expense entry (each entry can be a different currency). Log::debug(sprintf('Now in account #%d ("%s")', $account->id, $account->name));
foreach ($expenses as $currencyCode => $endAmount) { $expenses = $endBalances[$account->id] ?? false;
if (3 !== strlen($currencyCode)) { if(false === $expenses) {
Log::error(sprintf('Found no end balance for account #%d',$account->id));
continue; 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. // see if there is an accompanying start amount.
// grab the difference and find the currency. // grab the difference and find the currency.
$startAmount = (string) ($startBalances[$accountId][$currencyCode] ?? '0'); $startBalance = ($startBalances[$account->id][$key] ?? '0');
$diff = bcsub((string) $endAmount, $startAmount); Log::debug(sprintf('Start balance is %s', $startBalance));
$currencies[$currencyCode] ??= $this->currencyRepository->findByCode($currencyCode); $diff = bcsub($endBalance, $startBalance);
$currencies[$searchCode] ??= $this->currencyRepository->findByCode($searchCode);
if (0 !== bccomp($diff, '0')) { if (0 !== bccomp($diff, '0')) {
// store the values in a temporary array. // store the values in a temporary array.
$tempData[] = [ $tempData[] = [
'name' => $accountNames[$accountId], 'name' => $accountNames[$account->id],
'difference' => $diff, 'difference' => $diff,
'diff_float' => (float) $diff, // intentional float '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; $currencies = $newCurrencies;
// sort temp array by amount. // sort temp array by amount.
$amounts = array_column($tempData, 'diff_float'); $amounts = array_column($tempData, 'diff_float');
array_multisort($amounts, SORT_DESC, $tempData); array_multisort($amounts, SORT_DESC, $tempData);

View File

@@ -38,10 +38,12 @@ use FireflyIII\Repositories\Budget\NoBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface; use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Chart\Budget\FrontpageChartGenerator; use FireflyIII\Support\Chart\Budget\FrontpageChartGenerator;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Controllers\AugumentData; use FireflyIII\Support\Http\Controllers\AugumentData;
use FireflyIII\Support\Http\Controllers\DateCalculation; use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class BudgetController. * Class BudgetController.
@@ -376,20 +378,23 @@ class BudgetController extends Controller
{ {
$start = session('start', today(config('app.timezone'))->startOfMonth()); $start = session('start', today(config('app.timezone'))->startOfMonth());
$end = session('end', today(config('app.timezone'))->endOfMonth()); $end = session('end', today(config('app.timezone'))->endOfMonth());
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
// chart properties for cache: // chart properties for cache:
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty($convertToNative);
$cache->addProperty('chart.budget.frontpage'); $cache->addProperty('chart.budget.frontpage');
if ($cache->has()) { 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 = app(FrontpageChartGenerator::class);
$chartGenerator->setUser(auth()->user()); $chartGenerator->setUser(auth()->user());
$chartGenerator->setStart($start); $chartGenerator->setStart($start);
$chartGenerator->setEnd($end); $chartGenerator->setEnd($end);
$chartGenerator->convertToNative = $convertToNative;
$chartGenerator->default = Amount::getDefaultCurrency();
$chartData = $chartGenerator->generate(); $chartData = $chartGenerator->generate();
$data = $this->generator->multiSet($chartData); $data = $this->generator->multiSet($chartData);

View File

@@ -49,8 +49,7 @@ class CategoryController extends Controller
use ChartGeneration; use ChartGeneration;
use DateCalculation; use DateCalculation;
/** @var GeneratorInterface Chart generation methods. */ protected GeneratorInterface $generator;
protected $generator;
/** /**
* CategoryController constructor. * CategoryController constructor.
@@ -107,13 +106,15 @@ class CategoryController extends Controller
{ {
$start = session('start', today(config('app.timezone'))->startOfMonth()); $start = session('start', today(config('app.timezone'))->startOfMonth());
$end = session('end', today(config('app.timezone'))->endOfMonth()); $end = session('end', today(config('app.timezone'))->endOfMonth());
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
// chart properties for cache: // chart properties for cache:
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty($convertToNative);
$cache->addProperty('chart.category.frontpage'); $cache->addProperty('chart.category.frontpage');
if ($cache->has()) { if ($cache->has()) {
return response()->json($cache->get()); // return response()->json($cache->get());
} }
$frontpageGenerator = new FrontpageChartGenerator($start, $end); $frontpageGenerator = new FrontpageChartGenerator($start, $end);

View File

@@ -57,7 +57,7 @@ class BudgetLimit extends Model
'deleted' => Deleted::class, '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). * Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

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

View File

@@ -211,6 +211,7 @@ class OperationsRepository implements OperationsRepositoryInterface
?TransactionCurrency $currency = null ?TransactionCurrency $currency = null
): array ): array
{ {
Log::debug('Start of sumExpenses.');
// this collector excludes all transfers TO liabilities (which are also withdrawals) // 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. // 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) // TODO this filter must be somewhere in AccountRepositoryInterface because I suspect its needed more often (A113)
@@ -247,6 +248,7 @@ class OperationsRepository implements OperationsRepositoryInterface
$budgets = $this->getBudgets(); $budgets = $this->getBudgets();
} }
if (null !== $currency) { if (null !== $currency) {
Log::debug(sprintf('Limit to currency %s', $currency->code));
$collector->setCurrency($currency); $collector->setCurrency($currency);
} }
$collector->setBudgets($budgets); $collector->setBudgets($budgets);
@@ -265,27 +267,48 @@ class OperationsRepository implements OperationsRepositoryInterface
$result = $collector->getExtractedJournals(); $result = $collector->getExtractedJournals();
// app('log')->debug(sprintf('Found %d journals with currency %s.', count($result), $currency->code)); // 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): // 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; $journals = $result + $journals;
} }
$array = []; $array = [];
foreach ($journals as $journal) { 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']; $currencyId = (int) $journal['currency_id'];
$currencyName = $journal['currency_name']; $currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol']; $currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code']; $currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places']; $currencyDecimalPlaces = $journal['currency_decimal_places'];
if ($convertToNative) {
// if the user wants everything in native currency, use it. $useNative = $default->id !== (int) $journal['currency_id'];
// if the foreign amount is in this currency it's OK because Amount::getAmountFromJournal catches that. $amount = Amount::getAmountFromJournal($journal);
if ($convertToNative && $default->id !== (int) $journal['currency_id']) { if($useNative) {
// use default currency info
$currencyId = $default->id; $currencyId = $default->id;
$currencyName = $default->name; $currencyName = $default->name;
$currencySymbol = $default->symbol; $currencySymbol = $default->symbol;
$currencyCode = $default->code; $currencyCode = $default->code;
$currencyDecimalPlaces = $default->decimal_places; $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] ??= [ $array[$currencyId] ??= [
'sum' => '0', 'sum' => '0',
'currency_id' => $currencyId, 'currency_id' => $currencyId,
@@ -294,11 +317,10 @@ class OperationsRepository implements OperationsRepositoryInterface
'currency_code' => $currencyCode, 'currency_code' => $currencyCode,
'currency_decimal_places' => $currencyDecimalPlaces, 'currency_decimal_places' => $currencyDecimalPlaces,
]; ];
$amount = Amount::getAmountFromJournal($journal);
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($amount)); $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($amount));
Log::debug(sprintf('Journal #%d adds amount %s %s', $journal['transaction_journal_id'], $currencyCode, $amount)); Log::debug(sprintf('Journal #%d adds amount %s %s', $journal['transaction_journal_id'], $currencyCode, $amount));
} }
Log::debug('End of sumExpenses.', $array);
return $array; return $array;
} }
} }

View File

@@ -27,9 +27,11 @@ namespace FireflyIII\Repositories\Category;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class NoCategoryRepository * Class NoCategoryRepository
@@ -151,18 +153,46 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface
} }
$journals = $collector->getExtractedJournals(); $journals = $collector->getExtractedJournals();
$array = []; $array = [];
// default currency information for native stuff.
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$default = app('amount')->getDefaultCurrency();
foreach ($journals as $journal) { foreach ($journals as $journal) {
// Almost the same as in \FireflyIII\Repositories\Budget\OperationsRepository::sumExpenses
$amount = '0';
$currencyId = (int) $journal['currency_id']; $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] ??= [ $array[$currencyId] ??= [
'sum' => '0', 'sum' => '0',
'currency_id' => $currencyId, 'currency_id' => (string) $currencyId,
'currency_name' => $journal['currency_name'], 'currency_name' => $currencyName,
'currency_symbol' => $journal['currency_symbol'], 'currency_symbol' => $currencySymbol,
'currency_code' => $journal['currency_code'], 'currency_code' => $currencyCode,
'currency_decimal_places' => $journal['currency_decimal_places'], '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; return $array;

View File

@@ -25,11 +25,14 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Category; namespace FireflyIII\Repositories\Category;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class OperationsRepository * Class OperationsRepository
@@ -197,8 +200,7 @@ class OperationsRepository implements OperationsRepositoryInterface
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::TRANSFER]) $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::TRANSFER])
->setDestinationAccounts($accounts)->excludeSourceAccounts($accounts) ->setDestinationAccounts($accounts)->excludeSourceAccounts($accounts);
;
if (null !== $categories && $categories->count() > 0) { if (null !== $categories && $categories->count() > 0) {
$collector->setCategories($categories); $collector->setCategories($categories);
} }
@@ -260,8 +262,7 @@ class OperationsRepository implements OperationsRepositoryInterface
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::TRANSFER]) $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::TRANSFER])
->setSourceAccounts($accounts)->excludeDestinationAccounts($accounts) ->setSourceAccounts($accounts)->excludeDestinationAccounts($accounts);
;
if (null !== $categories && $categories->count() > 0) { if (null !== $categories && $categories->count() > 0) {
$collector->setCategories($categories); $collector->setCategories($categories);
} }
@@ -325,10 +326,11 @@ class OperationsRepository implements OperationsRepositoryInterface
{ {
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end) $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
->setTypes([TransactionType::WITHDRAWAL])
;
// 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) { if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts); $collector->setAccounts($accounts);
} }
@@ -340,17 +342,44 @@ class OperationsRepository implements OperationsRepositoryInterface
$journals = $collector->getExtractedJournals(); $journals = $collector->getExtractedJournals();
$array = []; $array = [];
Log::debug(sprintf('Collected %d journals', count($journals)));
foreach ($journals as $journal) { foreach ($journals as $journal) {
// Almost the same as in \FireflyIII\Repositories\Budget\OperationsRepository::sumExpenses
$amount = '0';
$currencyId = (int) $journal['currency_id']; $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] ??= [ $array[$currencyId] ??= [
'sum' => '0', 'sum' => '0',
'currency_id' => (string) $currencyId, 'currency_id' => (string) $currencyId,
'currency_name' => $journal['currency_name'], 'currency_name' => $currencyName,
'currency_symbol' => $journal['currency_symbol'], 'currency_symbol' => $currencySymbol,
'currency_code' => $journal['currency_code'], 'currency_code' => $currencyCode,
'currency_decimal_places' => (int) $journal['currency_decimal_places'], '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; return $array;
@@ -364,8 +393,7 @@ class OperationsRepository implements OperationsRepositoryInterface
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end) $collector->setUser($this->user)->setRange($start, $end)
->setTypes([TransactionType::DEPOSIT]) ->setTypes([TransactionType::DEPOSIT]);
;
if (null !== $accounts && $accounts->count() > 0) { if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts); $collector->setAccounts($accounts);
@@ -401,8 +429,7 @@ class OperationsRepository implements OperationsRepositoryInterface
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end) $collector->setUser($this->user)->setRange($start, $end)
->setTypes([TransactionType::TRANSFER]) ->setTypes([TransactionType::TRANSFER]);
;
if (null !== $accounts && $accounts->count() > 0) { if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts); $collector->setAccounts($accounts);

View File

@@ -30,6 +30,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\UserGroup; use FireflyIII\Models\UserGroup;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class Amount. * Class Amount.
@@ -57,9 +58,11 @@ class Amount
$currency = app('amount')->getDefaultCurrency(); $currency = app('amount')->getDefaultCurrency();
$field = $convertToNative && $currency->id !== $journal['currency_id'] ? 'native_amount' : 'amount'; $field = $convertToNative && $currency->id !== $journal['currency_id'] ? 'native_amount' : 'amount';
$amount = $journal[$field] ?? '0'; $amount = $journal[$field] ?? '0';
//Log::debug(sprintf('Field is %s, amount is %s', $field, $amount));
// fallback, the transaction has a foreign amount in $currency. // 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']; $amount = $journal['foreign_amount'];
//Log::debug(sprintf('Overruled, amount is now %s', $amount));
} }
return $amount; return $amount;
} }

View File

@@ -26,11 +26,13 @@ namespace FireflyIII\Support\Chart\Budget;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface; use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class FrontpageChartGenerator * Class FrontpageChartGenerator
@@ -43,6 +45,9 @@ class FrontpageChartGenerator
private Carbon $end; private Carbon $end;
private string $monthAndDayFormat; private string $monthAndDayFormat;
private Carbon $start; private Carbon $start;
public bool $convertToNative = false;
public TransactionCurrency $default;
/** /**
* FrontpageChartGenerator constructor. * FrontpageChartGenerator constructor.
@@ -62,6 +67,7 @@ class FrontpageChartGenerator
*/ */
public function generate(): array public function generate(): array
{ {
Log::debug('Now in generate for budget chart.');
$budgets = $this->budgetRepository->getActiveBudgets(); $budgets = $this->budgetRepository->getActiveBudgets();
$data = [ $data = [
['label' => (string) trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'], ['label' => (string) trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'],
@@ -74,6 +80,7 @@ class FrontpageChartGenerator
foreach ($budgets as $budget) { foreach ($budgets as $budget) {
$data = $this->processBudget($data, $budget); $data = $this->processBudget($data, $budget);
} }
Log::debug('DONE with generate budget chart.');
return $data; return $data;
} }
@@ -85,15 +92,19 @@ class FrontpageChartGenerator
*/ */
private function processBudget(array $data, Budget $budget): array private function processBudget(array $data, Budget $budget): array
{ {
Log::debug(sprintf('Now processing budget #%d ("%s")', $budget->id, $budget->name));
// get all limits: // get all limits:
$limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end); $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 no limits
if (0 === $limits->count()) { 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;
} }
$result = $this->budgetLimits($data, $budget, $limits);
return $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 private function budgetLimits(array $data, Budget $budget, Collection $limits): array
{ {
Log::debug('Start processing budget limits.');
/** @var BudgetLimit $limit */ /** @var BudgetLimit $limit */
foreach ($limits as $limit) { foreach ($limits as $limit) {
$data = $this->processLimit($data, $budget, $limit); $data = $this->processLimit($data, $budget, $limit);
} }
Log::debug('Done processing budget limits.');
return $data; return $data;
} }
@@ -134,12 +147,24 @@ class FrontpageChartGenerator
*/ */
private function processLimit(array $data, Budget $budget, BudgetLimit $limit): array 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 */ /** @var array $entry */
foreach ($spent as $entry) { foreach ($spent as $entry) {
// only spent the entry where the entry's currency matches the budget limit's currency // 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); $data = $this->processRow($data, $budget, $limit, $entry);
} }
} }
@@ -166,10 +191,14 @@ class FrontpageChartGenerator
); );
} }
$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[0]['entries'][$title] = bcadd($data[0]['entries'][$title], 1 === bccomp($sumSpent, $limit->amount) ? $limit->amount : $sumSpent); // spent
$data[2]['entries'][$title] = 1 === bccomp($limit->amount, $sumSpent) ? '0' : bcmul(bcadd($entry['sum'], $limit->amount), '-1'); // overspent $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; return $data;
} }

View File

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

View File

@@ -46,14 +46,15 @@ trait ChartGeneration
protected function accountBalanceChart(Collection $accounts, Carbon $start, Carbon $end): array // chart helper method. protected function accountBalanceChart(Collection $accounts, Carbon $start, Carbon $end): array // chart helper method.
{ {
// chart properties for cache: // chart properties for cache:
$convertToNative = app('preferences')->get('convert_to_native', false)->data;
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('chart.account.account-balance-chart'); $cache->addProperty('chart.account.account-balance-chart');
$cache->addProperty($accounts); $cache->addProperty($accounts);
$convertToNative = app('preferences')->get('convert_to_native', false)->data; $cache->addProperty($convertToNative);
if ($cache->has()) { if ($cache->has()) {
// return $cache->get(); return $cache->get();
} }
app('log')->debug('Regenerate chart.account.account-balance-chart from scratch.'); app('log')->debug('Regenerate chart.account.account-balance-chart from scratch.');
$locale = app('steam')->getLocale(); $locale = app('steam')->getLocale();
@@ -69,16 +70,10 @@ trait ChartGeneration
/** @var Account $account */ /** @var Account $account */
foreach ($accounts as $account) { foreach ($accounts as $account) {
// TODO we can use getAccountCurrency instead. $currency = $accountRepos->getAccountCurrency($account) ?? $default;
$currency = $accountRepos->getAccountCurrency($account); $useNative = $convertToNative && $default->id !== $currency->id;
if (null === $currency) { $field =$useNative ? 'native_balance' : 'balance';
$currency = $default; $currency = $useNative ? $default : $currency;
}
// if the user prefers the native currency, overrule the currency of the account.
if ($currency->id !== $default->id && $convertToNative) {
$currency = $default;
}
$currentSet = [ $currentSet = [
'label' => $account->name, 'label' => $account->name,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
@@ -94,7 +89,7 @@ trait ChartGeneration
$balance = $range[$format] ?? $previous; $balance = $range[$format] ?? $previous;
$previous = $balance; $previous = $balance;
$currentStart->addDay(); $currentStart->addDay();
$currentSet['entries'][$label] = $balance['balance']; // TODO or native_balance $currentSet['entries'][$label] = $balance[$field];
} }
$chartData[] = $currentSet; $chartData[] = $currentSet;
} }

View File

@@ -116,8 +116,7 @@ class Steam
\DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'), \DB::raw('SUM(transactions.foreign_amount) AS modified_foreign'),
\DB::raw('SUM(transactions.native_amount) AS modified_native'), \DB::raw('SUM(transactions.native_amount) AS modified_native'),
] ]
) );
;
$currentBalance = $startBalance; $currentBalance = $startBalance;
@@ -262,7 +261,14 @@ class Steam
public function finalAccountBalance(Account $account, Carbon $date): array public function finalAccountBalance(Account $account, Carbon $date): array
{ {
$native = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup); $native = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
$currency = $this->getAccountCurrency($account) ?? $native; $accountCurrency = $this->getAccountCurrency($account);
$hasCurrency = null !== $accountCurrency;
$currency = $accountCurrency ?? $native;
if (!$hasCurrency) {
Log::debug('Gave account fake currency.');
// fake currency
$native = new TransactionCurrency();
}
$return = [ $return = [
'native_balance' => '0', 'native_balance' => '0',
]; ];
@@ -272,25 +278,24 @@ class Steam
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->where('transactions.transaction_currency_id', $currency->id) ->where('transactions.transaction_currency_id', $currency->id)
->get(['transactions.amount'])->toArray() ->get(['transactions.amount'])->toArray();
;
$return['balance'] = $this->sumTransactions($array, 'amount'); $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: // add virtual balance:
$return['balance'] = bcadd('' === (string) $account->virtual_balance ? '0' : $account->virtual_balance, $return['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( // then, native balance (if necessary(
if ($native->id !== $currency->id) { if ($native->id !== $currency->id) {
Log::debug('Will grab native balance for transactions on this account.');
$array = $account->transactions() $array = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->get(['transactions.native_amount'])->toArray() ->get(['transactions.native_amount'])->toArray();
;
$return['native_balance'] = $this->sumTransactions($array, 'native_amount'); $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']); $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. // balance(s) in other currencies.
@@ -298,10 +303,17 @@ class Steam
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
->get(['transaction_currencies.code', 'transactions.amount'])->toArray() ->get(['transaction_currencies.code', 'transactions.amount'])->toArray();
;
$others = $this->groupAndSumTransactions($array, 'code', 'amount'); $others = $this->groupAndSumTransactions($array, 'code', 'amount');
// Log::debug('All others are (joined)', $others);
// 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); return array_merge($return, $others);
} }

View File

@@ -69,26 +69,31 @@ class General extends AbstractExtension
$date = session('end', today(config('app.timezone'))->endOfMonth()); $date = session('end', today(config('app.timezone'))->endOfMonth());
$info = Steam::finalAccountBalance($account, $date); $info = Steam::finalAccountBalance($account, $date);
$currency = Steam::getAccountCurrency($account); $currency = Steam::getAccountCurrency($account);
$native = Amount::getDefaultCurrency(); $default = Amount::getDefaultCurrency();
$convertToNative = app('preferences')->get('convert_to_native', false)->data; $convertToNative = app('preferences')->get('convert_to_native', false)->data;
$useNative = $convertToNative && $default->id !== $currency->id;
$strings = []; $strings = [];
foreach ($info as $key => $balance) { foreach ($info as $key => $balance) {
if ('balance' === $key) { if ('balance' === $key) {
// balance in account currency. // balance in account currency.
if (!$convertToNative || $currency->code === $native->code) { if (!$useNative) {
$strings[] = app('amount')->formatAnything($currency, $balance, false); $strings[] = app('amount')->formatAnything($currency, $balance, false);
} }
if($useNative) {
$strings[] =sprintf('(%s)', app('amount')->formatAnything($currency, $balance, false));
}
continue; continue;
} }
if ('native_balance' === $key) { if ('native_balance' === $key) {
// balance in native currency. // balance in native currency.
if ($convertToNative) { if ($useNative) {
$strings[] = app('amount')->formatAnything($native, $balance, false); $strings[] = app('amount')->formatAnything($default, $balance, false);
} }
continue; continue;
} }
// for multi currency accounts.
if ($key !== $currency->code) { if ($key !== $currency->code) {
$strings[] = app('amount')->formatAnything(TransactionCurrency::where('code', $key)->first(), $balance, false); $strings[] = app('amount')->formatAnything(TransactionCurrency::where('code', $key)->first(), $balance, false);
} }