Various report updates and code cleanup.

This commit is contained in:
James Cole 2016-12-04 18:02:19 +01:00
parent 905a2432c6
commit f4b9b7ae84
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
32 changed files with 592 additions and 450 deletions

View File

@ -44,4 +44,11 @@ interface BudgetChartGeneratorInterface
* @return array
*/
public function period(array $entries): array;
/**
* @param array $entries
*
* @return array
*/
public function periodNoBudget(array $entries): array;
}

View File

@ -104,7 +104,7 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface
*
* @return array
*/
public function period(array $entries) : array
public function period(array $entries): array
{
$data = [
@ -133,4 +133,32 @@ class ChartJsBudgetChartGenerator implements BudgetChartGeneratorInterface
return $data;
}
/**
* @param array $entries
*
* @return array
*/
public function periodNoBudget(array $entries): array
{
$data = [
'labels' => array_keys($entries),
'datasets' => [
0 => [
'label' => trans('firefly.spent'),
'data' => [],
],
],
'count' => 1,
];
foreach ($entries as $label => $entry) {
// data set 0 is budgeted
// data set 1 is spent:
$data['datasets'][0]['data'][] = round(($entry['spent'] * -1), 2);
}
return $data;
}
}

View File

@ -78,6 +78,7 @@ class MonthReportGenerator implements ReportGeneratorInterface
$auditData[$id]['dayBeforeBalance'] = $dayBeforeBalance;
}
$defaultShow = ['icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', 'to'];
$reportType = 'audit';
$accountIds = join(',', $this->accounts->pluck('id')->toArray());
$hideable = ['buttons', 'icon', 'description', 'balance_before', 'amount', 'balance_after', 'date',
@ -87,10 +88,9 @@ class MonthReportGenerator implements ReportGeneratorInterface
'from', 'to', 'budget', 'category', 'bill',
// more new optional fields
'internal_reference', 'notes',
'create_date', 'update_date',
];
$defaultShow = ['icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', 'to'];
return view('reports.audit.report', compact('reportType', 'accountIds', 'auditData', 'hideable', 'defaultShow'))
->with('start', $this->start)->with('end', $this->end)->with('accounts', $this->accounts)

View File

@ -42,22 +42,6 @@ class BudgetReportHelper implements BudgetReportHelperInterface
$this->repository = $repository;
}
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
public function getBudgetPeriodReport(Carbon $start, Carbon $end, Collection $accounts): array
{
$budgets = $this->repository->getBudgets();
$report = $this->repository->getBudgetPeriodReport($budgets, $accounts, $start, $end, true);
$data = $this->filterBudgetPeriodReport($report);
return $data;
}
/**
* @param Carbon $start
* @param Carbon $end
@ -162,31 +146,4 @@ class BudgetReportHelper implements BudgetReportHelperInterface
return $array;
}
/**
* Filters empty results from getBudgetPeriodReport
*
* @param array $data
*
* @return array
*/
private function filterBudgetPeriodReport(array $data): array
{
/**
* @var int $budgetId
* @var array $set
*/
foreach ($data as $budgetId => $set) {
$sum = '0';
foreach ($set['entries'] as $amount) {
$sum = bcadd($amount, $sum);
}
$data[$budgetId]['sum'] = $sum;
if (bccomp('0', $sum) === 0) {
unset($data[$budgetId]);
}
}
return $data;
}
}

View File

