This commit is contained in:
James Cole 2018-03-25 13:30:55 +02:00
parent 41e468b507
commit 992657b942
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
13 changed files with 392 additions and 210 deletions

View File

@ -25,11 +25,13 @@ namespace FireflyIII\Helpers\Collector;
use Carbon\Carbon; use Carbon\Carbon;
use DB; use DB;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Filter\CountAttachmentsFilter;
use FireflyIII\Helpers\Filter\FilterInterface; use FireflyIII\Helpers\Filter\FilterInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Helpers\Filter\NegativeAmountFilter; use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\OpposingAccountFilter; use FireflyIII\Helpers\Filter\OpposingAccountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter; use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Filter\SplitIndicatorFilter;
use FireflyIII\Helpers\Filter\TransferFilter; use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
@ -760,6 +762,8 @@ class JournalCollector implements JournalCollectorInterface
TransferFilter::class => new TransferFilter, TransferFilter::class => new TransferFilter,
PositiveAmountFilter::class => new PositiveAmountFilter, PositiveAmountFilter::class => new PositiveAmountFilter,
NegativeAmountFilter::class => new NegativeAmountFilter, NegativeAmountFilter::class => new NegativeAmountFilter,
SplitIndicatorFilter::class => new SplitIndicatorFilter,
CountAttachmentsFilter::class => new CountAttachmentsFilter,
]; ];
Log::debug(sprintf('Will run %d filters on the set.', count($this->filters))); Log::debug(sprintf('Will run %d filters on the set.', count($this->filters)));
foreach ($this->filters as $enabled) { foreach ($this->filters as $enabled) {

View File

@ -0,0 +1,67 @@
<?php
/**
* CountAttachmentsFilter.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Helpers\Filter;
use DB;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Support\Collection;
/**
* Class CountAttachmentsFilter
*/
class CountAttachmentsFilter implements FilterInterface
{
/**
* @param Collection $set
*
* @return Collection
*/
public function filter(Collection $set): Collection
{
// grab journal ID's:
$ids = $set->pluck('journal_id')->toArray();
$result = DB::table('attachments')
->whereNull('deleted_at')
->whereIn('attachable_id', $ids)
->where('attachable_type', TransactionJournal::class)
->groupBy('attachable_id')->get(['attachable_id', DB::raw('COUNT(*) as number')]);
$counter = [];
foreach ($result as $row) {
$counter[$row->attachable_id] = $row->number;
}
$set->each(
function (Transaction $transaction) use ($counter) {
$id = (int)$transaction->journal_id;
$count = $counter[$id] ?? 0;
$transaction->attachmentCount = $count;
}
);
return $set;
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* SplitIndicatorFilter.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Helpers\Filter;
use DB;
use FireflyIII\Models\Transaction;
use Illuminate\Support\Collection;
/**
* Class SplitIndicatorFilter
*/
class SplitIndicatorFilter implements FilterInterface
{
/**
* @param Collection $set
*
* @return Collection
*/
public function filter(Collection $set): Collection
{
// grab journal ID's:
$ids = $set->pluck('journal_id')->toArray();
$result = DB::table('transactions')
->whereNull('deleted_at')->whereIn('transaction_journal_id', $ids)
->groupBy('transaction_journal_id')->get(['transaction_journal_id', DB::raw('COUNT(*) as number')]);
$counter = [];
foreach ($result as $row) {
$counter[$row->transaction_journal_id] = $row->number;
}
$set->each(
function (Transaction $transaction) use ($counter) {
$id = (int)$transaction->journal_id;
$count = $counter[$id] ?? 0;
$transaction->is_split = false;
if ($count > 2) {
$transaction->is_split = true;
}
}
);
return $set;
}
}

View File

@ -435,33 +435,34 @@ class AccountController extends Controller
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range); $dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection; $entries = new Collection;
// loop dates // loop dates
foreach ($dates as $date) { foreach ($dates as $currentDate) {
// try a collector for income: // try a collector for income:
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($date['start'], $date['end'])->setTypes([TransactionType::DEPOSIT]) $collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount(); ->withOpposingAccount();
$earned = strval($collector->getJournals()->sum('transaction_amount')); $earned = (string)$collector->getJournals()->sum('transaction_amount');
// try a collector for expenses: // try a collector for expenses:
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($date['start'], $date['end'])->setTypes([TransactionType::WITHDRAWAL]) $collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount(); ->withOpposingAccount();
$spent = strval($collector->getJournals()->sum('transaction_amount')); $spent = (string)$collector->getJournals()->sum('transaction_amount');
$dateName = app('navigation')->periodShow($date['start'], $date['period']); $dateName = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
$entries->push( $entries->push(
[ [
'name' => $dateName, 'name' => $dateName,
'spent' => $spent, 'spent' => $spent,
'earned' => $earned, 'earned' => $earned,
'start' => $date['start']->format('Y-m-d'), 'start' => $currentDate['start']->format('Y-m-d'),
'end' => $date['end']->format('Y-m-d'), 'end' => $currentDate['end']->format('Y-m-d'),
] ]
); );
} }

View File

@ -447,20 +447,20 @@ class CategoryController extends Controller
if ($cache->has()) { if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore return $cache->get(); // @codeCoverageIgnore
} }
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range); $dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = new Collection; $entries = new Collection;
foreach ($dates as $date) { foreach ($dates as $currentDate) {
$spent = $this->repository->spentInPeriod(new Collection([$category]), $accounts, $date['start'], $date['end']); $spent = $this->repository->spentInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']);
$earned = $this->repository->earnedInPeriod(new Collection([$category]), $accounts, $date['start'], $date['end']); $earned = $this->repository->earnedInPeriod(new Collection([$category]), $accounts, $currentDate['start'], $currentDate['end']);
$dateStr = $date['end']->format('Y-m-d'); $dateStr = $currentDate['end']->format('Y-m-d');
$dateName = app('navigation')->periodShow($date['end'], $date['period']); $dateName = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
// amount transferred // amount transferred
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->setCategory($category) $collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->setCategory($category)
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
$collector->removeFilter(InternalTransferFilter::class); $collector->removeFilter(InternalTransferFilter::class);
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount')); $transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
@ -473,7 +473,7 @@ class CategoryController extends Controller
'earned' => $earned, 'earned' => $earned,
'sum' => bcadd($earned, $spent), 'sum' => bcadd($earned, $spent),
'transferred' => $transferred, 'transferred' => $transferred,
'date' => clone $date['end'], 'date' => clone $currentDate['end'],
] ]
); );
} }

