. */ declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalTaskerInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Http\Request; use Illuminate\Support\Collection; use Log; use Preferences; use Response; use View; /** * Class TransactionController. */ class TransactionController extends Controller { /** * TransactionController constructor. */ public function __construct() { parent::__construct(); $this->middleware( function ($request, $next) { app('view')->share('title', trans('firefly.transactions')); app('view')->share('mainTitleIcon', 'fa-repeat'); return $next($request); } ); } /** * @param Request $request * @param JournalRepositoryInterface $repository * @param string $what * @param string $moment * * @return View * * @throws FireflyException */ public function index(Request $request, JournalRepositoryInterface $repository, string $what, string $moment = '') { // default values: $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); $types = config('firefly.transactionTypesByWhat.' . $what); $page = intval($request->get('page')); $pageSize = intval(Preferences::get('listPageSize', 50)->data); $range = Preferences::get('viewRange', '1M')->data; $start = null; $end = null; $periods = new Collection; $path = route('transactions.index', [$what]); // prep for "all" view. if ('all' === $moment) { $subTitle = trans('firefly.all_' . $what); $first = $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]); $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)] ); } /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); $collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount(); $collector->removeFilter(InternalTransferFilter::class); $transactions = $collector->getPaginatedJournals(); $transactions->setPath($path); return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'transactions', 'periods', 'start', 'end', 'moment')); } /** * @param Request $request * @param JournalRepositoryInterface $repository * * @return \Illuminate\Http\JsonResponse */ public function reconcile(Request $request, JournalRepositoryInterface $repository) { $transactionIds = $request->get('transactions'); foreach ($transactionIds as $transactionId) { $transactionId = intval($transactionId); $transaction = $repository->findTransaction($transactionId); Log::debug(sprintf('Transaction ID is %d', $transaction->id)); $repository->reconcile($transaction); } return Response::json(['ok' => 'reconciled']); } /** * @param Request $request * @param JournalRepositoryInterface $repository * * @return \Illuminate\Http\JsonResponse */ public function reorder(Request $request, JournalRepositoryInterface $repository) { $ids = $request->get('items'); $date = new Carbon($request->get('date')); if (count($ids) > 0) { $order = 0; $ids = array_unique($ids); foreach ($ids as $id) { $journal = $repository->find(intval($id)); if ($journal && $journal->date->isSameDay($date)) { $repository->setOrder($journal, $order); ++$order; } } } Preferences::mark(); return Response::json([true]); } /** * @param TransactionJournal $journal * @param JournalTaskerInterface $tasker * @param LinkTypeRepositoryInterface $linkTypeRepository * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View */ public function show(TransactionJournal $journal, JournalTaskerInterface $tasker, LinkTypeRepositoryInterface $linkTypeRepository) { if ($this->isOpeningBalance($journal)) { return $this->redirectToAccount($journal); } if (TransactionType::RECONCILIATION === $journal->transactionType->type) { return redirect(route('accounts.reconcile.show', [$journal->id])); // @codeCoverageIgnore } $linkTypes = $linkTypeRepository->get(); $links = $linkTypeRepository->getLinks($journal); $events = $tasker->getPiggyBankEvents($journal); $transactions = $tasker->getTransactionsOverview($journal); $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); $subTitle = trans('firefly.' . $what) . ' "' . $journal->description . '"'; return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions', 'linkTypes', 'links')); } /** * @param string $what * * @return Collection */ private function getPeriodOverview(string $what): Collection { $repository = app(JournalRepositoryInterface::class); $first = $repository->first(); $start = $first->date ?? new Carbon; $range = Preferences::get('viewRange', '1M')->data; $start = app('navigation')->startOfPeriod($start, $range); $end = app('navigation')->endOfX(new Carbon, $range, null); $entries = new Collection; $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 } Log::debug(sprintf('Going to get period expenses and incomes between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d'))); 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 */ $collector = app(JournalCollectorInterface::class); $collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withOpposingAccount()->setTypes($types); $collector->removeFilter(InternalTransferFilter::class); $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) { $entries->push($array); // @codeCoverageIgnore } $end = app('navigation')->subtractPeriod($end, $range, 1); } Log::debug('End of loop'); $cache->store($entries); return $entries; } /** * @param Collection $collection * * @return array */ private function sumPerCurrency(Collection $collection): array { $return = []; /** @var Transaction $transaction */ foreach ($collection as $transaction) { $currencyId = $transaction->transaction_currency_id; // save currency information: if (!isset($return[$currencyId])) { $currencySymbol = $transaction->transaction_currency_symbol; $decimalPlaces = $transaction->transaction_currency_dp; $currencyCode = $transaction->transaction_currency_code; $return[$currencyId] = [ 'currency' => [ 'id' => $currencyId, 'code' => $currencyCode, 'symbol' => $currencySymbol, 'dp' => $decimalPlaces, ], 'sum' => '0', 'count' => 0, ]; } // save amount: $return[$currencyId]['sum'] = bcadd($return[$currencyId]['sum'], $transaction->transaction_amount); ++$return[$currencyId]['count']; } asort($return); return $return; } }