@ -26,15 +26,6 @@ use Illuminate\Support\Collection;
interface BudgetReportHelperInterface
{
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return array
*/
public function getBudgetPeriodReport(Carbon $start, Carbon $end, Collection $accounts): array;
/**
* @param Carbon $start
* @param Carbon $end

View File

@ -252,6 +252,48 @@ class BudgetController extends Controller
return Response::json($data);
}
/**
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
*/
public function periodNoBudget(BudgetRepositoryInterface $repository, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($accounts);
$cache->addProperty('no-budget');
$cache->addProperty('period');
if ($cache->has()) {
return Response::json($cache->get());
}
// the expenses:
$periods = Navigation::listOfPeriods($start, $end);
$entries = $repository->getNoBudgetPeriodReport($accounts, $start, $end);
// join them:
$result = [];
foreach (array_keys($periods) as $period) {
$nice = $periods[$period];
$result[$nice] = [
'spent' => isset($entries['entries'][$period]) ? $entries['entries'][$period] : '0',
];
}
$data = $this->generator->periodNoBudget($result);
$cache->store($data);
return Response::json($data);
}
/**
* @param Collection $repetitions
* @param Budget $budget

View File

@ -17,6 +17,7 @@ namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Report\BudgetReportHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Navigation;
@ -31,14 +32,13 @@ class BudgetController extends Controller
/**
*
* @param BudgetReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function budgetPeriodReport(BudgetReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
public function budgetPeriodReport(Carbon $start, Carbon $end, Collection $accounts)
{
$cache = new CacheProperties;
$cache->addProperty($start);
@ -46,12 +46,19 @@ class BudgetController extends Controller
$cache->addProperty('budget-period-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
return $cache->get();
}
$periods = Navigation::listOfPeriods($start, $end);
$budgets = $helper->getBudgetPeriodReport($start, $end, $accounts);
$result = view('reports.partials.budget-period', compact('budgets', 'periods'))->render();
// generate budget report right here.
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$budgets = $repository->getBudgets();
$data = $repository->getBudgetPeriodReport($budgets, $accounts, $start, $end);
$data[0] = $repository->getNoBudgetPeriodReport($accounts, $start, $end); // append report data for "no budget"
$report = $this->filterBudgetPeriodReport($data);
$periods = Navigation::listOfPeriods($start, $end);
$result = view('reports.partials.budget-period', compact('report', 'periods'))->render();
$cache->store($result);
return $result;
@ -86,4 +93,31 @@ class BudgetController extends Controller
return $result;
}
/**
* Filters empty results from getBudgetPeriodReport
*
* @param array $data
*
* @return array
*/
private function filterBudgetPeriodReport(array $data): array
{
/**
* @var int $budgetId
* @var array $set
*/
foreach ($data as $budgetId => $set) {
$sum = '0';
foreach ($set['entries'] as $amount) {
$sum = bcadd($amount, $sum);
}
$data[$budgetId]['sum'] = $sum;
if (bccomp('0', $sum) === 0) {
unset($data[$budgetId]);
}
}
return $data;
}
}

View File

