mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-03 11:08:28 +00:00
Update category overview to be multi-currency aware.
This commit is contained in:
@@ -50,7 +50,7 @@ class BudgetController extends Controller
|
|||||||
use CleansChartData;
|
use CleansChartData;
|
||||||
use ValidatesUserGroupTrait;
|
use ValidatesUserGroupTrait;
|
||||||
|
|
||||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||||
|
|
||||||
protected OperationsRepositoryInterface $opsRepository;
|
protected OperationsRepositoryInterface $opsRepository;
|
||||||
private BudgetLimitRepositoryInterface $blRepository;
|
private BudgetLimitRepositoryInterface $blRepository;
|
||||||
@@ -81,15 +81,15 @@ class BudgetController extends Controller
|
|||||||
*
|
*
|
||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
*/
|
*/
|
||||||
public function dashboard(DateRequest $request): JsonResponse
|
public function overview(DateRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
$params = $request->getAll();
|
$params = $request->getAll();
|
||||||
|
|
||||||
/** @var Carbon $start */
|
/** @var Carbon $start */
|
||||||
$start = $params['start'];
|
$start = $params['start'];
|
||||||
|
|
||||||
/** @var Carbon $end */
|
/** @var Carbon $end */
|
||||||
$end = $params['end'];
|
$end = $params['end'];
|
||||||
|
|
||||||
// code from FrontpageChartGenerator, but not in separate class
|
// code from FrontpageChartGenerator, but not in separate class
|
||||||
$budgets = $this->repository->getActiveBudgets();
|
$budgets = $this->repository->getActiveBudgets();
|
||||||
@@ -116,12 +116,12 @@ class BudgetController extends Controller
|
|||||||
$expenses = $this->processExpenses($budget->id, $spent, $start, $end);
|
$expenses = $this->processExpenses($budget->id, $spent, $start, $end);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int $currencyId
|
* @var int $currencyId
|
||||||
* @var array $row
|
* @var array $row
|
||||||
*/
|
*/
|
||||||
foreach ($expenses as $currencyId => $row) {
|
foreach ($expenses as $currencyId => $row) {
|
||||||
// budgeted, left and overspent are now 0.
|
// budgeted, left and overspent are now 0.
|
||||||
$limit = $this->filterLimit($currencyId, $limits);
|
$limit = $this->filterLimit($currencyId, $limits);
|
||||||
if (null !== $limit) {
|
if (null !== $limit) {
|
||||||
$row['budgeted'] = $limit->amount;
|
$row['budgeted'] = $limit->amount;
|
||||||
$row['left'] = bcsub($row['budgeted'], bcmul($row['spent'], '-1'));
|
$row['left'] = bcsub($row['budgeted'], bcmul($row['spent'], '-1'));
|
||||||
@@ -140,7 +140,7 @@ class BudgetController extends Controller
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// is always an array
|
// is always an array
|
||||||
$return = [];
|
$return = [];
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
$current = [
|
$current = [
|
||||||
'label' => $budget->name,
|
'label' => $budget->name,
|
||||||
@@ -149,14 +149,20 @@ class BudgetController extends Controller
|
|||||||
'currency_name' => $row['currency_name'],
|
'currency_name' => $row['currency_name'],
|
||||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||||
'period' => null,
|
'period' => null,
|
||||||
'start' => $row['start'],
|
'date' => $row['start'],
|
||||||
'end' => $row['end'],
|
'start_date' => $row['start'],
|
||||||
|
'end_date' => $row['end'],
|
||||||
|
'yAxisID' => 0,
|
||||||
|
'type' => 'bar',
|
||||||
'entries' => [
|
'entries' => [
|
||||||
'budgeted' => $row['budgeted'],
|
'budgeted' => $row['budgeted'],
|
||||||
'spent' => $row['spent'],
|
'spent' => $row['spent'],
|
||||||
'left' => $row['left'],
|
'left' => $row['left'],
|
||||||
'overspent' => $row['overspent'],
|
'overspent' => $row['overspent'],
|
||||||
],
|
],
|
||||||
|
'pc_entries' => [
|
||||||
|
|
||||||
|
],
|
||||||
];
|
];
|
||||||
$return[] = $current;
|
$return[] = $current;
|
||||||
}
|
}
|
||||||
@@ -191,7 +197,7 @@ class BudgetController extends Controller
|
|||||||
* This array contains the expenses in this budget. Grouped per currency.
|
* This array contains the expenses in this budget. Grouped per currency.
|
||||||
* The grouping is on the main currency only.
|
* The grouping is on the main currency only.
|
||||||
*
|
*
|
||||||
* @var int $currencyId
|
* @var int $currencyId
|
||||||
* @var array $block
|
* @var array $block
|
||||||
*/
|
*/
|
||||||
foreach ($spent as $currencyId => $block) {
|
foreach ($spent as $currencyId => $block) {
|
||||||
@@ -209,7 +215,7 @@ class BudgetController extends Controller
|
|||||||
'left' => '0',
|
'left' => '0',
|
||||||
'overspent' => '0',
|
'overspent' => '0',
|
||||||
];
|
];
|
||||||
$currentBudgetArray = $block['budgets'][$budgetId];
|
$currentBudgetArray = $block['budgets'][$budgetId];
|
||||||
|
|
||||||
// var_dump($return);
|
// var_dump($return);
|
||||||
/** @var array $journal */
|
/** @var array $journal */
|
||||||
@@ -250,7 +256,7 @@ class BudgetController extends Controller
|
|||||||
private function processLimit(Budget $budget, BudgetLimit $limit): array
|
private function processLimit(Budget $budget, BudgetLimit $limit): array
|
||||||
{
|
{
|
||||||
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
|
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
|
||||||
$end = clone $limit->end_date;
|
$end = clone $limit->end_date;
|
||||||
$end->endOfDay();
|
$end->endOfDay();
|
||||||
$spent = $this->opsRepository->listExpenses($limit->start_date, $end, null, new Collection([$budget]));
|
$spent = $this->opsRepository->listExpenses($limit->start_date, $end, null, new Collection([$budget]));
|
||||||
$limitCurrencyId = $limit->transaction_currency_id;
|
$limitCurrencyId = $limit->transaction_currency_id;
|
||||||
@@ -258,8 +264,8 @@ class BudgetController extends Controller
|
|||||||
/** @var array $entry */
|
/** @var array $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
|
||||||
// so $filtered will only have 1 or 0 entries
|
// so $filtered will only have 1 or 0 entries
|
||||||
$filtered = array_filter($spent, fn ($entry) => $entry['currency_id'] === $limitCurrencyId);
|
$filtered = array_filter($spent, fn($entry) => $entry['currency_id'] === $limitCurrencyId);
|
||||||
$result = $this->processExpenses($budget->id, $filtered, $limit->start_date, $end);
|
$result = $this->processExpenses($budget->id, $filtered, $limit->start_date, $end);
|
||||||
if (1 === count($result)) {
|
if (1 === count($result)) {
|
||||||
$compare = bccomp($limit->amount, (string)app('steam')->positive($result[$limitCurrencyId]['spent']));
|
$compare = bccomp($limit->amount, (string)app('steam')->positive($result[$limitCurrencyId]['spent']));
|
||||||
$result[$limitCurrencyId]['budgeted'] = $limit->amount;
|
$result[$limitCurrencyId]['budgeted'] = $limit->amount;
|
||||||
|
@@ -34,6 +34,7 @@ use FireflyIII\Exceptions\FireflyException;
|
|||||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||||
|
use FireflyIII\Support\Facades\Steam;
|
||||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||||
@@ -77,10 +78,10 @@ class CategoryController extends Controller
|
|||||||
*
|
*
|
||||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||||
*/
|
*/
|
||||||
public function dashboard(DateRequest $request): JsonResponse
|
public function overview(DateRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
/** @var Carbon $start */
|
/** @var Carbon $start */
|
||||||
$start = $this->parameters->get('start');
|
$start = $this->parameters->get('start');
|
||||||
|
|
||||||
/** @var Carbon $end */
|
/** @var Carbon $end */
|
||||||
$end = $this->parameters->get('end');
|
$end = $this->parameters->get('end');
|
||||||
@@ -91,11 +92,11 @@ class CategoryController extends Controller
|
|||||||
|
|
||||||
// get journals for entire period:
|
// get journals for entire period:
|
||||||
/** @var GroupCollectorInterface $collector */
|
/** @var GroupCollectorInterface $collector */
|
||||||
$collector = app(GroupCollectorInterface::class);
|
$collector = app(GroupCollectorInterface::class);
|
||||||
$collector->setRange($start, $end)->withAccountInformation();
|
$collector->setRange($start, $end)->withAccountInformation();
|
||||||
$collector->setXorAccounts($accounts)->withCategoryInformation();
|
$collector->setXorAccounts($accounts)->withCategoryInformation();
|
||||||
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::RECONCILIATION->value]);
|
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::RECONCILIATION->value]);
|
||||||
$journals = $collector->getExtractedJournals();
|
$journals = $collector->getExtractedJournals();
|
||||||
|
|
||||||
/** @var array $journal */
|
/** @var array $journal */
|
||||||
foreach ($journals as $journal) {
|
foreach ($journals as $journal) {
|
||||||
@@ -108,44 +109,62 @@ class CategoryController extends Controller
|
|||||||
$currencyCode = (string)$currency->code;
|
$currencyCode = (string)$currency->code;
|
||||||
$currencySymbol = (string)$currency->symbol;
|
$currencySymbol = (string)$currency->symbol;
|
||||||
$currencyDecimalPlaces = (int)$currency->decimal_places;
|
$currencyDecimalPlaces = (int)$currency->decimal_places;
|
||||||
$amount = app('steam')->positive($journal['amount']);
|
$amount = Steam::positive($journal['amount']);
|
||||||
|
$pcAmount = null;
|
||||||
|
|
||||||
// overrule if necessary:
|
// overrule if necessary:
|
||||||
|
if ($this->convertToPrimary && $journalCurrencyId === $this->primaryCurrency->id) {
|
||||||
|
$pcAmount = $amount;
|
||||||
|
}
|
||||||
if ($this->convertToPrimary && $journalCurrencyId !== $this->primaryCurrency->id) {
|
if ($this->convertToPrimary && $journalCurrencyId !== $this->primaryCurrency->id) {
|
||||||
$currencyId = (int)$this->primaryCurrency->id;
|
$currencyId = (int)$this->primaryCurrency->id;
|
||||||
$currencyName = (string)$this->primaryCurrency->name;
|
$currencyName = (string)$this->primaryCurrency->name;
|
||||||
$currencyCode = (string)$this->primaryCurrency->code;
|
$currencyCode = (string)$this->primaryCurrency->code;
|
||||||
$currencySymbol = (string)$this->primaryCurrency->symbol;
|
$currencySymbol = (string)$this->primaryCurrency->symbol;
|
||||||
$currencyDecimalPlaces = (int)$this->primaryCurrency->decimal_places;
|
$currencyDecimalPlaces = (int)$this->primaryCurrency->decimal_places;
|
||||||
$convertedAmount = $converter->convert($currency, $this->primaryCurrency, $journal['date'], $amount);
|
$pcAmount = $converter->convert($currency, $this->primaryCurrency, $journal['date'], $amount);
|
||||||
Log::debug(sprintf('Converted %s %s to %s %s', $journal['currency_code'], $amount, $this->primaryCurrency->code, $convertedAmount));
|
Log::debug(sprintf('Converted %s %s to %s %s', $journal['currency_code'], $amount, $this->primaryCurrency->code, $pcAmount));
|
||||||
$amount = $convertedAmount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
|
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
|
||||||
$key = sprintf('%s-%s', $categoryName, $currencyCode);
|
$key = sprintf('%s-%s', $categoryName, $currencyCode);
|
||||||
// create arrays
|
// create arrays
|
||||||
$return[$key] ??= [
|
$return[$key] ??= [
|
||||||
'label' => $categoryName,
|
'label' => $categoryName,
|
||||||
'currency_id' => (string)$currencyId,
|
'currency_id' => (string)$currencyId,
|
||||||
'currency_code' => $currencyCode,
|
'currency_name' => $currencyName,
|
||||||
'currency_name' => $currencyName,
|
'currency_code' => $currencyCode,
|
||||||
'currency_symbol' => $currencySymbol,
|
'currency_symbol' => $currencySymbol,
|
||||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||||
'period' => null,
|
'primary_currency_id' => (string)$this->primaryCurrency->id,
|
||||||
'start' => $start->toAtomString(),
|
'primary_currency_name' => (string)$this->primaryCurrency->name,
|
||||||
'end' => $end->toAtomString(),
|
'primary_currency_code' => (string)$this->primaryCurrency->code,
|
||||||
'amount' => '0',
|
'primary_currency_symbol' => (string)$this->primaryCurrency->symbol,
|
||||||
|
'primary_currency_decimal_places' => (int)$this->primaryCurrency->decimal_places,
|
||||||
|
'period' => null,
|
||||||
|
'start_date' => $start->toAtomString(),
|
||||||
|
'end_date' => $end->toAtomString(),
|
||||||
|
'yAxisID' => 0,
|
||||||
|
'type' => 'bar',
|
||||||
|
'entries' => [
|
||||||
|
'spent' => '0'
|
||||||
|
],
|
||||||
|
'pc_entries' => [
|
||||||
|
'spent' => '0'
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// add monies
|
// add monies
|
||||||
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string)$amount);
|
$return[$key]['entries']['spent'] = bcadd($return[$key]['entries']['spent'], (string)$amount);
|
||||||
|
if (null !== $pcAmount) {
|
||||||
|
$return[$key]['pc_entries']['spent'] = bcadd($return[$key]['pc_entries']['spent'], (string)$pcAmount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$return = array_values($return);
|
$return = array_values($return);
|
||||||
|
|
||||||
// order by amount
|
// order by amount
|
||||||
usort($return, static fn (array $a, array $b) => (float)$a['amount'] < (float)$b['amount'] ? 1 : -1);
|
usort($return, static fn(array $a, array $b) => (float)$a['entries']['spent'] < (float)$b['entries']['spent'] ? 1 : -1);
|
||||||
|
|
||||||
return response()->json($this->clean($return));
|
return response()->json($this->clean($return));
|
||||||
}
|
}
|
||||||
|
@@ -80,6 +80,7 @@ class AccountBalanceGrouped
|
|||||||
'start_date' => $this->start->toAtomString(),
|
'start_date' => $this->start->toAtomString(),
|
||||||
'end_date' => $this->end->toAtomString(),
|
'end_date' => $this->end->toAtomString(),
|
||||||
'yAxisID' => 0,
|
'yAxisID' => 0,
|
||||||
|
'type' => 'line',
|
||||||
'period' => $this->preferredRange,
|
'period' => $this->preferredRange,
|
||||||
'entries' => [],
|
'entries' => [],
|
||||||
'pc_entries' => [],
|
'pc_entries' => [],
|
||||||
@@ -97,6 +98,7 @@ class AccountBalanceGrouped
|
|||||||
'date' => $this->start->toAtomString(),
|
'date' => $this->start->toAtomString(),
|
||||||
'start_date' => $this->start->toAtomString(),
|
'start_date' => $this->start->toAtomString(),
|
||||||
'end_date' => $this->end->toAtomString(),
|
'end_date' => $this->end->toAtomString(),
|
||||||
|
'type' => 'line',
|
||||||
'yAxisID' => 0,
|
'yAxisID' => 0,
|
||||||
'period' => $this->preferredRange,
|
'period' => $this->preferredRange,
|
||||||
'entries' => [],
|
'entries' => [],
|
||||||
|
@@ -61,7 +61,10 @@ trait CleansChartData
|
|||||||
if (array_key_exists('primary_currency_id', $array)) {
|
if (array_key_exists('primary_currency_id', $array)) {
|
||||||
$array['primary_currency_id'] = (string)$array['primary_currency_id'];
|
$array['primary_currency_id'] = (string)$array['primary_currency_id'];
|
||||||
}
|
}
|
||||||
$required = ['start_date', 'end_date', 'period', 'yAxisID'];
|
$required = [
|
||||||
|
'start_date', 'end_date', 'period', 'yAxisID','type','entries','pc_entries',
|
||||||
|
'currency_id', 'primary_currency_id'
|
||||||
|
];
|
||||||
foreach ($required as $field) {
|
foreach ($required as $field) {
|
||||||
if (!array_key_exists($field, $array)) {
|
if (!array_key_exists($field, $array)) {
|
||||||
throw new FireflyException(sprintf('Data-set "%s" is missing the "%s"-variable.', $index, $field));
|
throw new FireflyException(sprintf('Data-set "%s" is missing the "%s"-variable.', $index, $field));
|
||||||
|
@@ -128,7 +128,7 @@ Route::group(
|
|||||||
'as' => 'api.v1.chart.budget.',
|
'as' => 'api.v1.chart.budget.',
|
||||||
],
|
],
|
||||||
static function (): void {
|
static function (): void {
|
||||||
Route::get('dashboard', ['uses' => 'BudgetController@dashboard', 'as' => 'dashboard']);
|
Route::get('overview', ['uses' => 'BudgetController@overview', 'as' => 'overview']);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ Route::group(
|
|||||||
'as' => 'api.v1.chart.category.',
|
'as' => 'api.v1.chart.category.',
|
||||||
],
|
],
|
||||||
static function (): void {
|
static function (): void {
|
||||||
Route::get('dashboard', ['uses' => 'CategoryController@dashboard', 'as' => 'dashboard']);
|
Route::get('overview', ['uses' => 'CategoryController@overview', 'as' => 'overview']);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user