mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-01-19 13:03:05 -06:00
Building report from issue #386
This commit is contained in:
parent
57e49c225b
commit
8377a2a0de
@ -60,6 +60,8 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
'account_types.type as account_type',
|
'account_types.type as account_type',
|
||||||
];
|
];
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
|
private $filterInternalTransfers;
|
||||||
|
/** @var bool */
|
||||||
private $filterTransfers = false;
|
private $filterTransfers = false;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
private $joinedBudget = false;
|
private $joinedBudget = false;
|
||||||
@ -127,6 +129,26 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function disableInternalFilter(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->filterInternalTransfers = false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function enableInternalFilter(): JournalCollectorInterface
|
||||||
|
{
|
||||||
|
$this->filterInternalTransfers = true;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
@ -138,6 +160,11 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
$set = $this->filterTransfers($set);
|
$set = $this->filterTransfers($set);
|
||||||
Log::debug(sprintf('Count of set after filterTransfers() is %d', $set->count()));
|
Log::debug(sprintf('Count of set after filterTransfers() is %d', $set->count()));
|
||||||
|
|
||||||
|
// possibly filter "internal" transfers:
|
||||||
|
$set = $this->filterInternalTransfers($set);
|
||||||
|
Log::debug(sprintf('Count of set after filterInternalTransfers() is %d', $set->count()));
|
||||||
|
|
||||||
|
|
||||||
// loop for decryption.
|
// loop for decryption.
|
||||||
$set->each(
|
$set->each(
|
||||||
function (Transaction $transaction) {
|
function (Transaction $transaction) {
|
||||||
@ -501,6 +528,47 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $set
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
private function filterInternalTransfers(Collection $set): Collection
|
||||||
|
{
|
||||||
|
if ($this->filterInternalTransfers === false) {
|
||||||
|
Log::debug('Did NO filtering for internal transfers on given set.');
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
if ($this->joinedOpposing === false) {
|
||||||
|
Log::error('Cannot filter internal transfers because no opposing information is present.');
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
$accountIds = $this->accountIds;
|
||||||
|
$set = $set->filter(
|
||||||
|
function (Transaction $transaction) use ($accountIds) {
|
||||||
|
// both id's in $accountids?
|
||||||
|
if (in_array($transaction->account_id, $accountIds) && in_array($transaction->opposing_account_id, $accountIds)) {
|
||||||
|
Log::debug(
|
||||||
|
sprintf(
|
||||||
|
'Transaction #%d has #%d and #%d in set, so removed',
|
||||||
|
$transaction->id, $transaction->account_id, $transaction->opposing_account_id
|
||||||
|
), $accountIds
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the set of accounts used by the collector includes more than one asset
|
* If the set of accounts used by the collector includes more than one asset
|
||||||
* account, chances are the set include double entries: transfers get selected
|
* account, chances are the set include double entries: transfers get selected
|
||||||
@ -583,6 +651,7 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
private function joinOpposingTables()
|
private function joinOpposingTables()
|
||||||
{
|
{
|
||||||
if (!$this->joinedOpposing) {
|
if (!$this->joinedOpposing) {
|
||||||
|
Log::debug('joinedOpposing is false');
|
||||||
// join opposing transaction (hard):
|
// join opposing transaction (hard):
|
||||||
$this->query->leftJoin(
|
$this->query->leftJoin(
|
||||||
'transactions as opposing', function (JoinClause $join) {
|
'transactions as opposing', function (JoinClause $join) {
|
||||||
@ -595,11 +664,12 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
$this->query->leftJoin('account_types as opposing_account_types', 'opposing_accounts.account_type_id', '=', 'opposing_account_types.id');
|
$this->query->leftJoin('account_types as opposing_account_types', 'opposing_accounts.account_type_id', '=', 'opposing_account_types.id');
|
||||||
$this->query->whereNull('opposing.deleted_at');
|
$this->query->whereNull('opposing.deleted_at');
|
||||||
|
|
||||||
$this->fields[] = 'opposing.account_id as opposing_account_id';
|
$this->fields[] = 'opposing.account_id as opposing_account_id';
|
||||||
$this->fields[] = 'opposing_accounts.name as opposing_account_name';
|
$this->fields[] = 'opposing_accounts.name as opposing_account_name';
|
||||||
$this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted';
|
$this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted';
|
||||||
$this->fields[] = 'opposing_account_types.type as opposing_account_type';
|
$this->fields[] = 'opposing_account_types.type as opposing_account_type';
|
||||||
|
$this->joinedOpposing = true;
|
||||||
|
Log::debug('joinedOpposing is now true!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -622,17 +692,17 @@ class JournalCollector implements JournalCollectorInterface
|
|||||||
{
|
{
|
||||||
|
|
||||||
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||||
->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transaction_journals.transaction_currency_id')
|
->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transaction_journals.transaction_currency_id')
|
||||||
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
||||||
->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id')
|
->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id')
|
||||||
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||||
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
|
->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')
|
||||||
->whereNull('transactions.deleted_at')
|
->whereNull('transactions.deleted_at')
|
||||||
->whereNull('transaction_journals.deleted_at')
|
->whereNull('transaction_journals.deleted_at')
|
||||||
->where('transaction_journals.user_id', $this->user->id)
|
->where('transaction_journals.user_id', $this->user->id)
|
||||||
->orderBy('transaction_journals.date', 'DESC')
|
->orderBy('transaction_journals.date', 'DESC')
|
||||||
->orderBy('transaction_journals.order', 'ASC')
|
->orderBy('transaction_journals.order', 'ASC')
|
||||||
->orderBy('transaction_journals.id', 'DESC');
|
->orderBy('transaction_journals.id', 'DESC');
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
|
|
||||||
|
@ -38,6 +38,16 @@ interface JournalCollectorInterface
|
|||||||
*/
|
*/
|
||||||
public function disableFilter(): JournalCollectorInterface;
|
public function disableFilter(): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function disableInternalFilter(): JournalCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return JournalCollectorInterface
|
||||||
|
*/
|
||||||
|
public function enableInternalFilter(): JournalCollectorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection
|
* @return Collection
|
||||||
*/
|
*/
|
||||||
@ -46,7 +56,7 @@ interface JournalCollectorInterface
|
|||||||
/**
|
/**
|
||||||
* @return LengthAwarePaginator
|
* @return LengthAwarePaginator
|
||||||
*/
|
*/
|
||||||
public function getPaginatedJournals():LengthAwarePaginator;
|
public function getPaginatedJournals(): LengthAwarePaginator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Collection $accounts
|
* @param Collection $accounts
|
||||||
|
@ -15,10 +15,15 @@ namespace FireflyIII\Http\Controllers\Report;
|
|||||||
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
use FireflyIII\Helpers\Report\ReportHelperInterface;
|
use FireflyIII\Helpers\Report\ReportHelperInterface;
|
||||||
use FireflyIII\Http\Controllers\Controller;
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Models\Category;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||||
use FireflyIII\Support\CacheProperties;
|
use FireflyIII\Support\CacheProperties;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Navigation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class CategoryController
|
* Class CategoryController
|
||||||
@ -27,6 +32,84 @@ use Illuminate\Support\Collection;
|
|||||||
*/
|
*/
|
||||||
class CategoryController extends Controller
|
class CategoryController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param Collection $accounts
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function categoryPeriodReport(Carbon $start, Carbon $end, Collection $accounts)
|
||||||
|
{
|
||||||
|
/** @var CategoryRepositoryInterface $repository */
|
||||||
|
$repository = app(CategoryRepositoryInterface::class);
|
||||||
|
$categories = $repository->getCategories();
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
// income only:
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts($accounts)->setRange($start, $end)
|
||||||
|
->withOpposingAccount()
|
||||||
|
->enableInternalFilter()
|
||||||
|
->setCategories($categories);
|
||||||
|
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
|
||||||
|
// this is the date format we need:
|
||||||
|
// define period to group on:
|
||||||
|
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
|
||||||
|
|
||||||
|
// this is the set of transactions for this period
|
||||||
|
// in these budgets. Now they must be grouped (manually)
|
||||||
|
// id, period => amount
|
||||||
|
$income = [];
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
$categoryId = max(intval($transaction->transaction_journal_category_id), intval($transaction->transaction_category_id));
|
||||||
|
$date = $transaction->date->format($carbonFormat);
|
||||||
|
|
||||||
|
if (!isset($income[$categoryId])) {
|
||||||
|
$income[$categoryId]['name'] = $this->getCategoryName($categoryId, $categories);
|
||||||
|
$income[$categoryId]['sum'] = '0';
|
||||||
|
$income[$categoryId]['entries'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($income[$categoryId]['entries'][$date])) {
|
||||||
|
$income[$categoryId]['entries'][$date] = '0';
|
||||||
|
}
|
||||||
|
$income[$categoryId]['entries'][$date] = bcadd($income[$categoryId]['entries'][$date], $transaction->transaction_amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// and now the same for stuff without a category:
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAllAssetAccounts()->setRange($start, $end);
|
||||||
|
$collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]);
|
||||||
|
$collector->withoutCategory();
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
|
||||||
|
$income[0]['entries'] = [];
|
||||||
|
$income[0]['name'] = strval(trans('firefly.no_category'));
|
||||||
|
$income[0]['sum'] = '0';
|
||||||
|
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
$date = $transaction->date->format($carbonFormat);
|
||||||
|
|
||||||
|
if (!isset($income[0]['entries'][$date])) {
|
||||||
|
$income[0]['entries'][$date] = '0';
|
||||||
|
}
|
||||||
|
$income[0]['entries'][$date] = bcadd($income[0]['entries'][$date], $transaction->transaction_amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
$periods = Navigation::listOfPeriods($start, $end);
|
||||||
|
|
||||||
|
$income = $this->filterCategoryPeriodReport($income);
|
||||||
|
|
||||||
|
$result = view('reports.partials.category-period', compact('categories', 'periods', 'income'))->render();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ReportHelperInterface $helper
|
* @param ReportHelperInterface $helper
|
||||||
@ -56,4 +139,52 @@ class CategoryController extends Controller
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters empty results from category period report
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function filterCategoryPeriodReport(array $data): array
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int $categoryId
|
||||||
|
* @var array $set
|
||||||
|
*/
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)';
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -226,55 +226,34 @@ class BudgetRepository implements BudgetRepositoryInterface
|
|||||||
*/
|
*/
|
||||||
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array
|
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array
|
||||||
{
|
{
|
||||||
|
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
|
||||||
|
$data = [];
|
||||||
|
// prep data array:
|
||||||
|
/** @var Budget $budget */
|
||||||
|
foreach ($budgets as $budget) {
|
||||||
|
$data[$budget->id] = [
|
||||||
|
'name' => $budget->name,
|
||||||
|
'sum' => '0',
|
||||||
|
'entries' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all transactions:
|
||||||
/** @var JournalCollectorInterface $collector */
|
/** @var JournalCollectorInterface $collector */
|
||||||
$collector = app(JournalCollectorInterface::class);
|
$collector = app(JournalCollectorInterface::class);
|
||||||
$collector->setAllAssetAccounts()->setRange($start, $end);
|
$collector->setAccounts($accounts)->setRange($start, $end);
|
||||||
$collector->setBudgets($budgets);
|
$collector->setBudgets($budgets);
|
||||||
$transactions = $collector->getJournals();
|
$transactions = $collector->getJournals();
|
||||||
|
|
||||||
// this is the date format we need:
|
// loop transactions:
|
||||||
// define period to group on:
|
/** @var Transaction $transaction */
|
||||||
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
|
|
||||||
|
|
||||||
// this is the set of transactions for this period
|
|
||||||
// in these budgets. Now they must be grouped (manually)
|
|
||||||
// id, period => amount
|
|
||||||
$data = [];
|
|
||||||
foreach ($transactions as $transaction) {
|
foreach ($transactions as $transaction) {
|
||||||
$budgetId = max(intval($transaction->transaction_journal_budget_id), intval($transaction->transaction_budget_id));
|
$budgetId = max(intval($transaction->transaction_journal_budget_id), intval($transaction->transaction_budget_id));
|
||||||
$date = $transaction->date->format($carbonFormat);
|
$date = $transaction->date->format($carbonFormat);
|
||||||
|
$data[$budgetId]['entries'][$date] = bcadd($data[$budgetId]['entries'][$date] ?? '0', $transaction->transaction_amount);
|
||||||
if (!isset($data[$budgetId])) {
|
|
||||||
$data[$budgetId]['name'] = $this->getBudgetName($budgetId, $budgets);
|
|
||||||
$data[$budgetId]['sum'] = '0';
|
|
||||||
$data[$budgetId]['entries'] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($data[$budgetId]['entries'][$date])) {
|
|
||||||
$data[$budgetId]['entries'][$date] = '0';
|
|
||||||
}
|
|
||||||
$data[$budgetId]['entries'][$date] = bcadd($data[$budgetId]['entries'][$date], $transaction->transaction_amount);
|
|
||||||
}
|
}
|
||||||
// and now the same for stuff without a budget:
|
// and now the same for stuff without a budget:
|
||||||
/** @var JournalCollectorInterface $collector */
|
$data[0] = $this->getNoBudgetPeriodReport($start, $end);
|
||||||
$collector = app(JournalCollectorInterface::class);
|
|
||||||
$collector->setAllAssetAccounts()->setRange($start, $end);
|
|
||||||
$collector->setTypes([TransactionType::WITHDRAWAL]);
|
|
||||||
$collector->withoutBudget();
|
|
||||||
$transactions = $collector->getJournals();
|
|
||||||
|
|
||||||
$data[0]['entries'] = [];
|
|
||||||
$data[0]['name'] = strval(trans('firefly.no_budget'));
|
|
||||||
$data[0]['sum'] = '0';
|
|
||||||
|
|
||||||
foreach ($transactions as $transaction) {
|
|
||||||
$date = $transaction->date->format($carbonFormat);
|
|
||||||
|
|
||||||
if (!isset($data[0]['entries'][$date])) {
|
|
||||||
$data[0]['entries'][$date] = '0';
|
|
||||||
}
|
|
||||||
$data[0]['entries'][$date] = bcadd($data[0]['entries'][$date], $transaction->transaction_amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
|
|
||||||
@ -332,19 +311,23 @@ class BudgetRepository implements BudgetRepositoryInterface
|
|||||||
Log::debug('spentInPeriod: and these accounts: ', $accountIds);
|
Log::debug('spentInPeriod: and these accounts: ', $accountIds);
|
||||||
Log::debug(sprintf('spentInPeriod: Start date is "%s", end date is "%s"', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
Log::debug(sprintf('spentInPeriod: Start date is "%s", end date is "%s"', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||||
|
|
||||||
$fromJournalsQuery = TransactionJournal::leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
|
$fromJournalsQuery = TransactionJournal::leftJoin(
|
||||||
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
'budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'
|
||||||
->leftJoin(
|
)
|
||||||
'transactions', function (JoinClause $join) {
|
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||||
$join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0);
|
->leftJoin(
|
||||||
}
|
'transactions', function (JoinClause $join) {
|
||||||
)
|
$join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where(
|
||||||
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
|
'transactions.amount', '<', 0
|
||||||
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
|
);
|
||||||
->whereNull('transaction_journals.deleted_at')
|
}
|
||||||
->whereNull('transactions.deleted_at')
|
)
|
||||||
->where('transaction_journals.user_id', $this->user->id)
|
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
|
||||||
->where('transaction_types.type', 'Withdrawal');
|
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
|
||||||
|
->whereNull('transaction_journals.deleted_at')
|
||||||
|
->whereNull('transactions.deleted_at')
|
||||||
|
->where('transaction_journals.user_id', $this->user->id)
|
||||||
|
->where('transaction_types.type', 'Withdrawal');
|
||||||
|
|
||||||
// add budgets:
|
// add budgets:
|
||||||
if ($budgets->count() > 0) {
|
if ($budgets->count() > 0) {
|
||||||
@ -368,15 +351,15 @@ class BudgetRepository implements BudgetRepositoryInterface
|
|||||||
* and transactions.account_id in (2)
|
* and transactions.account_id in (2)
|
||||||
*/
|
*/
|
||||||
$fromTransactionsQuery = Transaction::leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id')
|
$fromTransactionsQuery = Transaction::leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id')
|
||||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||||
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||||
->whereNull('transactions.deleted_at')
|
->whereNull('transactions.deleted_at')
|
||||||
->whereNull('transaction_journals.deleted_at')
|
->whereNull('transaction_journals.deleted_at')
|
||||||
->where('transactions.amount', '<', 0)
|
->where('transactions.amount', '<', 0)
|
||||||
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
|
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
|
||||||
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
|
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
|
||||||
->where('transaction_journals.user_id', $this->user->id)
|
->where('transaction_journals.user_id', $this->user->id)
|
||||||
->where('transaction_types.type', 'Withdrawal');
|
->where('transaction_types.type', 'Withdrawal');
|
||||||
|
|
||||||
// add budgets:
|
// add budgets:
|
||||||
if ($budgets->count() > 0) {
|
if ($budgets->count() > 0) {
|
||||||
@ -569,4 +552,37 @@ class BudgetRepository implements BudgetRepositoryInterface
|
|||||||
|
|
||||||
return '(unknown)';
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,8 +246,8 @@ class Navigation
|
|||||||
* 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".
|
||||||
*
|
*
|
||||||
* @param Carbon $start
|
* @param \Carbon\Carbon $start
|
||||||
* @param Carbon $end
|
* @param \Carbon\Carbon $end
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@ -270,8 +270,8 @@ class Navigation
|
|||||||
* If the date difference between start and end is less than a month, method returns "1D". If the difference is less than a year,
|
* If the date difference between start and end is less than a month, method returns "1D". If the difference is less than a year,
|
||||||
* method returns "1M". If the date difference is larger, method returns "1Y".
|
* method returns "1M". If the date difference is larger, method returns "1Y".
|
||||||
*
|
*
|
||||||
* @param Carbon $start
|
* @param \Carbon\Carbon $start
|
||||||
* @param Carbon $end
|
* @param \Carbon\Carbon $end
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -15,7 +15,7 @@ var fixHelper = function (e, tr) {
|
|||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
lineChart('chart/account/' + accountID, 'overview-chart');
|
lineChart('chart/account/' + accountID, 'overview-chart');
|
||||||
pieChart(incomeByCategoryUri, 'account-cat-in');
|
pieChart(incomeByCategoryUri, 'account-cat-in');
|
||||||
pieChart(expenseByCategoryUri, 'account-cat-out');
|
pieChart(expenseByCategoryUri, 'account-cat-out');
|
||||||
pieChart(expenseByBudgetUri, 'account-budget-out');
|
pieChart(expenseByBudgetUri, 'account-budget-out');
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
/* globals google, accountIds, budgetPeriodReportUri */
|
/* globals google, accountIds, budgetPeriodReportUri, categoryPeriodReportUri */
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
drawChart();
|
drawChart();
|
||||||
|
|
||||||
loadAjaxPartial('budgetPeriodReport', budgetPeriodReportUri);
|
loadAjaxPartial('budgetPeriodReport', budgetPeriodReportUri);
|
||||||
|
loadAjaxPartial('categoryPeriodReport', categoryPeriodReportUri);
|
||||||
});
|
});
|
||||||
|
|
||||||
function drawChart() {
|
function drawChart() {
|
||||||
|
@ -113,6 +113,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# same thing but for categories #}
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="box-body no-padding table-responsive loading" id="categoryPeriodReport">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script type="text/javascript" src="js/lib/bootstrap-sortable.js"></script>
|
<script type="text/javascript" src="js/lib/bootstrap-sortable.js"></script>
|
||||||
@ -135,6 +148,7 @@
|
|||||||
var incExpReportUri = '{{ route('reports.data.incExpReport', [start.format('Ymd'), end.format('Ymd'), accountIds]) }}';
|
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 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]) }}';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/ff/reports/default/all.js"></script>
|
<script type="text/javascript" src="js/ff/reports/default/all.js"></script>
|
||||||
|
54
resources/views/reports/partials/category-period.twig
Normal file
54
resources/views/reports/partials/category-period.twig
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<table class="table table-hover sortable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th rowspan="2" data-defaultsort="disabled">{{ 'category'|_ }}</th>
|
||||||
|
{% for period in periods %}
|
||||||
|
<th colspan="2" 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>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for category in categories %}
|
||||||
|
{% if income[category.id] %}
|
||||||
|
<tr>
|
||||||
|
<td data-value="{{ category.name }}">
|
||||||
|
<a title="{{ category.name }}" href="#" data-category="{{ category.id }}" class="category-chart-activate">{{ category.name }}</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{% for key, period in periods %}
|
||||||
|
{# income first #}
|
||||||
|
{% if(income[category.id].entries[key]) %}
|
||||||
|
<td data-value="{{ income[category.id].entries[key] }}">
|
||||||
|
{{ income[category.id].entries[key]|formatAmount }}
|
||||||
|
</td>
|
||||||
|
{% else %}
|
||||||
|
<td data-value="0">
|
||||||
|
{{ 0|formatAmount }}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# expenses #}
|
||||||
|
<td data-value="0">
|
||||||
|
<!-- expense -->
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
<td data-value="{{ info.sum }}">
|
||||||
|
0
|
||||||
|
</td>
|
||||||
|
<td data-value="{{ info.sum }}">
|
||||||
|
1
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
@ -402,12 +402,19 @@ Route::group(
|
|||||||
'/reports/data/budget-report/{start_date}/{end_date}/{accountList}',
|
'/reports/data/budget-report/{start_date}/{end_date}/{accountList}',
|
||||||
['uses' => 'Report\BudgetController@budgetReport', 'as' => 'reports.data.budgetReport']
|
['uses' => 'Report\BudgetController@budgetReport', 'as' => 'reports.data.budgetReport']
|
||||||
);
|
);
|
||||||
|
|
||||||
// budget period overview
|
// budget period overview
|
||||||
Route::get(
|
Route::get(
|
||||||
'/reports/data/budget-period/{start_date}/{end_date}/{accountList}',
|
'/reports/data/budget-period/{start_date}/{end_date}/{accountList}',
|
||||||
['uses' => 'Report\BudgetController@budgetPeriodReport', 'as' => 'reports.data.budgetPeriodReport']
|
['uses' => 'Report\BudgetController@budgetPeriodReport', 'as' => 'reports.data.budgetPeriodReport']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// category period overview
|
||||||
|
Route::get(
|
||||||
|
'/reports/data/category-period/{start_date}/{end_date}/{accountList}',
|
||||||
|
['uses' => 'Report\CategoryController@categoryPeriodReport', 'as' => 'reports.data.categoryPeriodReport']
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rules Controller
|
* Rules Controller
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user