@ -31,40 +31,6 @@ use Navigation;
*/
class CategoryController extends Controller
{
/**
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function categoryPeriodReport(Carbon $start, Carbon $end, Collection $accounts)
{
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('category-period-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
Log::debug('Return report from cache');
return $cache->get();
}
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
$categories = $repository->getCategories();
$report = $repository->getCategoryPeriodReport($categories, $accounts, $start, $end, true);
$report = $this->filterCategoryPeriodReport($report);
$periods = Navigation::listOfPeriods($start, $end);
$result = view('reports.partials.category-period', compact('categories', 'periods', 'report'))->render();
$cache->store($result);
return $result;
}
/**
* @param ReportHelperInterface $helper
* @param Carbon $start
@ -93,6 +59,73 @@ class CategoryController extends Controller
return $result;
}
/**
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function expenseReport(Carbon $start, Carbon $end, Collection $accounts)
{
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('category-period-expenses-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
Log::debug('Return report from cache');
return $cache->get();
}
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
$categories = $repository->getCategories();
$data = $repository->periodExpenses($categories, $accounts, $start, $end);
$data[0] = $repository->periodExpensesNoCategory($accounts, $start, $end);
$report = $this->filterReport($data);
$periods = Navigation::listOfPeriods($start, $end);
$result = view('reports.partials.category-period', compact('report', 'periods'))->render();
$cache->store($result);
return $result;
}
/**
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function incomeReport(Carbon $start, Carbon $end, Collection $accounts)
{
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('category-period-income-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
Log::debug('Return report from cache');
return $cache->get();
}
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
$categories = $repository->getCategories();
$data = $repository->periodIncome($categories, $accounts, $start, $end);
$data[0] = $repository->periodIncomeNoCategory($accounts, $start, $end);
$report = $this->filterReport($data);
$periods = Navigation::listOfPeriods($start, $end);
$result = view('reports.partials.category-period', compact('report', 'periods'))->render();
$cache->store($result);
return $result;
}
/**
* Filters empty results from category period report
*
@ -100,22 +133,16 @@ class CategoryController extends Controller
*
* @return array
*/
private function filterCategoryPeriodReport(array $data): array
private function filterReport(array $data): array
{
/**
* @var string $type
* @var array $report
*/
foreach ($data as $type => $report) {
foreach ($report as $categoryId => $set) {
$sum = '0';
foreach ($set['entries'] as $amount) {
$sum = bcadd($amount, $sum);
}
$data[$type][$categoryId]['sum'] = $sum;
if (bccomp('0', $sum) === 0) {
unset($data[$type][$categoryId]);
}
foreach ($data as $categoryId => $set) {
$sum = '0';
foreach ($set['entries'] as $amount) {
$sum = bcadd($amount, $sum);
}
$data[$categoryId]['sum'] = $sum;
if (bccomp('0', $sum) === 0) {
unset($data[$categoryId]);
}
}
@ -123,25 +150,5 @@ class CategoryController extends Controller
return $data;
}
/**
* @param int $categoryId
* @param Collection $categories
*
* @return string
*/
private function getCategoryName(int $categoryId, Collection $categories): string
{
$first = $categories->filter(
function (Category $category) use ($categoryId) {
return $categoryId === $category->id;
}
);
if (!is_null($first->first())) {
return $first->first()->name;
}
return '(unknown)';
}
}

View File

@ -368,10 +368,10 @@ class AccountRepository implements AccountRepositoryInterface
protected function openingBalanceTransaction(Account $account): TransactionJournal
{
$journal = TransactionJournal::sortCorrectly()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionType::OPENING_BALANCE])
->first(['transaction_journals.*']);
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionType::OPENING_BALANCE])
->first(['transaction_journals.*']);
if (is_null($journal)) {
Log::debug('Could not find a opening balance journal, return empty one.');

View File

@ -250,19 +250,19 @@ class AccountTasker implements AccountTaskerInterface
$selection = $incoming ? '>' : '<';
$query = Transaction::distinct()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin(
'transactions as other_side', function (JoinClause $join) use ($joinModifier) {
$join->on('transaction_journals.id', '=', 'other_side.transaction_journal_id')->where('other_side.amount', $joinModifier, 0);
}
)
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('transaction_journals.user_id', $this->user->id)
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereIn('transactions.account_id', $accounts['accounts'])
->where('transactions.amount', $selection, 0);
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin(
'transactions as other_side', function (JoinClause $join) use ($joinModifier) {
$join->on('transaction_journals.id', '=', 'other_side.transaction_journal_id')->where('other_side.amount', $joinModifier, 0);
}
)
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('transaction_journals.user_id', $this->user->id)
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereIn('transactions.account_id', $accounts['accounts'])
->where('transactions.amount', $selection, 0);
if (count($accounts['exclude']) > 0) {
$query->whereNotIn('other_side.account_id', $accounts['exclude']);
}
@ -302,25 +302,27 @@ class AccountTasker implements AccountTaskerInterface
$joinModifier = $incoming ? '<' : '>';
$selection = $incoming ? '>' : '<';
$query = Transaction::distinct()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id')
->leftJoin(
'transactions as other_side', function (JoinClause $join) use ($joinModifier) {
$join->on('transaction_journals.id', '=', 'other_side.transaction_journal_id')->where('other_side.amount', $joinModifier, 0);
}
)
->leftJoin('accounts as other_account', 'other_account.id', '=', 'other_side.account_id')
->where('transaction_types.type', '!=', TransactionType::OPENING_BALANCE)
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('transaction_journals.user_id', $this->user->id)
->whereNull('transactions.deleted_at')
->whereNull('other_side.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereIn('transactions.account_id', $accounts['accounts'])
->where('other_side.amount', '=', DB::raw('transactions.amount * -1'))
->where('transactions.amount', $selection, 0)
->orderBy('transactions.amount');
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id')
->leftJoin(
'transactions as other_side', function (JoinClause $join) use ($joinModifier) {
$join->on('transaction_journals.id', '=', 'other_side.transaction_journal_id')->where(
'other_side.amount', $joinModifier, 0
);
}
)
->leftJoin('accounts as other_account', 'other_account.id', '=', 'other_side.account_id')
->where('transaction_types.type', '!=', TransactionType::OPENING_BALANCE)
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('transaction_journals.user_id', $this->user->id)
->whereNull('transactions.deleted_at')
->whereNull('other_side.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereIn('transactions.account_id', $accounts['accounts'])
->where('other_side.amount', '=', DB::raw('transactions.amount * -1'))
->where('transactions.amount', $selection, 0)
->orderBy('transactions.amount');
if (count($accounts['exclude']) > 0) {
$query->whereNotIn('other_side.account_id', $accounts['exclude']);

View File

@ -66,7 +66,7 @@ class BillRepository implements BillRepositoryInterface
*
* @return Bill
*/
public function find(int $billId) : Bill
public function find(int $billId): Bill
{
$bill = $this->user->bills()->find($billId);
if (is_null($bill)) {
@ -83,7 +83,7 @@ class BillRepository implements BillRepositoryInterface
*
* @return Bill
*/
public function findByName(string $name) : Bill
public function findByName(string $name): Bill
{
$bills = $this->user->bills()->get(['bills.*']);

View File

@ -40,7 +40,7 @@ interface BillRepositoryInterface
*
* @return Bill
*/
public function find(int $billId) : Bill;
public function find(int $billId): Bill;
/**
* Find a bill by name.
@ -49,7 +49,7 @@ interface BillRepositoryInterface
*
* @return Bill
*/
public function findByName(string $name) : Bill;
public function findByName(string $name): Bill;
/**
* @return Collection

View File

@ -219,11 +219,10 @@ class BudgetRepository implements BudgetRepositoryInterface
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
* @param bool $noBudget
*
* @return array
*/
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end, bool $noBudget): array
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
$data = [];
@ -252,11 +251,6 @@ class BudgetRepository implements BudgetRepositoryInterface
$data[$budgetId]['entries'][$date] = bcadd($data[$budgetId]['entries'][$date] ?? '0', $transaction->transaction_amount);
}
if ($noBudget) {
// and now the same for stuff without a budget:
$data[0] = $this->getNoBudgetPeriodReport($start, $end);
}
return $data;
}
@ -295,6 +289,40 @@ class BudgetRepository implements BudgetRepositoryInterface
return $set;
}
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function getNoBudgetPeriodReport(Collection $accounts, Carbon $start, Carbon $end): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setTypes([TransactionType::WITHDRAWAL]);
$collector->withoutBudget();
$transactions = $collector->getJournals();
$result = [
'entries' => [],
'name' => strval(trans('firefly.no_budget')),
'sum' => '0',
];
foreach ($transactions as $transaction) {
$date = $transaction->date->format($carbonFormat);
if (!isset($result['entries'][$date])) {
$result['entries'][$date] = '0';
}
$result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount);
}
return $result;
}
/**
* @param Collection $budgets
* @param Collection $accounts
@ -533,58 +561,4 @@ class BudgetRepository implements BudgetRepositoryInterface
return $limit;
}
/**
* @param int $budgetId
* @param Collection $budgets
*
* @return string
*/
private function getBudgetName(int $budgetId, Collection $budgets): string
{
$first = $budgets->filter(
function (Budget $budget) use ($budgetId) {
return $budgetId === $budget->id;
}
);
if (!is_null($first->first())) {
return $first->first()->name;
}
return '(unknown)';
}
/**
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
private function getNoBudgetPeriodReport(Carbon $start, Carbon $end): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end);
$collector->setTypes([TransactionType::WITHDRAWAL]);
$collector->withoutBudget();
$transactions = $collector->getJournals();
$result = [
'entries' => [],
'name' => strval(trans('firefly.no_budget')),
'sum' => '0',
];
foreach ($transactions as $transaction) {
$date = $transaction->date->format($carbonFormat);
if (!isset($result['entries'][$date])) {
$result['entries'][$date] = '0';
}
$result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount);
}
return $result;
}
}

View File

@ -96,11 +96,10 @@ interface BudgetRepositoryInterface
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
* @param bool $noBudget
*
* @return array
*/
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end, bool $noBudget): array;
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array;
/**
* @return Collection
@ -112,6 +111,14 @@ interface BudgetRepositoryInterface
*/
public function getInactiveBudgets(): Collection;
/**
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function getNoBudgetPeriodReport(Collection $accounts, Carbon $start, Carbon $end): array;
/**
* @param Collection $budgets
* @param Collection $accounts

View File

@ -175,32 +175,6 @@ class CategoryRepository implements CategoryRepositoryInterface
return $set;
}
/**
* This method is being used to generate the category overview in the year/multi-year report. Its used
* in both the year/multi-year budget overview AND in the accompanying chart.
*
* @param Collection $categories
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
* @param @bool $noCategory
*
* @return array
*/
public function getCategoryPeriodReport(Collection $categories, Collection $accounts, Carbon $start, Carbon $end, bool $noCategory): array
{
$data = [
'income' => $this->getCategoryReportData($categories, $accounts, $start, $end, $noCategory, [TransactionType::DEPOSIT, TransactionType::TRANSFER]),
'expense' => $this->getCategoryReportData(
$categories, $accounts, $start, $end, $noCategory, [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]
),
];
return $data;
}
/**
* @param Category $category
* @param Collection $accounts
@ -250,6 +224,158 @@ class CategoryRepository implements CategoryRepositoryInterface
return $last;
}
/**
* @param Collection $categories
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function periodExpenses(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
$data = [];
// prep data array:
/** @var Category $category */
foreach ($categories as $category) {
$data[$category->id] = [
'name' => $category->name,
'sum' => '0',
'entries' => [],
];
}
// get all transactions:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setCategories($categories)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->withOpposingAccount()
->enableInternalFilter();
$transactions = $collector->getJournals();
// loop transactions:
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$categoryId = max(intval($transaction->transaction_journal_category_id), intval($transaction->transaction_category_id));
$date = $transaction->date->format($carbonFormat);
$data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $transaction->transaction_amount);
}
return $data;
}
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function periodExpensesNoCategory(Collection $accounts, Carbon $start, Carbon $end): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])->enableInternalFilter();
$collector->withoutCategory();
$transactions = $collector->getJournals();
$result = [
'entries' => [],
'name' => strval(trans('firefly.no_category')),
'sum' => '0',
];
foreach ($transactions as $transaction) {
$date = $transaction->date->format($carbonFormat);
if (!isset($result['entries'][$date])) {
$result['entries'][$date] = '0';
}
$result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount);
}
return $result;
}
/**
* @param Collection $categories
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function periodIncome(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
$data = [];
// prep data array:
/** @var Category $category */
foreach ($categories as $category) {
$data[$category->id] = [
'name' => $category->name,
'sum' => '0',
'entries' => [],
];
}
// get all transactions:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setCategories($categories)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
->withOpposingAccount()
->enableInternalFilter();
$transactions = $collector->getJournals();
// loop transactions:
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$categoryId = max(intval($transaction->transaction_journal_category_id), intval($transaction->transaction_category_id));
$date = $transaction->date->format($carbonFormat);
$data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $transaction->transaction_amount);
}
return $data;
}
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function periodIncomeNoCategory(Collection $accounts, Carbon $start, Carbon $end): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])->enableInternalFilter();
$collector->withoutCategory();
$transactions = $collector->getJournals();
$result = [
'entries' => [],
'name' => strval(trans('firefly.no_category')),
'sum' => '0',
];
foreach ($transactions as $transaction) {
$date = $transaction->date->format($carbonFormat);
if (!isset($result['entries'][$date])) {
$result['entries'][$date] = '0';
}
$result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount);
}
return $result;
}
/**
* @param Collection $categories
* @param Collection $accounts
@ -314,55 +440,6 @@ class CategoryRepository implements CategoryRepositoryInterface
return $category;
}
/**
* @param Collection $categories
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
* @param bool $noCategory
* @param array $types
*
* @return array
*/
private function getCategoryReportData(Collection $categories, Collection $accounts, Carbon $start, Carbon $end, bool $noCategory, array $types): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
$data = [];
// prep data array:
/** @var Category $category */
foreach ($categories as $category) {
$data[$category->id] = [
'name' => $category->name,
'sum' => '0',
'entries' => [],
];
}
// get all transactions:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setCategories($categories)->setTypes($types)
->withOpposingAccount()
->enableInternalFilter();
$transactions = $collector->getJournals();
// loop transactions:
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$categoryId = max(intval($transaction->transaction_journal_category_id), intval($transaction->transaction_category_id));
$date = $transaction->date->format($carbonFormat);
$data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $transaction->transaction_amount);
}
if ($noCategory) {
// and now the same for stuff without a budget:
//$data[0] = $this->getNoBudgetPeriodReport($start, $end);
}
return $data;
}
/**
* @param Collection $categories
* @param Collection $accounts

View File

@ -24,7 +24,6 @@ use Illuminate\Support\Collection;
*/
interface CategoryRepositoryInterface
{
/**
* @param Category $category
*
@ -83,20 +82,6 @@ interface CategoryRepositoryInterface
*/
public function getCategories(): Collection;
/**
* This method is being used to generate the category overview in the year/multi-year report. Its used
* in both the year/multi-year budget overview AND in the accompanying chart.
*
* @param Collection $categories
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
* @param bool $noCategory
*
* @return array
*/
public function getCategoryPeriodReport(Collection $categories, Collection $accounts, Carbon $start, Carbon $end, bool $noCategory): array;
/**
* Return most recent transaction(journal) date.
*
@ -107,6 +92,44 @@ interface CategoryRepositoryInterface
*/
public function lastUseDate(Category $category, Collection $accounts): Carbon;
/**
* @param Collection $categories
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function periodExpenses(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array;
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function periodExpensesNoCategory(Collection $accounts, Carbon $start, Carbon $end): array;
/**
* @param Collection $categories
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function periodIncome(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array;
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
public function periodIncomeNoCategory(Collection $accounts, Carbon $start, Carbon $end): array;
/**
* @param Collection $categories
* @param Collection $accounts

View File

@ -43,7 +43,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*
* @return TransactionCurrency
*/
public function find(int $currencyId) : TransactionCurrency
public function find(int $currencyId): TransactionCurrency
{
$currency = TransactionCurrency::find($currencyId);
if (is_null($currency)) {
@ -61,7 +61,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*
* @return TransactionCurrency
*/
public function findByCode(string $currencyCode) : TransactionCurrency
public function findByCode(string $currencyCode): TransactionCurrency
{
$currency = TransactionCurrency::whereCode($currencyCode)->first();
if (is_null($currency)) {
@ -78,7 +78,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*
* @return TransactionCurrency
*/
public function findByName(string $currencyName) : TransactionCurrency
public function findByName(string $currencyName): TransactionCurrency
{
$preferred = TransactionCurrency::whereName($currencyName)->first();
if (is_null($preferred)) {
@ -95,7 +95,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*
* @return TransactionCurrency
*/
public function findBySymbol(string $currencySymbol) : TransactionCurrency
public function findBySymbol(string $currencySymbol): TransactionCurrency
{
$currency = TransactionCurrency::whereSymbol($currencySymbol)->first();
if (is_null($currency)) {

View File

@ -39,7 +39,7 @@ interface CurrencyRepositoryInterface
*
* @return TransactionCurrency
*/
public function find(int $currencyId) : TransactionCurrency;
public function find(int $currencyId): TransactionCurrency;
/**
* Find by currency code
@ -48,7 +48,7 @@ interface CurrencyRepositoryInterface
*
* @return TransactionCurrency
*/
public function findByCode(string $currencyCode) : TransactionCurrency;
public function findByCode(string $currencyCode): TransactionCurrency;
/**
* Find by currency name
@ -57,7 +57,7 @@ interface CurrencyRepositoryInterface
*
* @return TransactionCurrency
*/
public function findByName(string $currencyName) : TransactionCurrency;
public function findByName(string $currencyName): TransactionCurrency;
/**
* Find by currency symbol
@ -66,7 +66,7 @@ interface CurrencyRepositoryInterface
*
* @return TransactionCurrency
*/
public function findBySymbol(string $currencySymbol) : TransactionCurrency;
public function findBySymbol(string $currencySymbol): TransactionCurrency;
/**
* @return Collection

View File

@ -105,7 +105,7 @@ class JournalRepository implements JournalRepositoryInterface
*
* @return TransactionJournal
*/
public function find(int $journalId) : TransactionJournal
public function find(int $journalId): TransactionJournal
{
$journal = $this->user->transactionJournals()->where('id', $journalId)->first();
if (is_null($journal)) {

View File

@ -52,7 +52,7 @@ interface JournalRepositoryInterface
*
* @return TransactionJournal
*/
public function find(int $journalId) : TransactionJournal;
public function find(int $journalId): TransactionJournal;
/**
* Get users very first transaction journal

View File

@ -172,36 +172,36 @@ class JournalTasker implements JournalTaskerInterface
// go!
$sum = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('account_id', $transaction->account_id)
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->where('transactions.id', '!=', $transactionId)
->where(
function (Builder $q1) use ($date, $order, $journalId, $identifier) {
$q1->where('transaction_journals.date', '<', $date); // date
$q1->orWhere(
function (Builder $q2) use ($date, $order) { // function 1
$q2->where('transaction_journals.date', $date);
$q2->where('transaction_journals.order', '>', $order);
}
);
$q1->orWhere(
function (Builder $q3) use ($date, $order, $journalId) { // function 2
$q3->where('transaction_journals.date', $date);
$q3->where('transaction_journals.order', $order);
$q3->where('transaction_journals.id', '<', $journalId);
}
);
$q1->orWhere(
function (Builder $q4) use ($date, $order, $journalId, $identifier) { // function 3
$q4->where('transaction_journals.date', $date);
$q4->where('transaction_journals.order', $order);
$q4->where('transaction_journals.id', $journalId);
$q4->where('transactions.identifier', '>', $identifier);
}
);
}
)->sum('transactions.amount');
->where('account_id', $transaction->account_id)
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->where('transactions.id', '!=', $transactionId)
->where(
function (Builder $q1) use ($date, $order, $journalId, $identifier) {
$q1->where('transaction_journals.date', '<', $date); // date
$q1->orWhere(
function (Builder $q2) use ($date, $order) { // function 1
$q2->where('transaction_journals.date', $date);
$q2->where('transaction_journals.order', '>', $order);
}
);
$q1->orWhere(
function (Builder $q3) use ($date, $order, $journalId) { // function 2
$q3->where('transaction_journals.date', $date);
$q3->where('transaction_journals.order', $order);
$q3->where('transaction_journals.id', '<', $journalId);
}
);
$q1->orWhere(
function (Builder $q4) use ($date, $order, $journalId, $identifier) { // function 3
$q4->where('transaction_journals.date', $date);
$q4->where('transaction_journals.order', $order);
$q4->where('transaction_journals.id', $journalId);
$q4->where('transactions.identifier', '>', $identifier);
}
);
}
)->sum('transactions.amount');
return strval($sum);
}

View File

@ -117,7 +117,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
*
* @return Collection
*/
public function getPiggyBanksWithAmount() : Collection
public function getPiggyBanksWithAmount(): Collection
{
$set = $this->getPiggyBanks();
foreach ($set as $piggy) {

View File

@ -58,7 +58,7 @@ interface PiggyBankRepositoryInterface
*
* @return Collection
*/
public function getEvents(PiggyBank $piggyBank) : Collection;
public function getEvents(PiggyBank $piggyBank): Collection;
/**
* Highest order of all piggy banks.
@ -72,14 +72,14 @@ interface PiggyBankRepositoryInterface
*
* @return Collection
*/
public function getPiggyBanks() : Collection;
public function getPiggyBanks(): Collection;
/**
* Also add amount in name.
*
* @return Collection
*/
public function getPiggyBanksWithAmount() : Collection;
public function getPiggyBanksWithAmount(): Collection;
/**
* Set all piggy banks to order 0.

View File

@ -93,7 +93,7 @@ class TagRepository implements TagRepositoryInterface
*
* @return Tag
*/
public function find(int $tagId) : Tag
public function find(int $tagId): Tag
{
$tag = $this->user->tags()->find($tagId);
if (is_null($tag)) {
@ -108,7 +108,7 @@ class TagRepository implements TagRepositoryInterface
*
* @return Tag
*/
public function findByTag(string $tag) : Tag
public function findByTag(string $tag): Tag
{
$tags = $this->user->tags()->get();
/** @var Tag $tag */

View File

@ -49,14 +49,14 @@ interface TagRepositoryInterface
*
* @return Tag
*/
public function find(int $tagId) : Tag;
public function find(int $tagId): Tag;
/**
* @param string $tag
*
* @return Tag
*/
public function findByTag(string $tag) : Tag;
public function findByTag(string $tag): Tag;
/**
* This method returns all the user's tags.

View File

@ -114,10 +114,10 @@ class UserRepository implements UserRepositoryInterface
$return['categories'] = $user->categories()->count();
$return['budgets'] = $user->budgets()->count();
$return['budgets_with_limits'] = BudgetLimit::distinct()
->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->where('amount', '>', 0)
->whereNull('budgets.deleted_at')
->where('budgets.user_id', $user->id)->get(['budget_limits.budget_id'])->count();
->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->where('amount', '>', 0)
->whereNull('budgets.deleted_at')
->where('budgets.user_id', $user->id)->get(['budget_limits.budget_id'])->count();
$return['export_jobs'] = $user->exportJobs()->count();
$return['export_jobs_success'] = $user->exportJobs()->where('status', 'export_downloaded')->count();
$return['import_jobs'] = $user->exportJobs()->count();

View File

@ -5,7 +5,9 @@ $(function () {
drawChart();
loadAjaxPartial('budgetPeriodReport', budgetPeriodReportUri);
loadAjaxPartial('categoryPeriodReport', categoryPeriodReportUri);
loadAjaxPartial('categoryExpense', categoryExpenseUri);
loadAjaxPartial('categoryIncome', categoryIncomeUri);
});
function drawChart() {

View File

@ -85,4 +85,5 @@ return [
'file' => 'The :attribute must be a file.',
'in_array' => 'The :attribute field does not exist in :other.',
'present' => 'The :attribute field must be present.',
'amount_zero' => 'The total amount cannot be zero',
];

View File

@ -113,14 +113,27 @@
</div>
</div>
{# same thing but for categories #}
{# same thing but for categories (expenses) #}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'categories'|_ }}</h3>
<h3 class="box-title">{{ 'categories'|_ }} ({{ 'expenses'|_ }})</h3>
</div>
<div class="box-body no-padding table-responsive loading" id="categoryPeriodReport">
<div class="box-body no-padding table-responsive loading" id="categoryExpense">
</div>
</div>
</div>
</div>
{# same thing but for categories (income) #}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'categories'|_ }} ({{ 'income'|_ }})</h3>
</div>
<div class="box-body no-padding table-responsive loading" id="categoryIncome">
</div>
</div>
</div>
@ -164,7 +177,9 @@
var incExpReportUri = '{{ route('reports.data.incExpReport', [start.format('Ymd'), end.format('Ymd'), accountIds]) }}';
var budgetPeriodReportUri = '{{ route('reports.data.budgetPeriodReport', [start.format('Ymd'), end.format('Ymd'), accountIds]) }}';
var categoryPeriodReportUri = '{{ route('reports.data.categoryPeriodReport', [start.format('Ymd'), end.format('Ymd'), accountIds]) }}';
var categoryExpenseUri = '{{ route('reports.data.categoryExpense', [start.format('Ymd'), end.format('Ymd'), accountIds]) }}';
var categoryIncomeUri = '{{ route('reports.data.categoryIncome', [start.format('Ymd'), end.format('Ymd'), accountIds]) }}';
</script>
<script type="text/javascript" src="js/ff/reports/default/all.js"></script>

View File

@ -9,7 +9,7 @@
</tr>
</thead>
<tbody>
{% for id, info in budgets %}
{% for id, info in report %}
<tr>
<td data-value="{{ info.name }}">
<a title="{{ info.name }}" href="#" data-budget="{{ id }}" class="budget-chart-activate">{{ info.name }}</a>

View File

@ -1,75 +1,44 @@
<table class="table table-hover sortable table-condensed">
<thead>
<tr>
<th rowspan="2" data-defaultsort="disabled">{{ 'category'|_ }}</th>
<th data-defaultsort="disabled">{{ 'category'|_ }}</th>
{% for period in periods %}
<th colspan="2" data-defaultsort="disabled">{{ period }}</th>
<th data-defaultsort="disabled">{{ period }}</th>
{% endfor %}
<th colspan="2" data-defaultsort="disabled">{{ 'sum'|_ }}</th>
</tr>
<tr>
{% for period in periods %}
<th data-defaultsign="_19">In</th>
<th data-defaultsign="_19">Out</th>
{% endfor %}
<th data-defaultsign="_19">In</th>
<th data-defaultsign="_19">Out</th>
<th data-defaultsort="disabled">{{ 'sum'|_ }}</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
{% if report.income[category.id] or report.expense[category.id] %}
<tr>
<td data-value="{{ category.name }}">
<a title="{{ category.name }}" href="#" data-category="{{ category.id }}" class="category-chart-activate">{{ category.name }}</a>
{% for id, info in report %}
<tr>
<td data-value="{{ info.name }}">
<a title="{{ info.name }}" href="#" data-category="{{ id }}" class="category-chart-activate">{{ info.name }}</a>
</td>
{% for key, period in periods %}
{# income first #}
{% if(info.entries[key]) %}
<td data-value="{{ info.entries[key] }}">
{{ info.entries[key]|formatAmount }}
</td>
{% else %}
<td data-value="0">
{{ 0|formatAmount }}
</td>
{% endif %}
{% endfor %}
{# if sum of income, display: #}
{% if info.sum %}
<td data-value="{{ info.sum }}">
{{ info.sum|formatAmount }}
</td>
{% for key, period in periods %}
{# income first #}
{% if(report.income[category.id].entries[key]) %}
<td data-value="{{ report.income[category.id].entries[key] }}">
{{ report.income[category.id].entries[key]|formatAmount }}
</td>
{% else %}
<td data-value="0">
{{ 0|formatAmount }}
</td>
{% endif %}
{# expenses #}
{% if(report.expense[category.id].entries[key]) %}
<td data-value="{{ report.expense[category.id].entries[key] }}">
{{ report.expense[category.id].entries[key]|formatAmount }}
</td>
{% else %}
<td data-value="0">
{{ 0|formatAmount }}
</td>
{% endif %}
{% endfor %}
{# if sum of income, display: #}
{% if report.income[category.id].sum %}
<td data-value="{{ report.income[category.id].sum }}">
{{ report.income[category.id].sum|formatAmount }}
</td>
{% else %}
<td data-value="0">
{{ 0|formatAmount }}
</td>
{% endif %}
{# if sum of expense, display: #}
{% if report.expense[category.id].sum %}
<td data-value="{{ report.expense[category.id].sum }}">
{{ report.expense[category.id].sum|formatAmount }}
</td>
{% else %}
<td data-value="0">
{{ 0|formatAmount }}
</td>
{% endif %}
</tr>
{% endif %}
{% else %}
<td data-value="0">
{{ 0|formatAmount }}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -210,6 +210,7 @@ Route::group(
// budgets:
Route::get('/chart/budget/frontpage', ['uses' => 'Chart\BudgetController@frontpage']);
Route::get('/chart/budget/period/0/default/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\BudgetController@periodNoBudget']);
Route::get('/chart/budget/period/{budget}/default/{start_date}/{end_date}/{accountList}', ['uses' => 'Chart\BudgetController@period']);
// this chart is used in reports:
@ -410,11 +411,16 @@ Route::group(
['uses' => 'Report\BudgetController@budgetPeriodReport', 'as' => 'reports.data.budgetPeriodReport']
);
// category period overview
// category period overview (expense and income)
Route::get(
'/reports/data/category-period/{start_date}/{end_date}/{accountList}',
['uses' => 'Report\CategoryController@categoryPeriodReport', 'as' => 'reports.data.categoryPeriodReport']
'/reports/data/category-expense/{start_date}/{end_date}/{accountList}',
['uses' => 'Report\CategoryController@expenseReport', 'as' => 'reports.data.categoryExpense']
);
Route::get(
'/reports/data/category-income/{start_date}/{end_date}/{accountList}',
['uses' => 'Report\CategoryController@incomeReport', 'as' => 'reports.data.categoryIncome']
);
/**
* Rules Controller