This commit is contained in:
James Cole
2016-12-15 13:47:28 +01:00
parent 482688ac3c
commit b58bc97422
7 changed files with 305 additions and 458 deletions

View File

@@ -52,11 +52,16 @@ class ChartJsGenerator implements GeneratorInterface
*/ */
public function multiSet(array $data): array public function multiSet(array $data): array
{ {
reset($data);
$first = current($data);
$labels = array_keys($first['entries']);
$chartData = [ $chartData = [
'count' => count($data), 'count' => count($data),
'labels' => array_keys($data[0]['entries']), // take ALL labels from the first set. 'labels' => $labels, // take ALL labels from the first set.
'datasets' => [], 'datasets' => [],
]; ];
unset($first, $labels);
foreach ($data as $set) { foreach ($data as $set) {
$chartData['datasets'][] = [ $chartData['datasets'][] = [

View File

@@ -1,67 +0,0 @@
<?php
/**
* CategoryChartGeneratorInterface.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Chart\Category;
use Illuminate\Support\Collection;
/**
* Interface CategoryChartGeneratorInterface
*
* @package FireflyIII\Generator\Chart\Category
*/
interface CategoryChartGeneratorInterface
{
/**
* @param Collection $entries
*
* @return array
*/
public function all(Collection $entries): array;
/**
* @param Collection $entries
*
* @return array
*/
public function frontpage(Collection $entries): array;
/**
* @param array $entries
*
* @return array
*/
public function mainReportChart(array $entries): array;
/**
* @param Collection $entries
*
* @return array
*/
public function period(Collection $entries): array;
/**
* @param array $data
*
* @return array
*/
public function pieChart(array $data): array;
/**
* @param array $entries
*
* @return array
*/
public function reportPeriod(array $entries): array;
}

View File

@@ -1,209 +0,0 @@
<?php
/**
* ChartJsCategoryChartGenerator.php
* Copyright (C) 2016 thegrumpydictator@gmail.com
*
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types = 1);
namespace FireflyIII\Generator\Chart\Category;
use FireflyIII\Support\ChartColour;
use Illuminate\Support\Collection;
/**
* Class ChartJsCategoryChartGenerator
*
* @package FireflyIII\Generator\Chart\Category
*/
class ChartJsCategoryChartGenerator implements CategoryChartGeneratorInterface
{
/**
* @param Collection $entries
*
* @return array
*/
public function all(Collection $entries): array
{
$data = [
'count' => 2,
'labels' => [],
'datasets' => [
[
'label' => trans('firefly.spent'),
'data' => [],
],
[
'label' => trans('firefly.earned'),
'data' => [],
],
],
];
foreach ($entries as $entry) {
$data['labels'][] = $entry[1];
$spent = $entry[2];
$earned = $entry[3];
$data['datasets'][0]['data'][] = bccomp($spent, '0') === 0 ? null : round(bcmul($spent, '-1'), 4);
$data['datasets'][1]['data'][] = bccomp($earned, '0') === 0 ? null : round($earned, 4);
}
return $data;
}
/**
* @param Collection $entries
*
* @return array
*/
public function frontpage(Collection $entries): array
{
$data = [
'count' => 1,
'labels' => [],
'datasets' => [
[
'label' => trans('firefly.spent'),
'data' => [],
],
],
];
foreach ($entries as $entry) {
if ($entry->spent != 0) {
$data['labels'][] = $entry->name;
$data['datasets'][0]['data'][] = round(bcmul($entry->spent, '-1'), 2);
}
}
return $data;
}
/**
* @param array $entries
*
* @return array
*/
public function mainReportChart(array $entries): array
{
$data = [
'count' => 0,
'labels' => array_keys($entries),
'datasets' => [],
];
foreach ($entries as $row) {
foreach ($row['in'] as $categoryId => $amount) {
// get in:
$data['datasets'][$categoryId . 'in']['data'][] = round($amount, 2);
// get out:
$opposite = $row['out'][$categoryId];
$data['datasets'][$categoryId . 'out']['data'][] = round($opposite, 2);
// set name:
$data['datasets'][$categoryId . 'out']['label'] = $row['name'][$categoryId] . ' (' . strtolower(strval(trans('firefly.expenses'))) . ')';
$data['datasets'][$categoryId . 'in']['label'] = $row['name'][$categoryId] . ' (' . strtolower(strval(trans('firefly.income'))) . ')';
}
}
// remove empty rows:
foreach ($data['datasets'] as $key => $content) {
if (array_sum($content['data']) === 0.0) {
unset($data['datasets'][$key]);
}
}
// re-key the datasets array:
$data['datasets'] = array_values($data['datasets']);
$data['count'] = count($data['datasets']);
return $data;
}
/**
*
* @param Collection $entries
*
* @return array
*/
public function period(Collection $entries): array
{
return $this->all($entries);
}
/**
* @param array $entries
*
* @return array
*/
public function pieChart(array $entries): array
{
$data = [
'datasets' => [
0 => [],
],
'labels' => [],
];
$index = 0;
foreach ($entries as $entry) {
if (bccomp($entry['amount'], '0') === -1) {
$entry['amount'] = bcmul($entry['amount'], '-1');
}
$data['datasets'][0]['data'][] = round($entry['amount'], 2);
$data['datasets'][0]['backgroundColor'][] = ChartColour::getColour($index);
$data['labels'][] = $entry['name'];
$index++;
}
return $data;
}
/**
* @param array $entries
*
* @return array
*/
public function reportPeriod(array $entries): array
{
$data = [
'labels' => array_keys($entries),
'datasets' => [
0 => [
'label' => trans('firefly.earned'),
'data' => [],
],
1 => [
'label' => trans('firefly.spent'),
'data' => [],
],
],
'count' => 2,
];
foreach ($entries as $label => $entry) {
// data set 0 is budgeted
// data set 1 is spent:
$data['datasets'][0]['data'][] = round($entry['earned'], 2);
$data['datasets'][1]['data'][] = round(bcmul($entry['spent'], '-1'), 2);
}
return $data;
}
}

View File

@@ -15,7 +15,7 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface; use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Category; use FireflyIII\Models\Category;
@@ -26,7 +26,6 @@ use Illuminate\Support\Collection;
use Navigation; use Navigation;
use Preferences; use Preferences;
use Response; use Response;
use stdClass;
/** /**
* Class CategoryController * Class CategoryController
@@ -35,7 +34,7 @@ use stdClass;
*/ */
class CategoryController extends Controller class CategoryController extends Controller
{ {
/** @var CategoryChartGeneratorInterface */ /** @var GeneratorInterface */
protected $generator; protected $generator;
/** /**
@@ -45,7 +44,7 @@ class CategoryController extends Controller
{ {
parent::__construct(); parent::__construct();
// create chart generator: // create chart generator:
$this->generator = app(CategoryChartGeneratorInterface::class); $this->generator = app(GeneratorInterface::class);
} }
/** /**
@@ -59,34 +58,42 @@ class CategoryController extends Controller
*/ */
public function all(CRI $repository, AccountRepositoryInterface $accountRepository, Category $category) public function all(CRI $repository, AccountRepositoryInterface $accountRepository, Category $category)
{ {
$start = $repository->firstUseDate($category); $cache = new CacheProperties;
$range = Preferences::get('viewRange', '1M')->data; $cache->addProperty('chart.category.all');
$start = Navigation::startOfPeriod($start, $range); $cache->addProperty($category->id);
$categoryCollection = new Collection([$category]);
$end = new Carbon;
$entries = new Collection;
$cache = new CacheProperties;
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('all');
$cache->addProperty('categories');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get());
} }
$start = $repository->firstUseDate($category);
$range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($start, $range);
$end = new Carbon;
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$chartData = [
[
'label' => strval(trans('firefly.spent')),
'entries' => [],
'type' => 'bar',
],
[
'label' => strval(trans('firefly.earned')),
'entries' => [],
'type' => 'bar',
],
];
while ($start <= $end) { while ($start <= $end) {
$currentEnd = Navigation::endOfPeriod($start, $range); $currentEnd = Navigation::endOfPeriod($start, $range);
$spent = $repository->spentInPeriod($categoryCollection, $accounts, $start, $currentEnd); $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
$earned = $repository->earnedInPeriod($categoryCollection, $accounts, $start, $currentEnd); $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $currentEnd);
$date = Navigation::periodShow($start, $range); $label = Navigation::periodShow($start, $range);
$entries->push([clone $start, $date, $spent, $earned]); $chartData[0]['entries'][$label] = bcmul($spent, '-1');
$start = Navigation::addPeriod($start, $range, 0); $chartData[1]['entries'][$label] = $earned;
$start = Navigation::addPeriod($start, $range, 0);
} }
$entries = $entries->reverse();
$entries = $entries->slice(0, 48); $data = $this->generator->multiSet($chartData);
$entries = $entries->reverse();
$data = $this->generator->all($entries);
$cache->store($data); $cache->store($data);
return Response::json($data); return Response::json($data);
@@ -122,34 +129,29 @@ class CategoryController extends Controller
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('category'); $cache->addProperty('chart.category.frontpage');
$cache->addProperty('frontpage');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); return Response::json($cache->get());
} }
$chartData = [];
$categories = $repository->getCategories(); $categories = $repository->getCategories();
$accounts = $accountRepository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]); $accounts = $accountRepository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
$set = new Collection;
/** @var Category $category */ /** @var Category $category */
foreach ($categories as $category) { foreach ($categories as $category) {
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end); $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
if (bccomp($spent, '0') === -1) { if (bccomp($spent, '0') === -1) {
$category->spent = $spent; $chartData[$category->name] = bcmul($spent, '-1');
$set->push($category);
} }
} }
// this is a "fake" entry for the "no category" entry. $chartData[strval(trans('firefly.no_category'))] = bcmul($repository->spentInPeriodWithoutCategory(new Collection, $start, $end), '-1');
$entry = new stdClass;
$entry->name = trans('firefly.no_category');
$entry->spent = $repository->spentInPeriodWithoutCategory(new Collection, $start, $end);
$set->push($entry);
$set = $set->sortBy('spent'); // sort
$data = $this->generator->frontpage($set); arsort($chartData);
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
$cache->store($data); $cache->store($data);
return Response::json($data); return Response::json($data);
} }
/** /**
@@ -166,35 +168,43 @@ class CategoryController extends Controller
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('category-period-chart'); $cache->addProperty('chart.category.period');
$cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($accounts->pluck('id')->toArray());
$cache->addProperty($category); $cache->addProperty($category);
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get();
} }
$expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end); $expenses = $repository->periodExpenses(new Collection([$category]), $accounts, $start, $end);
$income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end); $income = $repository->periodIncome(new Collection([$category]), $accounts, $start, $end);
$periods = Navigation::listOfPeriods($start, $end); $periods = Navigation::listOfPeriods($start, $end);
$chartData = [
[
'label' => strval(trans('firefly.spent')),
'entries' => [],
'type' => 'bar',
],
[
'label' => strval(trans('firefly.earned')),
'entries' => [],
'type' => 'bar',
],
];
// join them:
$result = [];
foreach (array_keys($periods) as $period) { foreach (array_keys($periods) as $period) {
$nice = $periods[$period]; $label = $periods[$period];
$result[$nice] = [ $spent = $expenses[$category->id]['entries'][$period] ?? '0';
'earned' => $income[$category->id]['entries'][$period] ?? '0', $chartData[0]['entries'][$label] = bcmul($spent, '-1');
'spent' => $expenses[$category->id]['entries'][$period] ?? '0', $chartData[1]['entries'][$label] = $income[$category->id]['entries'][$period] ?? '0';
];
} }
$data = $this->generator->reportPeriod($result);
$data = $this->generator->multiSet($chartData);
$cache->store($data);
return Response::json($data); return Response::json($data);
} }
/** /**
* @param CRI $repository * @param CRI $repository
* @param Category $category
* @param Collection $accounts * @param Collection $accounts
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
@@ -206,26 +216,36 @@ class CategoryController extends Controller
$cache = new CacheProperties; $cache = new CacheProperties;
$cache->addProperty($start); $cache->addProperty($start);
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty('no-category-period-chart'); $cache->addProperty('chart.category.period.no-cat');
$cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get();
} }
$expenses = $repository->periodExpensesNoCategory($accounts, $start, $end); $expenses = $repository->periodExpensesNoCategory($accounts, $start, $end);
$income = $repository->periodIncomeNoCategory($accounts, $start, $end); $income = $repository->periodIncomeNoCategory($accounts, $start, $end);
$periods = Navigation::listOfPeriods($start, $end); $periods = Navigation::listOfPeriods($start, $end);
$chartData = [
[
'label' => strval(trans('firefly.spent')),
'entries' => [],
'type' => 'bar',
],
[
'label' => strval(trans('firefly.earned')),
'entries' => [],
'type' => 'bar',
],
];
// join them:
$result = [];
foreach (array_keys($periods) as $period) { foreach (array_keys($periods) as $period) {
$nice = $periods[$period]; $label = $periods[$period];
$result[$nice] = [ $spent = $expenses['entries'][$period] ?? '0';
'earned' => $income['entries'][$period] ?? '0', $chartData[0]['entries'][$label] = bcmul($spent, '-1');
'spent' => $expenses['entries'][$period] ?? '0', $chartData[1]['entries'][$label] = $income['entries'][$period] ?? '0';
];
} }
$data = $this->generator->reportPeriod($result); $data = $this->generator->multiSet($chartData);
$cache->store($data);
return Response::json($data); return Response::json($data);
} }
@@ -260,33 +280,47 @@ class CategoryController extends Controller
*/ */
private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end) private function makePeriodChart(CRI $repository, Category $category, Carbon $start, Carbon $end)
{ {
$categoryCollection = new Collection([$category]); $cache = new CacheProperties;
$cache = new CacheProperties; $cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($category->id);
$cache->addProperty('chart.category.period-chart');
/** @var AccountRepositoryInterface $accountRepository */ /** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class); $accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
$cache->addProperty($category->id);
$cache->addProperty('specific-period');
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); return $cache->get();
} }
$entries = new Collection;
// chart data
$chartData = [
[
'label' => strval(trans('firefly.spent')),
'entries' => [],
'type' => 'bar',
],
[
'label' => strval(trans('firefly.earned')),
'entries' => [],
'type' => 'bar',
],
];
while ($start <= $end) { while ($start <= $end) {
$spent = $repository->spentInPeriod($categoryCollection, $accounts, $start, $start); $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $start);
$earned = $repository->earnedInPeriod($categoryCollection, $accounts, $start, $start); $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $start);
$date = Navigation::periodShow($start, '1D'); $label = Navigation::periodShow($start, '1D');
$entries->push([clone $start, $date, $spent, $earned]);
$chartData[0]['entries'][$label] = bcmul($spent, '-1');
$chartData[1]['entries'][$label] = $earned;
$start->addDay(); $start->addDay();
} }
$data = $this->generator->period($entries); $data = $this->generator->multiSet($chartData);
$cache->store($data); $cache->store($data);
return $data; return $data;

View File

@@ -15,7 +15,7 @@ namespace FireflyIII\Http\Controllers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface; use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
use FireflyIII\Generator\Report\Category\MonthReportGenerator; use FireflyIII\Generator\Report\Category\MonthReportGenerator;
use FireflyIII\Helpers\Collector\JournalCollector; use FireflyIII\Helpers\Collector\JournalCollector;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
@@ -24,8 +24,9 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log; use Navigation;
use Response; use Response;
@@ -43,7 +44,7 @@ class CategoryReportController extends Controller
private $accountRepository; private $accountRepository;
/** @var CategoryRepositoryInterface */ /** @var CategoryRepositoryInterface */
private $categoryRepository; private $categoryRepository;
/** @var CategoryChartGeneratorInterface */ /** @var GeneratorInterface */
private $generator; private $generator;
/** /**
@@ -54,7 +55,7 @@ class CategoryReportController extends Controller
parent::__construct(); parent::__construct();
$this->middleware( $this->middleware(
function ($request, $next) { function ($request, $next) {
$this->generator = app(CategoryChartGeneratorInterface::class); $this->generator = app(GeneratorInterface::class);
$this->categoryRepository = app(CategoryRepositoryInterface::class); $this->categoryRepository = app(CategoryRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class); $this->accountRepository = app(AccountRepositoryInterface::class);
@@ -76,38 +77,45 @@ class CategoryReportController extends Controller
{ {
/** @var bool $others */ /** @var bool $others */
$others = intval($others) === 1; $others = intval($others) === 1;
$names = []; $cache = new CacheProperties;
$cache->addProperty('chart.category.report.account-expense');
$cache->addProperty($accounts);
$cache->addProperty($categories);
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
return Response::json($cache->get());
}
// collect journals (just like the category report does): $names = [];
$set = $this->getExpenses($accounts, $categories, $start, $end); $set = $this->getExpenses($accounts, $categories, $start, $end);
$grouped = $this->groupByOpposingAccount($set); $grouped = $this->groupByOpposingAccount($set);
$chartData = [];
$total = '0';
// show the grouped results:
$result = [];
$total = '0';
foreach ($grouped as $accountId => $amount) { foreach ($grouped as $accountId => $amount) {
if (!isset($names[$accountId])) { if (!isset($names[$accountId])) {
$account = $this->accountRepository->find(intval($accountId)); $account = $this->accountRepository->find(intval($accountId));
$names[$accountId] = $account->name; $names[$accountId] = $account->name;
} }
$amount = bcmul($amount, '-1'); $amount = bcmul($amount, '-1');
$total = bcadd($total, $amount); $total = bcadd($total, $amount);
$result[] = ['name' => $names[$accountId], 'id' => $accountId, 'amount' => $amount]; $chartData[$names[$accountId]] = $amount;
} }
// also collect all transactions NOT in these categories. // also collect all transactions NOT in these categories.
if ($others) { if ($others) {
$collector = new JournalCollector(auth()->user()); $collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
$journals = $collector->getJournals(); $journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount')); $sum = strval($journals->sum('transaction_amount'));
$sum = bcmul($sum, '-1'); $sum = bcmul($sum, '-1');
Log::debug(sprintf('Sum of others in accountExpense is %f', $sum)); $sum = bcsub($sum, $total);
$sum = bcsub($sum, $total); $chartData[strval(trans('firefly.everything_else'))] = $sum;
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
} }
$data = $this->generator->pieChart($result); $data = $this->generator->pieChart($chartData);
$cache->store($data);
return Response::json($data); return Response::json($data);
} }
@@ -125,36 +133,44 @@ class CategoryReportController extends Controller
{ {
/** @var bool $others */ /** @var bool $others */
$others = intval($others) === 1; $others = intval($others) === 1;
$names = []; $cache = new CacheProperties;
$cache->addProperty('chart.category.report.account-income');
$cache->addProperty($accounts);
$cache->addProperty($categories);
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
return Response::json($cache->get());
}
// collect journals (just like the category report does):
$set = $this->getIncome($accounts, $categories, $start, $end);
$grouped = $this->groupByOpposingAccount($set);
// loop and show the grouped results: $names = [];
$result = []; $set = $this->getIncome($accounts, $categories, $start, $end);
$total = '0'; $grouped = $this->groupByOpposingAccount($set);
$chartData = [];
$total = '0';
foreach ($grouped as $accountId => $amount) { foreach ($grouped as $accountId => $amount) {
if (!isset($names[$accountId])) { if (!isset($names[$accountId])) {
$account = $this->accountRepository->find(intval($accountId)); $account = $this->accountRepository->find(intval($accountId));
$names[$accountId] = $account->name; $names[$accountId] = $account->name;
} }
$total = bcadd($total, $amount); $total = bcadd($total, $amount);
$result[] = ['name' => $names[$accountId], 'id' => $accountId, 'amount' => $amount]; $chartData[$names[$accountId]] = $amount;
} }
// also collect others? // also collect others?
if ($others) { if ($others) {
$collector = new JournalCollector(auth()->user()); $collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
$journals = $collector->getJournals(); $journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount')); $sum = strval($journals->sum('transaction_amount'));
Log::debug(sprintf('Sum of others in accountIncome is %f', $sum)); $sum = bcsub($sum, $total);
$sum = bcsub($sum, $total); $chartData[strval(trans('firefly.everything_else'))] = $sum;
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
} }
$data = $this->generator->pieChart($result); $data = $this->generator->pieChart($chartData);
$cache->store($data);
return Response::json($data); return Response::json($data);
} }
@@ -172,38 +188,45 @@ class CategoryReportController extends Controller
{ {
/** @var bool $others */ /** @var bool $others */
$others = intval($others) === 1; $others = intval($others) === 1;
$names = []; $cache = new CacheProperties;
$cache->addProperty('chart.category.report.category-expense');
$cache->addProperty($accounts);
$cache->addProperty($categories);
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
return Response::json($cache->get());
}
// collect journals (just like the category report does): $names = [];
$set = $this->getExpenses($accounts, $categories, $start, $end); $set = $this->getExpenses($accounts, $categories, $start, $end);
$grouped = $this->groupByCategory($set); $grouped = $this->groupByCategory($set);
$total = '0';
$chartData = [];
// show the grouped results:
$result = [];
$total = '0';
foreach ($grouped as $categoryId => $amount) { foreach ($grouped as $categoryId => $amount) {
if (!isset($names[$categoryId])) { if (!isset($names[$categoryId])) {
$category = $this->categoryRepository->find(intval($categoryId)); $category = $this->categoryRepository->find(intval($categoryId));
$names[$categoryId] = $category->name; $names[$categoryId] = $category->name;
} }
$amount = bcmul($amount, '-1'); $amount = bcmul($amount, '-1');
$total = bcadd($total, $amount); $total = bcadd($total, $amount);
$result[] = ['name' => $names[$categoryId], 'id' => $categoryId, 'amount' => $amount]; $chartData[$names[$categoryId]] = $amount;
} }
// also collect all transactions NOT in these categories. // also collect all transactions NOT in these categories.
if ($others) { if ($others) {
$collector = new JournalCollector(auth()->user()); $collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
$journals = $collector->getJournals(); $journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount')); $sum = strval($journals->sum('transaction_amount'));
$sum = bcmul($sum, '-1'); $sum = bcmul($sum, '-1');
Log::debug(sprintf('Sum of others in categoryExpense is %f', $sum)); $sum = bcsub($sum, $total);
$sum = bcsub($sum, $total); $chartData[strval(trans('firefly.everything_else'))] = $sum;
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
} }
$data = $this->generator->pieChart($result); $data = $this->generator->pieChart($chartData);
$cache->store($data);
return Response::json($data); return Response::json($data);
} }
@@ -221,36 +244,42 @@ class CategoryReportController extends Controller
{ {
/** @var bool $others */ /** @var bool $others */
$others = intval($others) === 1; $others = intval($others) === 1;
$names = []; $cache = new CacheProperties;
$cache->addProperty('chart.category.report.category-income');
$cache->addProperty($accounts);
$cache->addProperty($categories);
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
return Response::json($cache->get());
}
// collect journals (just like the category report does): $names = [];
$set = $this->getIncome($accounts, $categories, $start, $end); $set = $this->getIncome($accounts, $categories, $start, $end);
$grouped = $this->groupByCategory($set); $grouped = $this->groupByCategory($set);
$total = '0';
$chartData = [];
// loop and show the grouped results:
$result = [];
$total = '0';
foreach ($grouped as $categoryId => $amount) { foreach ($grouped as $categoryId => $amount) {
if (!isset($names[$categoryId])) { if (!isset($names[$categoryId])) {
$category = $this->categoryRepository->find(intval($categoryId)); $category = $this->categoryRepository->find(intval($categoryId));
$names[$categoryId] = $category->name; $names[$categoryId] = $category->name;
} }
$total = bcadd($total, $amount); $total = bcadd($total, $amount);
$result[] = ['name' => $names[$categoryId], 'id' => $categoryId, 'amount' => $amount]; $chartData[$names[$categoryId]] = $amount;
} }
// also collect others?
if ($others) { if ($others) {
$collector = new JournalCollector(auth()->user()); $collector = new JournalCollector(auth()->user());
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
$journals = $collector->getJournals(); $journals = $collector->getJournals();
$sum = strval($journals->sum('transaction_amount')); $sum = strval($journals->sum('transaction_amount'));
Log::debug(sprintf('Sum of others in categoryIncome is %f', $sum)); $sum = bcsub($sum, $total);
$sum = bcsub($sum, $total); $chartData[strval(trans('firefly.everything_else'))] = $sum;
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
} }
$data = $this->generator->pieChart($result); $data = $this->generator->pieChart($chartData);
$cache->store($data);
return Response::json($data); return Response::json($data);
} }
@@ -265,52 +294,56 @@ class CategoryReportController extends Controller
*/ */
public function mainChart(Collection $accounts, Collection $categories, Carbon $start, Carbon $end) public function mainChart(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
{ {
// determin optimal period: $cache = new CacheProperties;
$period = '1D'; $cache->addProperty('chart.category.report.main');
$format = 'month_and_day'; $cache->addProperty($accounts);
$function = 'endOfDay'; $cache->addProperty($categories);
if ($start->diffInMonths($end) > 1) { $cache->addProperty($start);
$period = '1M'; $cache->addProperty($end);
$format = 'month'; if ($cache->has()) {
$function = 'endOfMonth'; return Response::json($cache->get());
} }
if ($start->diffInMonths($end) > 13) {
$period = '1Y'; $format = Navigation::preferredCarbonLocalizedFormat($start, $end);
$format = 'year'; $function = Navigation::preferredEndOfPeriod($start, $end);
$function = 'endOfYear'; $chartData = [];
}
Log::debug(sprintf('Period is %s', $period));
$data = [];
$currentStart = clone $start; $currentStart = clone $start;
// prep chart data:
foreach ($categories as $category) {
$chartData[$category->id . '-in'] = [
'label' => $category->name . ' (' . strtolower(strval(trans('firefly.income'))) . ')',
'type' => 'bar',
'entries' => [],
];
$chartData[$category->id . '-out'] = [
'label' => $category->name . ' (' . strtolower(strval(trans('firefly.expenses'))) . ')',
'type' => 'bar',
'entries' => [],
];
}
while ($currentStart < $end) { while ($currentStart < $end) {
$currentEnd = clone $currentStart; $currentEnd = clone $currentStart;
Log::debug(sprintf('Function is %s', $function));
$currentEnd = $currentEnd->$function(); $currentEnd = $currentEnd->$function();
$expenses = $this->groupByCategory($this->getExpenses($accounts, $categories, $currentStart, $currentEnd)); $expenses = $this->groupByCategory($this->getExpenses($accounts, $categories, $currentStart, $currentEnd));
$income = $this->groupByCategory($this->getIncome($accounts, $categories, $currentStart, $currentEnd)); $income = $this->groupByCategory($this->getIncome($accounts, $categories, $currentStart, $currentEnd));
$label = $currentStart->formatLocalized(strval(trans('config.' . $format))); $label = $currentStart->formatLocalized($format);
Log::debug(sprintf('Now grabbing CMC expenses between %s and %s', $currentStart->format('Y-m-d'), $currentEnd->format('Y-m-d')));
$data[$label] = [
'in' => [],
'out' => [],
];
/** @var Category $category */ /** @var Category $category */
foreach ($categories as $category) { foreach ($categories as $category) {
$labelIn = $category->id . '-in';
$labelOut = $category->id . '-out';
// get sum, and get label: // get sum, and get label:
$categoryId = $category->id; $chartData[$labelIn]['entries'][$label] = $income[$category->id] ?? '0';
$data[$label]['name'][$categoryId] = $category->name; $chartData[$labelOut]['entries'][$label] = $expenses[$category->id] ?? '0';
$data[$label]['in'][$categoryId] = $income[$categoryId] ?? '0';
$data[$label]['out'][$categoryId] = $expenses[$categoryId] ?? '0';
} }
$currentStart = clone $currentEnd; $currentStart = clone $currentEnd;
$currentStart->addDay(); $currentStart->addDay();
} }
$data = $this->generator->mainReportChart($data); $data = $this->generator->multiSet($chartData);
$cache->store($data);
return Response::json($data); return Response::json($data);
} }

View File

@@ -95,6 +95,7 @@ class CacheProperties
*/ */
private function md5() private function md5()
{ {
$this->md5 = '';
foreach ($this->properties as $property) { foreach ($this->properties as $property) {
if ($property instanceof Collection || $property instanceof EloquentCollection) { if ($property instanceof Collection || $property instanceof EloquentCollection) {
@@ -108,6 +109,12 @@ class CacheProperties
if (is_object($property)) { if (is_object($property)) {
$this->md5 .= $property->__toString(); $this->md5 .= $property->__toString();
} }
if (is_bool($property) && $property === false) {
$this->md5 .= 'false';
}
if (is_bool($property) && $property === true) {
$this->md5 .= 'true';
}
$this->md5 .= json_encode($property); $this->md5 .= json_encode($property);
} }

View File

@@ -243,6 +243,28 @@ class Navigation
throw new FireflyException(sprintf('No date formats for frequency "%s"!', $repeatFrequency)); throw new FireflyException(sprintf('No date formats for frequency "%s"!', $repeatFrequency));
} }
/**
* If the date difference between start and end is less than a month, method returns "endOfDay". If the difference is less than a year,
* method returns "endOfMonth". If the date difference is larger, method returns "endOfYear".
*
* @param \Carbon\Carbon $start
* @param \Carbon\Carbon $end
*
* @return string
*/
public function preferredEndOfPeriod(Carbon $start, Carbon $end): string
{
$format = 'endOfDay';
if ($start->diffInMonths($end) > 1) {
$format = 'endOfMonth';
}
if ($start->diffInMonths($end) > 12) {
$format = 'endOfYear';
}
return $format;
}
/** /**
* If the date difference between start and end is less than a month, method returns "Y-m-d". If the difference is less than a year, * If the date difference between start and end is less than a month, method returns "Y-m-d". If the difference is less than a year,
* method returns "Y-m". If the date difference is larger, method returns "Y". * method returns "Y-m". If the date difference is larger, method returns "Y".
@@ -262,6 +284,28 @@ class Navigation
if ($start->diffInMonths($end) > 12) { if ($start->diffInMonths($end) > 12) {
$format = 'Y'; $format = 'Y';
} }
return $format;
}
/**
* If the date difference between start and end is less than a month, method returns trans(config.month_and_day). If the difference is less than a year,
* method returns "config.month". If the date difference is larger, method returns "config.year".
*
* @param \Carbon\Carbon $start
* @param \Carbon\Carbon $end
*
* @return string
*/
public function preferredCarbonLocalizedFormat(Carbon $start, Carbon $end): string
{
$format = strval(trans('config.month_and_day'));
if ($start->diffInMonths($end) > 1) {
$format = strval(trans('config.month'));
}
if ($start->diffInMonths($end) > 12) {
$format = strval(trans('config.year'));
}
return $format; return $format;