diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php new file mode 100644 index 0000000000..27398a2fab --- /dev/null +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -0,0 +1,137 @@ +start; + $dayBefore->subDay(); + /** @var Account $account */ + foreach ($this->accounts as $account) { + // balance the day before: + $id = $account->id; + $dayBeforeBalance = Steam::balance($account, $dayBefore); + $collector = new JournalCollector(auth()->user()); + $collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end); + $journals = $collector->getJournals(); + $journals = $journals->reverse(); + $startBalance = $dayBeforeBalance; + + + /** @var Transaction $journal */ + foreach ($journals as $transaction) { + $transaction->before = $startBalance; + $transactionAmount = $transaction->transaction_amount; + $newBalance = bcadd($startBalance, $transactionAmount); + $transaction->after = $newBalance; + $startBalance = $newBalance; + } + + /* + * Reverse set again. + */ + $auditData[$id]['journals'] = $journals->reverse(); + $auditData[$id]['exists'] = $journals->count() > 0; + $auditData[$id]['end'] = $this->end->formatLocalized(strval(trans('config.month_and_day'))); + $auditData[$id]['endBalance'] = Steam::balance($account, $this->end); + $auditData[$id]['dayBefore'] = $dayBefore->formatLocalized(strval(trans('config.month_and_day'))); + $auditData[$id]['dayBeforeBalance'] = $dayBeforeBalance; + } + + $reportType = 'audit'; + $accountIds = join(',', $this->accounts->pluck('id')->toArray()); + + $hideable = ['buttons', 'icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', + 'interest_date', 'book_date', 'process_date', + // three new optional fields. + 'due_date', 'payment_date', 'invoice_date', + '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) + ->render(); + + } + + /** + * @param Collection $accounts + * + * @return ReportGeneratorInterface + */ + public function setAccounts(Collection $accounts): ReportGeneratorInterface + { + $this->accounts = $accounts; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setEndDate(Carbon $date): ReportGeneratorInterface + { + $this->end = $date; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setStartDate(Carbon $date): ReportGeneratorInterface + { + $this->start = $date; + + return $this; + } +} \ No newline at end of file diff --git a/app/Generator/Report/Audit/MultiYearReportGenerator.php b/app/Generator/Report/Audit/MultiYearReportGenerator.php new file mode 100644 index 0000000000..6d9e2857b8 --- /dev/null +++ b/app/Generator/Report/Audit/MultiYearReportGenerator.php @@ -0,0 +1,27 @@ +diffInMonths($end) > 12) { + $period = 'MultiYear'; + } + // more than two months date difference means year report. + if ($start->diffInMonths($end) > 1) { + $period = 'Year'; + } + + $class = sprintf('FireflyIII\Generator\Report\%s\%sReportGenerator', $type, $period); + if (class_exists($class)) { + /** @var ReportGeneratorInterface $obj */ + $obj = new $class; + $obj->setStartDate($start); + $obj->setEndDate($end); + + return $obj; + } + throw new FireflyException(sprintf('Class "%s" does not exist.', $class)); + } +} \ No newline at end of file diff --git a/app/Generator/Report/ReportGeneratorInterface.php b/app/Generator/Report/ReportGeneratorInterface.php new file mode 100644 index 0000000000..3d1770700e --- /dev/null +++ b/app/Generator/Report/ReportGeneratorInterface.php @@ -0,0 +1,53 @@ +getBillReport($this->start, $this->end, $this->accounts); + + // and some id's, joined: + $accountIds = join(',', $this->accounts->pluck('id')->toArray()); + $reportType = 'default'; + + // continue! + return view( + 'reports.default.month', + compact('bills', 'accountIds', 'reportType') + )->with('start', $this->start)->with('end', $this->end)->render(); + } + + /** + * @param Collection $accounts + * + * @return ReportGeneratorInterface + */ + public function setAccounts(Collection $accounts): ReportGeneratorInterface + { + $this->accounts = $accounts; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setEndDate(Carbon $date): ReportGeneratorInterface + { + $this->end = $date; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setStartDate(Carbon $date): ReportGeneratorInterface + { + $this->start = $date; + + return $this; + } +} \ No newline at end of file diff --git a/app/Generator/Report/Standard/MultiYearReportGenerator.php b/app/Generator/Report/Standard/MultiYearReportGenerator.php new file mode 100644 index 0000000000..e3ab423b7d --- /dev/null +++ b/app/Generator/Report/Standard/MultiYearReportGenerator.php @@ -0,0 +1,87 @@ +accounts->pluck('id')->toArray()); + $reportType = 'default'; + + // continue! + return view( + 'reports.default.multi-year', + compact('accountIds', 'reportType') + )->with('start', $this->start)->with('end', $this->end)->render(); + } + + /** + * @param Collection $accounts + * + * @return ReportGeneratorInterface + */ + public function setAccounts(Collection $accounts): ReportGeneratorInterface + { + $this->accounts = $accounts; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setEndDate(Carbon $date): ReportGeneratorInterface + { + $this->end = $date; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setStartDate(Carbon $date): ReportGeneratorInterface + { + $this->start = $date; + + return $this; + } +} \ No newline at end of file diff --git a/app/Generator/Report/Standard/YearReportGenerator.php b/app/Generator/Report/Standard/YearReportGenerator.php new file mode 100644 index 0000000000..265d79e9fe --- /dev/null +++ b/app/Generator/Report/Standard/YearReportGenerator.php @@ -0,0 +1,87 @@ +accounts->pluck('id')->toArray()); + $reportType = 'default'; + + // continue! + return view( + 'reports.default.year', + compact('accountIds', 'reportType') + )->with('start', $this->start)->with('end', $this->end)->render(); + } + + /** + * @param Collection $accounts + * + * @return ReportGeneratorInterface + */ + public function setAccounts(Collection $accounts): ReportGeneratorInterface + { + $this->accounts = $accounts; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setEndDate(Carbon $date): ReportGeneratorInterface + { + $this->end = $date; + + return $this; + } + + /** + * @param Carbon $date + * + * @return ReportGeneratorInterface + */ + public function setStartDate(Carbon $date): ReportGeneratorInterface + { + $this->start = $date; + + return $this; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index b01e4fcbaa..2a24548513 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -15,12 +15,18 @@ namespace FireflyIII\Http\Controllers; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Generator\Report\ReportGeneratorFactory; +use FireflyIII\Generator\Report\Standard\MonthReportGenerator; +use FireflyIII\Generator\Report\StandardReportGenerator; use FireflyIII\Helpers\Collector\JournalCollector; use FireflyIII\Helpers\Report\ReportHelperInterface; +use FireflyIII\Http\Requests\ReportFormRequest; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use Illuminate\Http\RedirectResponse; use Illuminate\Support\Collection; use Preferences; use Response; @@ -59,6 +65,84 @@ class ReportController extends Controller } + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return string + * @throws FireflyException + */ + public function auditReport(Carbon $start, Carbon $end, Collection $accounts) + { + // throw an error if necessary. + if ($end < $start) { + throw new FireflyException('End date cannot be before start date, silly!'); + } + + // lower threshold + if ($start < session('first')) { + $start = session('first'); + } + + View::share( + 'subTitle', trans( + 'firefly.report_audit', + [ + 'start' => $start->formatLocalized($this->monthFormat), + 'end' => $end->formatLocalized($this->monthFormat), + ] + ) + ); + View::share('subTitleIcon', 'fa-calendar'); + + $generator = ReportGeneratorFactory::reportGenerator('Audit', $start, $end); + $generator->setAccounts($accounts); + $result = $generator->generate(); + + return $result; + + } + + /** + * @param Carbon $start + * @param Carbon $end + * @param Collection $accounts + * + * @return string + * @throws FireflyException + */ + public function defaultReport(Carbon $start, Carbon $end, Collection $accounts) + { + // throw an error if necessary. + if ($end < $start) { + throw new FireflyException('End date cannot be before start date, silly!'); + } + + // lower threshold + if ($start < session('first')) { + $start = session('first'); + } + + View::share( + 'subTitle', trans( + 'firefly.report_default', + [ + 'start' => $start->formatLocalized($this->monthFormat), + 'end' => $end->formatLocalized($this->monthFormat), + ] + ) + ); + View::share('subTitleIcon', 'fa-calendar'); + + $generator = ReportGeneratorFactory::reportGenerator('Standard', $start, $end); + $generator->setAccounts($accounts); + $result = $generator->generate(); + + return $result; + + } + /** * @param AccountRepositoryInterface $repository * @@ -93,28 +177,34 @@ class ReportController extends Controller */ public function options(string $reportType) { - $result = false; + $result = ''; switch ($reportType) { default: $result = $this->noReportOptions(); break; + case 'category': + $result = $this->categoryReportOptions(); + break; } - return Response::json($result); + return Response::json(['html' => $result]); } /** - * @param string $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts + * @param ReportFormRequest $request * - * @return View + * @return RedirectResponse * @throws FireflyException */ - public function report(string $reportType, Carbon $start, Carbon $end, Collection $accounts) + public function postIndex(ReportFormRequest $request): RedirectResponse { - // throw an error if necessary. + // report type: + $reportType = $request->get('report_type'); + $start = $request->getStartDate()->format('Ymd'); + $end = $request->getEndDate()->format('Ymd'); + $accounts = join(',', $request->getAccountList()->pluck('id')->toArray()); + $categories = join(',', $request->getCategoryList()->pluck('id')->toArray()); + if ($end < $start) { throw new FireflyException('End date cannot be before start date, silly!'); } @@ -124,198 +214,42 @@ class ReportController extends Controller $start = session('first'); } - View::share( - 'subTitle', trans( - 'firefly.report_' . $reportType, - [ - 'start' => $start->formatLocalized($this->monthFormat), - 'end' => $end->formatLocalized($this->monthFormat), - ] - ) - ); - View::share('subTitleIcon', 'fa-calendar'); - switch ($reportType) { default: - throw new FireflyException('Unfortunately, reports of the type "' . e($reportType) . '" are not available at this time.'); + throw new FireflyException(sprintf('Firefly does not support the "%s"-report yet.', $reportType)); + case 'category': + $uri = route('reports.report.category', [$start, $end, $accounts, $categories]); + break; case 'default': - - // more than one year date difference means year report. - if ($start->diffInMonths($end) > 12) { - return $this->defaultMultiYear($reportType, $start, $end, $accounts); - } - // more than two months date difference means year report. - if ($start->diffInMonths($end) > 1) { - return $this->defaultYear($reportType, $start, $end, $accounts); - } - - // otherwise default - return $this->defaultMonth($reportType, $start, $end, $accounts); + $uri = route('reports.report.default', [$start, $end, $accounts]); + break; case 'audit': - // always default - return $this->auditReport($start, $end, $accounts); + $uri = route('reports.report.audit', [$start, $end, $accounts]); + break; } + return redirect($uri); + } + + /** + * @return string + */ + private function categoryReportOptions(): string + { + /** @var CategoryRepositoryInterface $repository */ + $repository = app(CategoryRepositoryInterface::class); + $categories = $repository->getCategories(); + $result = view('reports.options.category', compact('categories'))->render(); + + return $result; } /** - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return View + * @return string */ - private function auditReport(Carbon $start, Carbon $end, Collection $accounts) + private function noReportOptions(): string { - $auditData = []; - $dayBefore = clone $start; - $dayBefore->subDay(); - /** @var Account $account */ - foreach ($accounts as $account) { - // balance the day before: - $id = $account->id; - $dayBeforeBalance = Steam::balance($account, $dayBefore); - $collector = new JournalCollector(auth()->user()); - $collector->setAccounts(new Collection([$account]))->setRange($start, $end); - $journals = $collector->getJournals(); - $journals = $journals->reverse(); - $startBalance = $dayBeforeBalance; - - - /** @var Transaction $journal */ - foreach ($journals as $transaction) { - $transaction->before = $startBalance; - $transactionAmount = $transaction->transaction_amount; - $newBalance = bcadd($startBalance, $transactionAmount); - $transaction->after = $newBalance; - $startBalance = $newBalance; - } - - /* - * Reverse set again. - */ - $auditData[$id]['journals'] = $journals->reverse(); - $auditData[$id]['exists'] = $journals->count() > 0; - $auditData[$id]['end'] = $end->formatLocalized(strval(trans('config.month_and_day'))); - $auditData[$id]['endBalance'] = Steam::balance($account, $end); - $auditData[$id]['dayBefore'] = $dayBefore->formatLocalized(strval(trans('config.month_and_day'))); - $auditData[$id]['dayBeforeBalance'] = $dayBeforeBalance; - } - - $reportType = 'audit'; - $accountIds = join(',', $accounts->pluck('id')->toArray()); - - $hideable = ['buttons', 'icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', - 'interest_date', 'book_date', 'process_date', - // three new optional fields. - 'due_date', 'payment_date', 'invoice_date', - '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('start', 'end', 'reportType', 'accountIds', 'accounts', 'auditData', 'hideable', 'defaultShow')); - } - - /** - * @param $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return View - */ - private function defaultMonth(string $reportType, Carbon $start, Carbon $end, Collection $accounts) - { - $bills = $this->helper->getBillReport($start, $end, $accounts); - $tags = $this->helper->tagReport($start, $end, $accounts); - - // and some id's, joined: - $accountIds = join(',', $accounts->pluck('id')->toArray()); - - // continue! - return view( - 'reports.default.month', - compact( - 'start', 'end', - 'tags', - 'bills', - 'accountIds', - 'reportType' - ) - ); - } - - /** - * @param $reportType - * @param $start - * @param $end - * @param $accounts - * - * @return View - */ - private function defaultMultiYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts) - { - // need all budgets - // need all years. - - - // and some id's, joined: - $accountIds = []; - /** @var Account $account */ - foreach ($accounts as $account) { - $accountIds[] = $account->id; - } - $accountIds = join(',', $accountIds); - - return view( - 'reports.default.multi-year', - compact( - 'accounts', 'start', 'end', 'accountIds', 'reportType' - ) - ); - } - - /** - * @param $reportType - * @param Carbon $start - * @param Carbon $end - * @param Collection $accounts - * - * @return View - */ - private function defaultYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts) - { - Session::flash('gaEventCategory', 'report'); - Session::flash('gaEventAction', 'year'); - Session::flash('gaEventLabel', $start->format('Y')); - - // and some id's, joined: - $accountIds = []; - /** @var Account $account */ - foreach ($accounts as $account) { - $accountIds[] = $account->id; - } - $accountIds = join(',', $accountIds); - - return view( - 'reports.default.year', - compact( - 'start', 'reportType', - 'accountIds', 'end' - ) - ); - } - - /** - * @return array - */ - private function noReportOptions(): array - { - return ['html' => view('reports.options.no-options')->render()]; + return view('reports.options.no-options')->render(); } } diff --git a/app/Http/Requests/ReportFormRequest.php b/app/Http/Requests/ReportFormRequest.php new file mode 100644 index 0000000000..6219cce4ea --- /dev/null +++ b/app/Http/Requests/ReportFormRequest.php @@ -0,0 +1,128 @@ +check(); + } + + /** + * @return Collection + */ + public function getAccountList():Collection + { + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $set = $this->get('accounts'); + $collection = new Collection; + if (is_array($set)) { + foreach ($set as $accountId) { + $account = $repository->find(intval($accountId)); + if (!is_null($account->id)) { + $collection->push($account); + } + } + } + + return $collection; + } + + /** + * @return Collection + */ + public function getCategoryList(): Collection + { + /** @var CategoryRepositoryInterface $repository */ + $repository = app(CategoryRepositoryInterface::class); + $set = $this->get('category'); + $collection = new Collection; + if (is_array($set)) { + foreach ($set as $categoryId) { + $category = $repository->find(intval($categoryId)); + if (!is_null($category->id)) { + $collection->push($category); + } + } + } + + return $collection; + } + + public function getEndDate(): Carbon + { + $date = new Carbon; + $range = $this->get('daterange'); + $parts = explode(' - ', strval($range)); + if (count($parts) === 2) { + try { + $date = new Carbon($parts[1]); + } catch (Exception $e) { + throw new FireflyException(sprintf('"%s" is not a valid date range.', $range)); + } + } + + return $date; + } + + /** + * @return Carbon + * @throws FireflyException + */ + public function getStartDate(): Carbon + { + $date = new Carbon; + $range = $this->get('daterange'); + $parts = explode(' - ', strval($range)); + if (count($parts) === 2) { + try { + $date = new Carbon($parts[0]); + } catch (Exception $e) { + throw new FireflyException(sprintf('"%s" is not a valid date range.', $range)); + } + } + + return $date; + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'report_type' => 'in:audit,default,category', + ]; + } + +} diff --git a/public/js/ff/reports/index.js b/public/js/ff/reports/index.js index 6edeccac2a..5df1bd03cc 100644 --- a/public/js/ff/reports/index.js +++ b/public/js/ff/reports/index.js @@ -55,23 +55,17 @@ function getReportOptions() { $('#extra-options').empty(); $('#extra-options').addClass('loading'); console.log('Changed report type to ' + reportType); - $.getJSON('reports/options/' + reportType, function(data) { + $.getJSON('reports/options/' + reportType, function (data) { $('#extra-options').removeClass('loading').html(data.html); - }).fail(function(){ + }).fail(function () { $('#extra-options').removeClass('loading').addClass('error'); }); } function catchSubmit() { "use strict"; - // default;20141201;20141231;4;5 - // report name: - var url = '' + $('select[name="report_type"]').val() + '/'; - // date, processed: var picker = $('#inputDateRange').data('daterangepicker'); - url += moment(picker.startDate).format("YYYYMMDD") + '/'; - url += moment(picker.endDate).format("YYYYMMDD") + '/'; // all account ids: var count = 0; @@ -79,23 +73,24 @@ function catchSubmit() { $.each($('.account-checkbox'), function (i, v) { var c = $(v); if (c.prop('checked')) { - url += c.val() + ','; accounts.push(c.val()); count++; } }); + + // all category ids to come + + + // remember all if (count > 0) { // set cookie to remember choices. createCookie('report-type', $('select[name="report_type"]').val(), 365); createCookie('report-accounts', accounts, 365); createCookie('report-start', moment(picker.startDate).format("YYYYMMDD"), 365); createCookie('report-end', moment(picker.endDate).format("YYYYMMDD"), 365); - - window.location.href = reportURL + "/" + url; } - //console.log(url); - return false; + return true; } function preSelectDate(e) { diff --git a/resources/views/reports/index.twig b/resources/views/reports/index.twig index 03ded3a6ab..886deb0477 100644 --- a/resources/views/reports/index.twig +++ b/resources/views/reports/index.twig @@ -7,7 +7,7 @@ {% block content %}