View File

@ -25,13 +25,14 @@ namespace FireflyIII\Http\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\CountAttachmentsFilter;
use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Helpers\Filter\SplitIndicatorFilter;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Transformers\TransactionTransformer; use FireflyIII\Transformers\TransactionTransformer;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -45,6 +46,9 @@ use View;
*/ */
class TransactionController extends Controller class TransactionController extends Controller
{ {
/** @var JournalRepositoryInterface */
private $repository;
/** /**
* TransactionController constructor. * TransactionController constructor.
*/ */
@ -56,6 +60,7 @@ class TransactionController extends Controller
function ($request, $next) { function ($request, $next) {
app('view')->share('title', trans('firefly.transactions')); app('view')->share('title', trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-repeat'); app('view')->share('mainTitleIcon', 'fa-repeat');
$this->repository = app(JournalRepositoryInterface::class);
return $next($request); return $next($request);
} }
@ -63,96 +68,141 @@ class TransactionController extends Controller
} }
/** /**
* @param Request $request * Index for a range of transactions.
* @param JournalRepositoryInterface $repository *
* @param string $what * @param Request $request
* @param string $moment * @param string $what
* @param Carbon $start
* @param Carbon $end
* *
* @return View * @return View
* *
*/ */
public function index(Request $request, JournalRepositoryInterface $repository, string $what, string $moment = '') public function index(Request $request, string $what, Carbon $start = null, Carbon $end = null)
{ {
// default values:
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
$types = config('firefly.transactionTypesByWhat.' . $what); $types = config('firefly.transactionTypesByWhat.' . $what);
$page = intval($request->get('page')); $page = (int)$request->get('page');
$pageSize = intval(Preferences::get('listPageSize', 50)->data); $pageSize = (int)Preferences::get('listPageSize', 50)->data;
$range = Preferences::get('viewRange', '1M')->data;
$start = null;
$end = null;
$periods = new Collection;
$path = route('transactions.index', [$what]); $path = route('transactions.index', [$what]);
if (null === $start) {
// prep for "all" view. $start = session('start');
if ('all' === $moment) { $end = session('end');
$subTitle = trans('firefly.all_' . $what); }
$first = $repository->first(); if (null === $end) {
$start = $first->date ?? new Carbon; $end = session('end');
$end = new Carbon;
$path = route('transactions.index', [$what, 'all']);
} }
// prep for "specific date" view. if ($end < $start) {
if (strlen($moment) > 0 && 'all' !== $moment) { list($start, $end) = [$end, $start];
$start = new Carbon($moment);
$end = app('navigation')->endOfPeriod($start, $range);
$path = route('transactions.index', [$what, $moment]);
$subTitle = trans(
'firefly.title_' . $what . '_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
$periods = $this->getPeriodOverview($what);
}
// prep for current period
if (0 === strlen($moment)) {
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
$periods = $this->getPeriodOverview($what);
$subTitle = trans(
'firefly.title_' . $what . '_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
);
} }
$startStr = $start->formatLocalized($this->monthAndDayFormat);
$endStr = $end->formatLocalized($this->monthAndDayFormat);
$subTitle = trans('firefly.title_' . $what . '_between', ['start' => $startStr, 'end' => $endStr]);
$periods = $this->getPeriodOverview($what, $end);
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount(); $collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
->withBudgetInformation()->withCategoryInformation();
$collector->removeFilter(InternalTransferFilter::class); $collector->removeFilter(InternalTransferFilter::class);
$collector->addFilter(SplitIndicatorFilter::class);
$collector->addFilter(CountAttachmentsFilter::class);
$transactions = $collector->getPaginatedJournals(); $transactions = $collector->getPaginatedJournals();
$transactions->setPath($path); $transactions->setPath($path);
return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'transactions', 'periods', 'start', 'end', 'moment')); return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'transactions', 'periods', 'start', 'end'));
//
// // prep for "all" view.
// if ('all' === $moment) {
// $subTitle = trans('firefly.all_' . $what);
// $first = $this->repository->first();
// $start = $first->date ?? new Carbon;
// $end = new Carbon;
// $path = route('transactions.index', [$what, 'all']);
// }
//
// // prep for "specific date" view.
// if (strlen($moment) > 0 && 'all' !== $moment) {
// $start = new Carbon($moment);
// $end = app('navigation')->endOfPeriod($start, $range);
// $path = route('transactions.index', [$what, $moment]);
//
// }
// // prep for current period
// if (0 === strlen($moment)) {
// $start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
// $end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
// $subTitle = trans(
// 'firefly.title_' . $what . '_between',
// ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
// );
// }
} }
/** /**
* @param Request $request * @param Request $request
* @param JournalRepositoryInterface $repository * @param string $what
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function indexAll(Request $request, string $what)
{
$subTitleIcon = config('firefly.transactionIconsByWhat.' . $what);
$types = config('firefly.transactionTypesByWhat.' . $what);
$page = (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$path = route('transactions.index.all', [$what]);
$first = $this->repository->first();
$start = $first->date ?? new Carbon;
$end = new Carbon;
$subTitle = trans('firefly.all_' . $what);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount()
->withBudgetInformation()->withCategoryInformation();
$collector->removeFilter(InternalTransferFilter::class);
$collector->addFilter(SplitIndicatorFilter::class);
$collector->addFilter(CountAttachmentsFilter::class);
$transactions = $collector->getPaginatedJournals();
$transactions->setPath($path);
return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'transactions', 'start', 'end'));
}
/**
* @param Request $request
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function reconcile(Request $request, JournalRepositoryInterface $repository) public function reconcile(Request $request)
{ {
$transactionIds = $request->get('transactions'); $transactionIds = $request->get('transactions');
foreach ($transactionIds as $transactionId) { foreach ($transactionIds as $transactionId) {
$transactionId = intval($transactionId); $transactionId = (int)$transactionId;
$transaction = $repository->findTransaction($transactionId); $transaction = $this->repository->findTransaction($transactionId);
Log::debug(sprintf('Transaction ID is %d', $transaction->id)); Log::debug(sprintf('Transaction ID is %d', $transaction->id));
$repository->reconcile($transaction); $this->repository->reconcile($transaction);
} }
return response()->json(['ok' => 'reconciled']); return response()->json(['ok' => 'reconciled']);
} }
/** /**
* @param Request $request * @param Request $request
* @param JournalRepositoryInterface $repository
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function reorder(Request $request, JournalRepositoryInterface $repository) public function reorder(Request $request)
{ {
$ids = $request->get('items'); $ids = $request->get('items');
$date = new Carbon($request->get('date')); $date = new Carbon($request->get('date'));
@ -160,9 +210,9 @@ class TransactionController extends Controller
$order = 0; $order = 0;
$ids = array_unique($ids); $ids = array_unique($ids);
foreach ($ids as $id) { foreach ($ids as $id) {
$journal = $repository->find(intval($id)); $journal = $this->repository->find((int)$id);
if ($journal && $journal->date->isSameDay($date)) { if ($journal && $journal->date->isSameDay($date)) {
$repository->setOrder($journal, $order); $this->repository->setOrder($journal, $order);
++$order; ++$order;
} }
} }
@ -174,13 +224,12 @@ class TransactionController extends Controller
/** /**
* @param TransactionJournal $journal * @param TransactionJournal $journal
* @param JournalRepositoryInterface $repository
* @param LinkTypeRepositoryInterface $linkTypeRepository * @param LinkTypeRepositoryInterface $linkTypeRepository
* *
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
* @throws FireflyException * @throws FireflyException
*/ */
public function show(TransactionJournal $journal, JournalRepositoryInterface $repository, LinkTypeRepositoryInterface $linkTypeRepository) public function show(TransactionJournal $journal, LinkTypeRepositoryInterface $linkTypeRepository)
{ {
if ($this->isOpeningBalance($journal)) { if ($this->isOpeningBalance($journal)) {
return $this->redirectToAccount($journal); return $this->redirectToAccount($journal);
@ -206,7 +255,7 @@ class TransactionController extends Controller
$transactions[] = $transformer->transform($transaction); $transactions[] = $transformer->transform($transaction);
} }
$events = $repository->getPiggyBankEvents($journal); $events = $this->repository->getPiggyBankEvents($journal);
$what = strtolower($transactionType); $what = strtolower($transactionType);
$subTitle = trans('firefly.' . $what) . ' "' . $journal->description . '"'; $subTitle = trans('firefly.' . $what) . ' "' . $journal->description . '"';
@ -218,61 +267,44 @@ class TransactionController extends Controller
* *
* @return Collection * @return Collection
*/ */
private function getPeriodOverview(string $what): Collection private function getPeriodOverview(string $what, Carbon $date): Collection
{ {
$repository = app(JournalRepositoryInterface::class); $range = Preferences::get('viewRange', '1M')->data;
$first = $repository->first(); $first = $this->repository->first();
$start = $first->date ?? new Carbon; $start = (new Carbon)->subYear();
$range = Preferences::get('viewRange', '1M')->data; $types = config('firefly.transactionTypesByWhat.' . $what);
$start = app('navigation')->startOfPeriod($start, $range); $entries = new Collection;
$end = app('navigation')->endOfX(new Carbon, $range, null); if (null !== $first) {
$entries = new Collection; $start = $first->date;
$types = config('firefly.transactionTypesByWhat.' . $what);
// properties for cache
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($what);
$cache->addProperty('transaction-list-entries');
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
} }
if ($date < $start) {
list($start, $date) = [$date, $start]; // @codeCoverageIgnore
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $date, $range);
Log::debug(sprintf('Going to get period expenses and incomes between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d'))); foreach ($dates as $currentDate) {
while ($end >= $start) {
Log::debug('Loop start!');
$end = app('navigation')->startOfPeriod($end, $range);
$currentEnd = app('navigation')->endOfPeriod($end, $range);
// count journals without budget in this period:
/** @var JournalCollectorInterface $collector */ /** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class); $collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withOpposingAccount()->setTypes($types); $collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->withOpposingAccount()->setTypes($types);
$collector->removeFilter(InternalTransferFilter::class); $collector->removeFilter(InternalTransferFilter::class);
$journals = $collector->getJournals(); $journals = $collector->getJournals();
$sum = $journals->sum('transaction_amount');
// count per currency:
$sums = $this->sumPerCurrency($journals);
$dateStr = $end->format('Y-m-d');
$dateName = app('navigation')->periodShow($end, $range);
$array = [
'string' => $dateStr,
'name' => $dateName,
'sum' => $sum,
'sums' => $sums,
'date' => clone $end,
];
Log::debug(sprintf('What is %s', $what));
if ($journals->count() > 0) { if ($journals->count() > 0) {
$entries->push($array); // @codeCoverageIgnore $sums = $this->sumPerCurrency($journals);
$dateName = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
$sum = $journals->sum('transaction_amount');
$entries->push(
[
'name' => $dateName,
'sums' => $sums,
'sum' => $sum,
'start' => $currentDate['start']->format('Y-m-d'),
'end' => $currentDate['end']->format('Y-m-d'),
]
);
} }
$end = app('navigation')->subtractPeriod($end, $range, 1);
} }
Log::debug('End of loop');
$cache->store($entries);
return $entries; return $entries;
} }
@ -287,7 +319,7 @@ class TransactionController extends Controller
$return = []; $return = [];
/** @var Transaction $transaction */ /** @var Transaction $transaction */
foreach ($collection as $transaction) { foreach ($collection as $transaction) {
$currencyId = intval($transaction->transaction_currency_id); $currencyId = (int)$transaction->transaction_currency_id;
// save currency information: // save currency information:
if (!isset($return[$currencyId])) { if (!isset($return[$currencyId])) {

View File

@ -68,6 +68,8 @@ use Watson\Validating\ValidatingTrait;
* @property int $transaction_currency_dp * @property int $transaction_currency_dp
* @property string $transaction_currency_code * @property string $transaction_currency_code
* @property string $description * @property string $description
* @property bool $is_split
* @property int $attachmentCount
*/ */
class Transaction extends Model class Transaction extends Model
{ {

View File

@ -103,8 +103,8 @@ class EventServiceProvider extends ServiceProvider
function (PiggyBank $piggyBank) { function (PiggyBank $piggyBank) {
$repetition = new PiggyBankRepetition; $repetition = new PiggyBankRepetition;
$repetition->piggyBank()->associate($piggyBank); $repetition->piggyBank()->associate($piggyBank);
$repetition->startdate = null === $piggyBank->startdate ? null : $piggyBank->startdate; $repetition->startdate = $piggyBank->startdate;
$repetition->targetdate = null === $piggyBank->targetdate ? null : $piggyBank->targetdate; $repetition->targetdate = $piggyBank->targetdate;
$repetition->currentamount = 0; $repetition->currentamount = 0;
$repetition->save(); $repetition->save();
} }

View File

@ -120,44 +120,40 @@ class Transaction extends Twig_Extension
} }
/** /**
*
* @param TransactionModel $transaction * @param TransactionModel $transaction
* *
* @return string * @return string
*/ */
public function budgets(TransactionModel $transaction): string public function budgets(TransactionModel $transaction): string
{ {
$txt = '';
// journal has a budget: // journal has a budget:
if (isset($transaction->transaction_journal_budget_id)) { if (null !== $transaction->transaction_journal_budget_id) {
$name = app('steam')->tryDecrypt($transaction->transaction_journal_budget_name); $name = app('steam')->tryDecrypt($transaction->transaction_journal_budget_name);
$txt = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$transaction->transaction_journal_budget_id]), $name, $name); $txt = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$transaction->transaction_journal_budget_id]), $name, $name);
return $txt;
} }
// transaction has a budget // transaction has a budget
if (isset($transaction->transaction_budget_id)) { if (null !== $transaction->transaction_budget_id && $txt === '') {
$name = app('steam')->tryDecrypt($transaction->transaction_budget_name); $name = app('steam')->tryDecrypt($transaction->transaction_budget_name);
$txt = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$transaction->transaction_budget_id]), $name, $name); $txt = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$transaction->transaction_budget_id]), $name, $name);
return $txt;
} }
// see if the transaction has a budget: if ($txt === '') {
$budgets = $transaction->budgets()->get(); // see if the transaction has a budget:
if (0 === $budgets->count()) { $budgets = $transaction->budgets()->get();
$budgets = $transaction->transactionJournal()->first()->budgets()->get(); if (0 === $budgets->count()) {
} $budgets = $transaction->transactionJournal()->first()->budgets()->get();
if ($budgets->count() > 0) { }
$str = []; if ($budgets->count() > 0) {
foreach ($budgets as $budget) { $str = [];
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$budget->id]), $budget->name, $budget->name); foreach ($budgets as $budget) {
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('budgets.show', [$budget->id]), $budget->name, $budget->name);
}
$txt = implode(', ', $str);
} }
$txt = join(', ', $str);
return $txt;
} }
$txt = '';
return $txt; return $txt;
} }
@ -169,40 +165,35 @@ class Transaction extends Twig_Extension
*/ */
public function categories(TransactionModel $transaction): string public function categories(TransactionModel $transaction): string
{ {
$txt = '';
// journal has a category: // journal has a category:
if (isset($transaction->transaction_journal_category_id)) { if (null !== $transaction->transaction_journal_category_id) {
$name = app('steam')->tryDecrypt($transaction->transaction_journal_category_name); $name = app('steam')->tryDecrypt($transaction->transaction_journal_category_name);
$txt = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$transaction->transaction_journal_category_id]), $name, $name); $txt = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$transaction->transaction_journal_category_id]), $name, $name);
return $txt;
} }
// transaction has a category: // transaction has a category:
if (isset($transaction->transaction_category_id)) { if (null !== $transaction->transaction_category_id && $txt === '') {
$name = app('steam')->tryDecrypt($transaction->transaction_category_name); $name = app('steam')->tryDecrypt($transaction->transaction_category_name);
$txt = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$transaction->transaction_category_id]), $name, $name); $txt = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$transaction->transaction_category_id]), $name, $name);
return $txt;
} }
// see if the transaction has a category: if ($txt === '') {
$categories = $transaction->categories()->get(); // see if the transaction has a category:
if (0 === $categories->count()) { $categories = $transaction->categories()->get();
$categories = $transaction->transactionJournal()->first()->categories()->get(); if (0 === $categories->count()) {
} $categories = $transaction->transactionJournal()->first()->categories()->get();
if ($categories->count() > 0) {
$str = [];
foreach ($categories as $category) {
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$category->id]), $category->name, $category->name);
} }
if ($categories->count() > 0) {
$str = [];
foreach ($categories as $category) {
$str[] = sprintf('<a href="%s" title="%s">%s</a>', route('categories.show', [$category->id]), $category->name, $category->name);
}
$txt = join(', ', $str); $txt = implode(', ', $str);
}
return $txt;
} }
$txt = '';
return $txt; return $txt;
} }
@ -286,18 +277,25 @@ class Transaction extends Twig_Extension
*/ */
public function hasAttachments(TransactionModel $transaction): string public function hasAttachments(TransactionModel $transaction): string
{ {
$journalId = intval($transaction->journal_id);
$count = Attachment::whereNull('deleted_at')
->where('attachable_type', 'FireflyIII\Models\TransactionJournal')
->where('attachable_id', $journalId)
->count();
if ($count > 0) {
$res = sprintf('<i class="fa fa-paperclip" title="%s"></i>', Lang::choice('firefly.nr_of_attachments', $count, ['count' => $count]));
return $res;
}
$res = ''; $res = '';
if (is_int($transaction->attachmentCount) && $transaction->attachmentCount > 0) {
$res = sprintf(
'<i class="fa fa-paperclip" title="%s"></i>', Lang::choice(
'firefly.nr_of_attachments',
$transaction->attachmentCount, ['count' => $transaction->attachmentCount]
)
);
}
if ($transaction->attachmentCount === null) {
$journalId = (int)$transaction->journal_id;
$count = Attachment::whereNull('deleted_at')
->where('attachable_type', 'FireflyIII\Models\TransactionJournal')
->where('attachable_id', $journalId)
->count();
if ($count > 0) {
$res = sprintf('<i class="fa fa-paperclip" title="%s"></i>', Lang::choice('firefly.nr_of_attachments', $count, ['count' => $count]));
}
}
return $res; return $res;
} }
@ -341,7 +339,7 @@ class Transaction extends Twig_Extension
public function isReconciled(TransactionModel $transaction): string public function isReconciled(TransactionModel $transaction): string
{ {
$icon = ''; $icon = '';
if (1 === intval($transaction->reconciled)) { if (1 === (int)$transaction->reconciled) {
$icon = '<i class="fa fa-check"></i>'; $icon = '<i class="fa fa-check"></i>';
} }
@ -349,21 +347,26 @@ class Transaction extends Twig_Extension
} }
/** /**
* Returns an icon when the transaction is a split transaction.
*
* @param TransactionModel $transaction * @param TransactionModel $transaction
* *
* @return string * @return string
*/ */
public function isSplit(TransactionModel $transaction): string public function isSplit(TransactionModel $transaction): string
{ {
$journalId = intval($transaction->journal_id); $res = '';
$count = TransactionModel::where('transaction_journal_id', $journalId)->whereNull('deleted_at')->count(); if ($transaction->is_split === true) {
if ($count > 2) { $res = '<i class="fa fa-fw fa-share-alt" aria-hidden="true"></i>!!!';
$res = '<i class="fa fa-fw fa-share-alt" aria-hidden="true"></i>';
return $res;
} }
$res = ''; if ($transaction->is_split === null) {
$journalId = (int)$transaction->journal_id;
$count = TransactionModel::where('transaction_journal_id', $journalId)->whereNull('deleted_at')->count();
if ($count > 2) {
$res = '<i class="fa fa-fw fa-share-alt" aria-hidden="true"></i>';
}
}
return $res; return $res;
} }

