2016-11-12 03:14:20 -06:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* CategoryReportController.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\Http\Controllers\Chart;
|
|
|
|
|
|
|
|
|
|
|
|
use Carbon\Carbon;
|
|
|
|
use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface;
|
|
|
|
use FireflyIII\Generator\Report\Category\MonthReportGenerator;
|
|
|
|
use FireflyIII\Helpers\Collector\JournalCollector;
|
|
|
|
use FireflyIII\Http\Controllers\Controller;
|
2016-11-12 13:29:16 -06:00
|
|
|
use FireflyIII\Models\Category;
|
2016-11-12 03:14:20 -06:00
|
|
|
use FireflyIII\Models\Transaction;
|
|
|
|
use FireflyIII\Models\TransactionType;
|
|
|
|
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
|
|
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
|
|
|
use Illuminate\Support\Collection;
|
|
|
|
use Log;
|
2016-11-12 13:29:16 -06:00
|
|
|
use Navigation;
|
2016-11-12 03:14:20 -06:00
|
|
|
use Response;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Separate controller because many helper functions are shared.
|
|
|
|
*
|
|
|
|
* TODO much of this code is actually repeated. First for the object (category, account), then for the direction (in / out).
|
|
|
|
*
|
|
|
|
* Class CategoryReportController
|
|
|
|
*
|
|
|
|
* @package FireflyIII\Http\Controllers\Chart
|
|
|
|
*/
|
|
|
|
class CategoryReportController extends Controller
|
|
|
|
{
|
|
|
|
|
|
|
|
/** @var AccountRepositoryInterface */
|
|
|
|
private $accountRepository;
|
|
|
|
/** @var CategoryRepositoryInterface */
|
|
|
|
private $categoryRepository;
|
|
|
|
/** @var CategoryChartGeneratorInterface */
|
|
|
|
private $generator;
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
parent::__construct();
|
|
|
|
$this->middleware(
|
|
|
|
function ($request, $next) {
|
|
|
|
$this->generator = app(CategoryChartGeneratorInterface::class);
|
|
|
|
$this->categoryRepository = app(CategoryRepositoryInterface::class);
|
|
|
|
$this->accountRepository = app(AccountRepositoryInterface::class);
|
|
|
|
|
|
|
|
return $next($request);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Collection $accounts
|
|
|
|
* @param Collection $categories
|
|
|
|
* @param Carbon $start
|
|
|
|
* @param Carbon $end
|
|
|
|
* @param string $others
|
|
|
|
*
|
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
|
*/
|
|
|
|
public function accountExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
|
|
|
|
{
|
|
|
|
/** @var bool $others */
|
|
|
|
$others = intval($others) === 1;
|
|
|
|
$names = [];
|
|
|
|
|
|
|
|
// collect journals (just like the category report does):
|
|
|
|
$set = $this->getExpenses($accounts, $categories, $start, $end);
|
|
|
|
$grouped = $this->groupByOpposingAccount($set);
|
|
|
|
|
|
|
|
// show the grouped results:
|
|
|
|
$result = [];
|
|
|
|
$total = '0';
|
|
|
|
foreach ($grouped as $accountId => $amount) {
|
|
|
|
if (!isset($names[$accountId])) {
|
|
|
|
$account = $this->accountRepository->find(intval($accountId));
|
|
|
|
$names[$accountId] = $account->name;
|
|
|
|
}
|
|
|
|
$amount = bcmul($amount, '-1');
|
|
|
|
$total = bcadd($total, $amount);
|
|
|
|
$result[] = ['name' => $names[$accountId], 'id' => $accountId, 'amount' => $amount];
|
|
|
|
}
|
|
|
|
|
|
|
|
// also collect all transactions NOT in these categories.
|
|
|
|
// TODO include transfers
|
|
|
|
if ($others) {
|
|
|
|
$collector = new JournalCollector(auth()->user());
|
|
|
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
|
|
|
|
$journals = $collector->getJournals();
|
|
|
|
$sum = strval($journals->sum('transaction_amount'));
|
|
|
|
$sum = bcmul($sum, '-1');
|
|
|
|
Log::debug(sprintf('Sum of others in accountExpense is %f', $sum));
|
|
|
|
$sum = bcsub($sum, $total);
|
|
|
|
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = $this->generator->pieChart($result);
|
|
|
|
|
|
|
|
return Response::json($data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Collection $accounts
|
|
|
|
* @param Collection $categories
|
|
|
|
* @param Carbon $start
|
|
|
|
* @param Carbon $end
|
|
|
|
* @param string $others
|
|
|
|
*
|
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
|
*/
|
|
|
|
public function accountIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
|
|
|
|
{
|
|
|
|
/** @var bool $others */
|
|
|
|
$others = intval($others) === 1;
|
|
|
|
$names = [];
|
|
|
|
|
|
|
|
// 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:
|
|
|
|
$result = [];
|
|
|
|
$total = '0';
|
|
|
|
foreach ($grouped as $accountId => $amount) {
|
|
|
|
if (!isset($names[$accountId])) {
|
|
|
|
$account = $this->accountRepository->find(intval($accountId));
|
|
|
|
$names[$accountId] = $account->name;
|
|
|
|
}
|
|
|
|
$total = bcadd($total, $amount);
|
|
|
|
$result[] = ['name' => $names[$accountId], 'id' => $accountId, 'amount' => $amount];
|
|
|
|
}
|
|
|
|
|
|
|
|
// also collect others?
|
|
|
|
// TODO include transfers
|
|
|
|
if ($others) {
|
|
|
|
$collector = new JournalCollector(auth()->user());
|
|
|
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
|
|
|
|
$journals = $collector->getJournals();
|
|
|
|
$sum = strval($journals->sum('transaction_amount'));
|
|
|
|
Log::debug(sprintf('Sum of others in accountIncome is %f', $sum));
|
|
|
|
$sum = bcsub($sum, $total);
|
|
|
|
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = $this->generator->pieChart($result);
|
|
|
|
|
|
|
|
return Response::json($data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Collection $accounts
|
|
|
|
* @param Collection $categories
|
|
|
|
* @param Carbon $start
|
|
|
|
* @param Carbon $end
|
|
|
|
* @param string $others
|
|
|
|
*
|
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
|
*/
|
|
|
|
public function categoryExpense(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
|
|
|
|
{
|
|
|
|
/** @var bool $others */
|
|
|
|
$others = intval($others) === 1;
|
|
|
|
$names = [];
|
|
|
|
|
|
|
|
// collect journals (just like the category report does):
|
|
|
|
$set = $this->getExpenses($accounts, $categories, $start, $end);
|
|
|
|
$grouped = $this->groupByCategory($set);
|
|
|
|
|
|
|
|
// show the grouped results:
|
|
|
|
$result = [];
|
|
|
|
$total = '0';
|
|
|
|
foreach ($grouped as $categoryId => $amount) {
|
|
|
|
if (!isset($names[$categoryId])) {
|
|
|
|
$category = $this->categoryRepository->find(intval($categoryId));
|
|
|
|
$names[$categoryId] = $category->name;
|
|
|
|
}
|
|
|
|
$amount = bcmul($amount, '-1');
|
|
|
|
$total = bcadd($total, $amount);
|
|
|
|
$result[] = ['name' => $names[$categoryId], 'id' => $categoryId, 'amount' => $amount];
|
|
|
|
}
|
|
|
|
|
|
|
|
// also collect all transactions NOT in these categories.
|
|
|
|
// TODO include transfers
|
|
|
|
if ($others) {
|
|
|
|
$collector = new JournalCollector(auth()->user());
|
|
|
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
|
|
|
|
$journals = $collector->getJournals();
|
|
|
|
$sum = strval($journals->sum('transaction_amount'));
|
|
|
|
$sum = bcmul($sum, '-1');
|
|
|
|
Log::debug(sprintf('Sum of others in categoryExpense is %f', $sum));
|
|
|
|
$sum = bcsub($sum, $total);
|
|
|
|
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = $this->generator->pieChart($result);
|
|
|
|
|
|
|
|
return Response::json($data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Collection $accounts
|
|
|
|
* @param Collection $categories
|
|
|
|
* @param Carbon $start
|
|
|
|
* @param Carbon $end
|
|
|
|
* @param string $others
|
|
|
|
*
|
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
|
*/
|
|
|
|
public function categoryIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end, string $others)
|
|
|
|
{
|
|
|
|
/** @var bool $others */
|
|
|
|
$others = intval($others) === 1;
|
|
|
|
$names = [];
|
|
|
|
|
|
|
|
// collect journals (just like the category report does):
|
|
|
|
$set = $this->getIncome($accounts, $categories, $start, $end);
|
|
|
|
$grouped = $this->groupByCategory($set);
|
|
|
|
|
|
|
|
// loop and show the grouped results:
|
|
|
|
$result = [];
|
|
|
|
$total = '0';
|
|
|
|
foreach ($grouped as $categoryId => $amount) {
|
|
|
|
if (!isset($names[$categoryId])) {
|
|
|
|
$category = $this->categoryRepository->find(intval($categoryId));
|
|
|
|
$names[$categoryId] = $category->name;
|
|
|
|
}
|
|
|
|
$total = bcadd($total, $amount);
|
|
|
|
$result[] = ['name' => $names[$categoryId], 'id' => $categoryId, 'amount' => $amount];
|
|
|
|
}
|
|
|
|
|
|
|
|
// also collect others?
|
|
|
|
// TODO include transfers
|
|
|
|
if ($others) {
|
|
|
|
$collector = new JournalCollector(auth()->user());
|
|
|
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
|
|
|
|
$journals = $collector->getJournals();
|
|
|
|
$sum = strval($journals->sum('transaction_amount'));
|
|
|
|
Log::debug(sprintf('Sum of others in categoryIncome is %f', $sum));
|
|
|
|
$sum = bcsub($sum, $total);
|
|
|
|
$result[] = ['name' => trans('firefly.everything_else'), 'id' => 0, 'amount' => $sum];
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = $this->generator->pieChart($result);
|
|
|
|
|
|
|
|
return Response::json($data);
|
|
|
|
}
|
|
|
|
|
2016-11-12 13:29:16 -06:00
|
|
|
/**
|
|
|
|
* @param Collection $accounts
|
|
|
|
* @param Collection $categories
|
|
|
|
* @param Carbon $start
|
|
|
|
* @param Carbon $end
|
|
|
|
*
|
|
|
|
* @return \Illuminate\Http\JsonResponse
|
|
|
|
*/
|
|
|
|
public function mainChart(Collection $accounts, Collection $categories, Carbon $start, Carbon $end)
|
|
|
|
{
|
|
|
|
// determin optimal period:
|
|
|
|
$period = '1D';
|
|
|
|
$format = 'month_and_day';
|
|
|
|
if ($start->diffInMonths($end) > 1) {
|
|
|
|
$period = '1M';
|
|
|
|
$format = 'month';
|
|
|
|
}
|
|
|
|
if ($start->diffInMonths($end) > 13) {
|
|
|
|
$period = '1Y';
|
|
|
|
$format = 'year';
|
|
|
|
}
|
|
|
|
Log::debug(sprintf('Period is %s', $period));
|
|
|
|
$data = [];
|
|
|
|
$current = clone $start;
|
|
|
|
while ($current < $end) {
|
|
|
|
$currentEnd = Navigation::endOfPeriod($current, $period);
|
|
|
|
$expenses = $this->groupByCategory($this->getExpenses($accounts, $categories, $current, $currentEnd));
|
|
|
|
$income = $this->groupByCategory($this->getIncome($accounts, $categories, $current, $currentEnd));
|
|
|
|
$label = $current->formatLocalized(strval(trans('config.' . $format)));
|
|
|
|
$data[$label] = [
|
|
|
|
'in' => [],
|
|
|
|
'out' => [],
|
|
|
|
];
|
|
|
|
|
|
|
|
/** @var Category $category */
|
|
|
|
foreach ($categories as $category) {
|
|
|
|
// get sum, and get label:
|
2016-11-20 04:43:19 -06:00
|
|
|
$categoryId = $category->id;
|
|
|
|
$data[$label]['name'][$categoryId] = $category->name;
|
|
|
|
$data[$label]['in'][$categoryId] = $income[$categoryId] ?? '0';
|
|
|
|
$data[$label]['out'][$categoryId] = $expenses[$categoryId] ?? '0';
|
2016-11-12 13:29:16 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
$current = Navigation::addPeriod($current, $period, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = $this->generator->mainReportChart($data);
|
2016-11-20 04:43:19 -06:00
|
|
|
|
2016-11-12 13:29:16 -06:00
|
|
|
return Response::json($data);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-11-12 03:14:20 -06:00
|
|
|
/**
|
|
|
|
* @param Collection $accounts
|
|
|
|
* @param Collection $categories
|
|
|
|
* @param Carbon $start
|
|
|
|
* @param Carbon $end
|
|
|
|
*
|
|
|
|
* @return Collection
|
|
|
|
*/
|
|
|
|
private function getExpenses(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): Collection
|
|
|
|
{
|
|
|
|
$collector = new JournalCollector(auth()->user());
|
|
|
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
2016-11-12 14:06:48 -06:00
|
|
|
->setCategories($categories)->withOpposingAccount()->disableFilter();
|
2016-11-12 03:14:20 -06:00
|
|
|
$accountIds = $accounts->pluck('id')->toArray();
|
|
|
|
$transactions = $collector->getJournals();
|
|
|
|
$set = MonthReportGenerator::filterExpenses($transactions, $accountIds);
|
|
|
|
|
|
|
|
return $set;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Collection $accounts
|
|
|
|
* @param Collection $categories
|
|
|
|
* @param Carbon $start
|
|
|
|
* @param Carbon $end
|
|
|
|
*
|
|
|
|
* @return Collection
|
|
|
|
*/
|
|
|
|
private function getIncome(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): Collection
|
|
|
|
{
|
|
|
|
$collector = new JournalCollector(auth()->user());
|
|
|
|
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
|
2016-11-12 14:06:48 -06:00
|
|
|
->setCategories($categories)->withOpposingAccount();
|
2016-11-12 03:14:20 -06:00
|
|
|
$accountIds = $accounts->pluck('id')->toArray();
|
|
|
|
$transactions = $collector->getJournals();
|
|
|
|
$set = MonthReportGenerator::filterIncome($transactions, $accountIds);
|
|
|
|
|
|
|
|
return $set;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Collection $set
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function groupByCategory(Collection $set): array
|
|
|
|
{
|
|
|
|
// group by category ID:
|
|
|
|
$grouped = [];
|
|
|
|
/** @var Transaction $transaction */
|
|
|
|
foreach ($set as $transaction) {
|
|
|
|
$jrnlCatId = intval($transaction->transaction_journal_category_id);
|
|
|
|
$transCatId = intval($transaction->transaction_category_id);
|
|
|
|
$categoryId = max($jrnlCatId, $transCatId);
|
|
|
|
$grouped[$categoryId] = $grouped[$categoryId] ?? '0';
|
|
|
|
$grouped[$categoryId] = bcadd($transaction->transaction_amount, $grouped[$categoryId]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $grouped;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Collection $set
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function groupByOpposingAccount(Collection $set): array
|
|
|
|
{
|
|
|
|
$grouped = [];
|
|
|
|
/** @var Transaction $transaction */
|
|
|
|
foreach ($set as $transaction) {
|
|
|
|
$accountId = $transaction->opposing_account_id;
|
|
|
|
$grouped[$accountId] = $grouped[$accountId] ?? '0';
|
|
|
|
$grouped[$accountId] = bcadd($transaction->transaction_amount, $grouped[$accountId]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $grouped;
|
|
|
|
}
|
|
|
|
}
|