View File

@ -35,34 +35,31 @@
</td> </td>
<td style="text-align: right;"><span style="margin-right:5px;">{{ transaction|transactionAmount }}</span></td> <td style="text-align: right;"><span style="margin-right:5px;">{{ transaction|transactionAmount }}</span></td>
<td class="hidden-sm hidden-xs"> <td class="hidden-sm hidden-xs">
{{ transaction.date.formatLocalized(monthAndDayFormat) }} {{ transaction.date.formatLocalized(monthAndDayFormat) }}
</td> </td>
<td class="hidden-xs hidden-sm hidden-md"> <td class="hidden-xs hidden-sm hidden-md">
{# all source accounts #}
{{ transaction|transactionSourceAccount }} {{ transaction|transactionSourceAccount }}
</td> </td>
<td class="hidden-xs hidden-sm hidden-md"> <td class="hidden-xs hidden-sm hidden-md">
{# all destination accounts #}
{{ transaction|transactionDestinationAccount }} {{ transaction|transactionDestinationAccount }}
</td> </td>
{# Do NOT hide the budget? #}
{% if not hideBudgets %} {% if not hideBudgets %}
<td class="hidden-xs"> <td class="hidden-xs">
{{ transaction|transactionBudgets }} {{ transaction|transactionBudgets }}
</td> </td>
{% endif %} {% endif %}
{# Do NOT hide the category? #}
{% if not hideCategories %} {% if not hideCategories %}
<td class="hidden-xs"> <td class="hidden-xs">
{{ transaction|transactionCategories }} {{ transaction|transactionCategories }}
</td> </td>
{% endif %} {% endif %}
{# Do NOT hide the bill? #}
{% if not hideBills %} {% if not hideBills %}
<td class="hidden-xs"> <td class="hidden-xs">
{% if transaction.bill_id %} {% if transaction.bill_id %}

View File

@ -1,7 +1,7 @@
{% extends "./layout/default" %} {% extends "./layout/default" %}
{% block breadcrumbs %} {% block breadcrumbs %}
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, what, moment, start, end) }} {{ Breadcrumbs.render(Route.getCurrentRoute.getName, what, start, end) }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -10,7 +10,7 @@
{% if periods.count > 0 %} {% if periods.count > 0 %}
<div class="row"> <div class="row">
<div class="col-lg-offset-10 col-lg-2 col-md-offset-10 col-md-2 col-sm-12 col-xs-12"> <div class="col-lg-offset-10 col-lg-2 col-md-offset-10 col-md-2 col-sm-12 col-xs-12">
<p class="small text-center"><a href="{{ route('transactions.index',[what, 'all']) }}">{{ 'showEverything'|_ }}</a></p> <p class="small text-center"><a href="{{ route('transactions.index.all',[what]) }}">{{ 'showEverything'|_ }}</a></p>
</div> </div>
</div> </div>
{% endif %} {% endif %}
@ -38,7 +38,7 @@
{% if periods.count > 0 %} {% if periods.count > 0 %}
<p> <p>
<i class="fa fa-calendar"></i> <i class="fa fa-calendar"></i>
<a href="{{ route('transactions.index', [what, 'all']) }}">{{ 'show_all_no_filter'|_ }}</a> <a href="{{ route('transactions.index.all', [what]) }}">{{ 'show_all_no_filter'|_ }}</a>
</p> </p>
{% else %} {% else %}
<p> <p>
@ -56,10 +56,9 @@
{% for period in periods %} {% for period in periods %}
{% if period.sum != 0 %} {% if period.sum != 0 %}
<div class="box">
<div class="box {% if period.date == start %}box-solid box-primary{% endif %}">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title"><a href="{{ route('transactions.index',[what, period.string]) }}">{{ period.name }}</a> <h3 class="box-title"><a href="{{ route('transactions.index',[what, period.start,period.end]) }}">{{ period.name }}</a>
</h3> </h3>
</div> </div>
<div class="box-body no-padding"> <div class="box-body no-padding">
@ -91,7 +90,6 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
{% endif %} {% endif %}
@ -105,7 +103,7 @@
{% if periods.count > 0 %} {% if periods.count > 0 %}
<div class="row"> <div class="row">
<div class="col-lg-offset-10 col-lg-2 col-md-offset-10 col-md-2 col-sm-12 col-xs-12"> <div class="col-lg-offset-10 col-lg-2 col-md-offset-10 col-md-2 col-sm-12 col-xs-12">
<p class="small text-center"><a href="{{ route('transactions.index',[what, 'all']) }}">{{ 'showEverything'|_ }}</a></p> <p class="small text-center"><a href="{{ route('transactions.index.all',[what]) }}">{{ 'showEverything'|_ }}</a></p>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View File

@ -877,31 +877,37 @@ Breadcrumbs::register(
); );
// TRANSACTIONS // TRANSACTIONS
Breadcrumbs::register( Breadcrumbs::register(
'transactions.index', 'transactions.index',
function (BreadCrumbsGenerator $breadcrumbs, string $what, string $moment = '', Carbon $start, Carbon $end) { function (BreadCrumbsGenerator $breadcrumbs, string $what, Carbon $start = null, Carbon $end = null) {
$breadcrumbs->parent('home'); $breadcrumbs->parent('home');
$breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what]));
if ('all' === $moment) {
$breadcrumbs->push(trans('firefly.everything'), route('transactions.index', [$what, 'all']));
}
// when is specific period or when empty: if (null !== $start && null !== $end) {
if ('all' !== $moment && '(nothing)' !== $moment) { // add date range:
$title = trans( $title = trans(
'firefly.between_dates_breadcrumb', 'firefly.between_dates_breadcrumb',
['start' => $start->formatLocalized(strval(trans('config.month_and_day'))), ['start' => $start->formatLocalized((string)trans('config.month_and_day')),
'end' => $end->formatLocalized(strval(trans('config.month_and_day'))),] 'end' => $end->formatLocalized((string)trans('config.month_and_day')),]
); );
$breadcrumbs->push($title, route('transactions.index', [$what, $moment])); $breadcrumbs->push($title, route('transactions.index', [$what, $start, $end]));
} }
} }
); );
Breadcrumbs::register(
'transactions.index.all',
function (BreadCrumbsGenerator $breadcrumbs, string $what, Carbon $start = null, Carbon $end = null) {
$breadcrumbs->parent('home');
$breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what]));
}
);
Breadcrumbs::register( Breadcrumbs::register(
'transactions.create', 'transactions.create',
function (BreadCrumbsGenerator $breadcrumbs, string $what) { function (BreadCrumbsGenerator $breadcrumbs, string $what) {
$breadcrumbs->parent('transactions.index', $what, '(nothing)', new Carbon, new Carbon); $breadcrumbs->parent('transactions.index', $what);
$breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what])); $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what]));
} }
); );
@ -937,7 +943,7 @@ Breadcrumbs::register(
'transactions.show', 'transactions.show',
function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) {
$what = strtolower($journal->transactionType->type); $what = strtolower($journal->transactionType->type);
$breadcrumbs->parent('transactions.index', $what, '(nothing)', new Carbon, new Carbon); $breadcrumbs->parent('transactions.index', $what);
$breadcrumbs->push($journal->description, route('transactions.show', [$journal->id])); $breadcrumbs->push($journal->description, route('transactions.show', [$journal->id]));
} }
); );
@ -960,7 +966,7 @@ Breadcrumbs::register(
if ($journals->count() > 0) { if ($journals->count() > 0) {
$journalIds = $journals->pluck('id')->toArray(); $journalIds = $journals->pluck('id')->toArray();
$what = strtolower($journals->first()->transactionType->type); $what = strtolower($journals->first()->transactionType->type);
$breadcrumbs->parent('transactions.index', $what, '(nothing)', new Carbon, new Carbon); $breadcrumbs->parent('transactions.index');
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds)); $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.edit', $journalIds));
return; return;
@ -977,7 +983,7 @@ Breadcrumbs::register(
function (BreadCrumbsGenerator $breadcrumbs, Collection $journals) { function (BreadCrumbsGenerator $breadcrumbs, Collection $journals) {
$journalIds = $journals->pluck('id')->toArray(); $journalIds = $journals->pluck('id')->toArray();
$what = strtolower($journals->first()->transactionType->type); $what = strtolower($journals->first()->transactionType->type);
$breadcrumbs->parent('transactions.index', $what, '(nothing)', new Carbon, new Carbon); $breadcrumbs->parent('transactions.index');
$breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', $journalIds)); $breadcrumbs->push(trans('firefly.mass_edit_journals'), route('transactions.mass.delete', $journalIds));
} }
); );
@ -989,7 +995,7 @@ Breadcrumbs::register(
if ($journals->count() > 0) { if ($journals->count() > 0) {
$journalIds = $journals->pluck('id')->toArray(); $journalIds = $journals->pluck('id')->toArray();
$what = strtolower($journals->first()->transactionType->type); $what = strtolower($journals->first()->transactionType->type);
$breadcrumbs->parent('transactions.index', $what, '(nothing)', new Carbon, new Carbon); $breadcrumbs->parent('transactions.index');
$breadcrumbs->push(trans('firefly.mass_bulk_journals'), route('transactions.bulk.edit', $journalIds)); $breadcrumbs->push(trans('firefly.mass_bulk_journals'), route('transactions.bulk.edit', $journalIds));
return; return;

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
Route::group( Route::group(
['namespace' => 'FireflyIII\Http\Controllers\System', ['namespace' => 'FireflyIII\Http\Controllers\System',
'as' => 'installer.', 'prefix' => 'install'], function () { 'as' => 'installer.', 'prefix' => 'install'], function () {
Route::get('', ['uses' => 'InstallController@index', 'as' => 'index']); Route::get('', ['uses' => 'InstallController@index', 'as' => 'index']);
Route::post('migrate', ['uses' => 'InstallController@migrate', 'as' => 'migrate']); Route::post('migrate', ['uses' => 'InstallController@migrate', 'as' => 'migrate']);
Route::post('keys', ['uses' => 'InstallController@keys', 'as' => 'keys']); Route::post('keys', ['uses' => 'InstallController@keys', 'as' => 'keys']);
@ -780,7 +780,12 @@ Route::group(
*/ */
Route::group( Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'transactions', 'as' => 'transactions.'], function () { ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'transactions', 'as' => 'transactions.'], function () {
Route::get('{what}/{moment?}', ['uses' => 'TransactionController@index', 'as' => 'index'])->where(['what' => 'withdrawal|deposit|transfers|transfer']);
Route::get('{what}/all', ['uses' => 'TransactionController@indexAll', 'as' => 'index.all'])->where(['what' => 'withdrawal|deposit|transfers|transfer']);
Route::get('{what}/{start_date?}/{end_date?}', ['uses' => 'TransactionController@index', 'as' => 'index'])->where(
['what' => 'withdrawal|deposit|transfers|transfer']
);
Route::get('show/{tj}', ['uses' => 'TransactionController@show', 'as' => 'show']); Route::get('show/{tj}', ['uses' => 'TransactionController@show', 'as' => 'show']);
Route::post('reorder', ['uses' => 'TransactionController@reorder', 'as' => 'reorder']); Route::post('reorder', ['uses' => 'TransactionController@reorder', 'as' => 'reorder']);
Route::post('reconcile', ['uses' => 'TransactionController@reconcile', 'as' => 'reconcile']); Route::post('reconcile', ['uses' => 'TransactionController@reconcile', 'as' => 'reconcile']);